1

我有一个 RDLC 报告,我将其作为 PDF 直接呈现给响应流(而不是使用 ReportViewer)。在呈现报表的代码中,它的 DataSource 绑定到自定义程序集中定义的 List(Of ClassA) 对象。这似乎在大多数情况下都有效。我的问题是我似乎无法处理嵌套对象为空的情况。例如,给定 ClassA 和 ClassB(嵌套对象)定义如下:

    Public Class ClassA
       Public Id As Integer
       Public Name As String
       Public TheNestedObject As ClassB
    End Class

    Public Class ClassB
       Public Id As Integer
       Public Name As String
       Public TheParentObject As ClassA
    End Class

每当我尝试有条件地显示“不适用”时,如果 B 类在我的表达式中为空,如下所示:

=IIf(IsNothing(Fields!TheNestedObject.Value,"n/a", Fields!TheNestedObject.Value.Name))

如果 TheNestedObject 为空,则报告显示“#Error”。如果 TheNestedObject 不为空,它会正确显示名称。

我在这里做错了什么?

谢谢!!!

4

4 回答 4

1

iif 函数评估所有参数,因此 Fields!TheNestedObject.Value.Name 被评估并给出错误,因为 Fields!TheNestedObject.Value 为空。

我最终在报告中添加了一些自定义代码。它位于报告属性 -> 代码选项卡下。

Public Function GetName(ByRef obj As Object) As String
    If obj Is Nothing Then
        Return "n/a"
    Else : Return obj.Name
    End If
End Function

然后你的文本框表达式是:

=Code.GetName(Fields!TheNestedObject.Value)

该函数在为 null 时返回“n/a”,如果不是,则返回 Name 属性。

于 2009-11-11T23:05:12.723 回答
0

我不确定如何解决您的问题,但我有几个建议...

  1. 也许如果您将 IIF 语句更改为:

    IIf(IsNothing(Fields!TheNestedObject,"n/a", Fields!TheNestedObject.Value.Name))
    

    IIf总是评估所有的论点,所以它试图评估TheNestedObject.Value。如果TheNestedObjectis NULLor NOTHING,那么看到它抛出错误我不会感到惊讶。

  2. 另一个想法是修改您的构造函数以在没有“B”时添加一个“空”“B”对象。例如,A.TheNestedObject将指向没有数据的“B”对象。 B.Id除非您将其设为可为空的 Int,否则将为 0(默认情况下)。 B.Name将会 ””。等等。

于 2009-10-27T20:18:24.540 回答
0
=iif(First(Fields!model.Value, "model") Is Nothing, "value is NULL", "value is not NULL")
于 2011-12-30T12:12:33.330 回答
0

=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 及其AllowNullDestinationValuesAllowNullCollections、和功能,将我的所有域类映射到报告特定类,并将所有空引用替换为空对象(当将域对象空引用映射到报告对象时)或合理的默认值(例如,空字符串的空字符串或 ) 的底层原始类型的默认值。PreserveReferences()NullSubstituteForAllPropertyMaps()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);
    }
}
于 2021-01-31T10:15:59.017 回答