=IIf(IsNothing(Fields!TheNestedObject.Value,"n/a", Fields!TheNestedObject.Value.Name))
虽然您的表达式需要如下所示,但为了在语法上正确,它仍然会在将其结果用作IIf()
函数的参数之前评估 false 部分,因此仍将显示#Error
:
=IIf(IsNothing(Fields!TheNestedObject.Value),"n/a", Fields!TheNestedObject.Value.Name)
我首先通过实现Null 对象模式来解决 SSRS/RDLC 的这个缺点。当涉及多于一两个域对象时,手动实现这一点当然是太多的努力。
但是,由于我已经在使用 AutoMapper,@LucianBargaoanu 在评论中正确指出,AutoMapper 支持空对象作为可选功能,因此不需要显式实现。因此,我结合使用 AutoMapper 及其AllowNullDestinationValues
、AllowNullCollections
、和功能,将我的所有域类映射到报告特定类,并将所有空引用替换为空对象(当将域对象空引用映射到报告对象时)或合理的默认值(例如,空字符串的空字符串或 ) 的底层原始类型的默认值。PreserveReferences()
NullSubstitute
ForAllPropertyMaps()
Nullable<PrimitiveType>
下面是一些示例代码来演示该方法:
namespace Domain
{
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // could be null
public string Code {get; set;} // could be null
public decimal? SomeNullableValue {get; set;} // could be null
public MyOtherClass OptionalOtherClass {get; set;} // could be null
}
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // could be null
public decimal? SomeOtherNullableValue {get; set;} // could be null
}
}
namespace ReportViewModels
{
[Serializable]
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // should not be null (but empty)
public string Code {get; set;} // should not be null (but empty)
public decimal? SomeNullableValue {get; set;} // should not be null (but default(decimal))
public string CommonName
=> (Name + " " + Code).Trim();
public MyOtherClass OptionalOtherClass {get; set;} // should not be null (but a MyOtherClass null object)
}
[Serializable]
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // should not be null (but empty)
public decimal? SomeOtherNullableValue {get; set;} // should not be null (but default(decimal))
}
}
public partial class Form1 : Form
{
private Context _context;
private ReportObjectGenerator _reportObjectGenerator;
public Form1(Context context, ReportObjectGenerator reportObjectGenerator)
{
_context = context;
_reportObjectGenerator = reportObjectGenerator;
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var myDomainObjects = context.MyClass
.Include(e => e.OptionalOtherClass)
.ToList();
var myReportViewModels = _reportObjectGenerator.GetReportObjects<Domain.MyClass, ReportViewModels.MyClass>(myDomainObjects);
components ??= new System.ComponentModel.Container();
//reportViewer1.LocalReport.ReportEmbeddedResource = "MyNamespace.Report1.rdlc";
reportViewer1.LocalReport.ReportPath = "Report1.rdlc";
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(
new ReportDataSource
{
Name = "MyClassDataSet",
Value = new BindingSource(components)
{
DataMember = "MyClass",
DataSource = myReportViewModels
}
});
reportViewer1.RefreshReport();
}
}
public class ReportObjectGenerator
{
public List<TDestination> GetReportObjects<TSource, TDestination>(
IEnumerable<TSource> sourceObjects)
{
var domainNamespace = typeof(TSource).Namespace ?? throw new InvalidOperationException();
var reportNamespace = typeof(TDestination).Namespace ?? throw new InvalidOperationException();
var mapper = new MapperConfiguration(
cfg =>
{
cfg.AllowNullDestinationValues = false;
cfg.AllowNullCollections = false;
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).ToList();
var allDomainTypes = allTypes.Where(t => t.Namespace?.StartsWith(domainNamespace) ?? false).ToList();
var allReportTypes = allTypes.Where(t => t.Namespace?.StartsWith(reportNamespace) ?? false).ToList();
foreach (var reportClassType in allReportTypes)
{
var domainClassType = allDomainTypes.Single(t => t.Name == reportClassType.Name);
cfg.CreateMap(domainClassType, reportClassType)
.PreserveReferences();
}
// If we want to set the default value of the underlying type of Nullable<UnderlyingType>
// properties in case they would be null, than AllowNullDestinationValues is not enough and we
// need to manually replace the null value here.
cfg.ForAllPropertyMaps(
pm => pm.SourceMember.GetMemberType().IsNullableType(),
(p, _) => p.NullSubstitute ??= Activator.CreateInstance(p.SourceMember.GetMemberType().GetTypeOfNullable()));
})
.CreateMapper();
return mapper.Map<IEnumerable<TSource>, List<TDestination>>(sourceObjects);
}
}