Visual Studio 中使用 Reporting Services 的对象数据源

发布于 2024-09-27 07:35:18 字数 561 浏览 0 评论 0原文

我正在开发一个网站,我们希望在页面上包含饼图。现在我正在努力通过 Reporting Services(RDLC 文件)和对象数据源来实现这一点。

我的对象数据源的SelectMethod是一个返回业务对象列表的方法;假设有一个 List 并且 Alpha 有一个带有 Name 属性的子对象 Beta。在我的报告定义中,我将类别组设置为:=Fields!Beta.Value.Name,这意味着 Alpha.Beta.Name 是我的饼图切片。我收到以下错误:

  • 报告处理期间发生错误。分组“chart1_CategoryGroup1”的 Group 表达式包含错误:未设置对象变量或 With 块变量。

我能够确认这是因为 Beta 可为空,并且能够通过更新对象 Alpha 以在 Beta 属性为空时返回新的 Beta() 来解决问题。但这个解决方案并不理想,因为我的代码中还有其他地方,如果 Beta 还没有值,我需要将 Beta 设为 null。

有没有办法更新报表定义以接受空属性为有效?理想情况下,如果 Beta 为空,我希望将该值指定为“未设置”。

I'm working on a site where we want to include a pie chart on a page. Right now I'm working on implementing that through Reporting Services (RDLC file) with an object data source.

The SelectMethod of my object data source is a method that returns a list of business objects; lets say a List<Alpha> and Alpha has a sub object Beta with a Name property. In my report definition I have set the Category groups to be: =Fields!Beta.Value.Name this means that Alpha.Beta.Name are my pie slices. I got the following error:

  • An error has occurred during report processing. The Group expression for the grouping 'chart1_CategoryGroup1' contains an error: Object variable or With block variable not set.

I was able to confirm this is because Beta is nullable and was able to fix the issue by updating the object Alpha to return a new Beta() if the Beta property is null. This solution is not ideal though because there are other places in my code where I need Beta to be null if it doesn't have a value yet.

Is there a way to update the report definition to accept a null property as valid? Ideally I would like to specify the value as "Not Set" if Beta is null.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

等风也等你 2024-10-04 07:35:18

我遇到了与您类似的问题,我使用空对象重构解决了它(非常感谢 Martin Fowler 的书重构:))。
在这里你可以找到很好的例子空对象重构

所以你可以创建继承Beta的类NullableBeta,而属性Name例如 IsNullable 在 Beta 实体上是虚拟的。

 public class Beta
    {
        public virtual Name{ get; set;}
        public virtual IsSet{ get{return true;}}
    }
    
    public class NullableBeta:Beta
    {
       public override Name
           { 
             get{
                   return "Not Set";
                }
             set{}
          }
       public override IsSet{ get{ return false;}}
    }

现在,如果 Alfa 实体上未设置 Beta,您可以返回 NullableBeta 实体的实例。在报告中,您将使用“未设置”而不是空字符串,并且在将 Beta 实体设置为 Alfa 的地方,您可以检查 IsSet 属性,而不是检查 Beta 是否为空。

希望这有帮助

I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)).
Here you can find nice example Null object refactoring

So you could create class NullableBeta that inherits Beta, while properties Name and e.g. IsNullable are virtual on Beta entity.

 public class Beta
    {
        public virtual Name{ get; set;}
        public virtual IsSet{ get{return true;}}
    }
    
    public class NullableBeta:Beta
    {
       public override Name
           { 
             get{
                   return "Not Set";
                }
             set{}
          }
       public override IsSet{ get{ return false;}}
    }

Now, if Beta is not set on Alfa entity, you could return instance of NullableBeta entity. In reports, you would have "Not Set" instead of empty string, and on places where you are setting Beta entity to Alfa you can check IsSet property instead of checking if Beta is null.

Hope this was helpful

↘紸啶 2024-10-04 07:35:18

我遇到了和你类似的问题,我使用空对象重构解决了它(非常感谢 Martin Fowler 的书重构:))。在这里您可以找到空对象重构的好示例

我首先通过实现 空对象来处理 SSRS/RDLC 的缺点模式也是如此。
当涉及超过一两个域对象时,手动实现这一点当然会花费太多精力。

但是,由于我已经在使用 AutoMapper,@LucianBargaoanu 在评论中正确指出,AutoMapper 本身支持空对象作为选择加入功能,因此不需要显式实现。

因此,我使用 AutoMapper 及其 AllowNullDestinationValuesAllowNullCollectionsPreserveReferences()NullSubstituteForAllPropertyMaps() 功能,将我的所有域类映射到报告特定类,并将所有空引用替换为空对象(将域对象空引用映射到报告对象时)或合理的默认值(例如,空字符串替换空字符串或Nullable 的基础基元类型的默认值)。

下面是一些示例代码来演示该方法:

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);
    }
}

I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)). Here you can find nice example Null object refactoring

I first deal with this shortcomings of SSRS/RDLC by implementing the Null object pattern as well.
Implementing this manually is of course too much effort when more then one or two domain objects are involved.

However, since I am already using AutoMapper, @LucianBargaoanu correctly pointed out in the comments that null objects are natively supported as an opt-in feature by AutoMapper, so there is no explicit implementation needed.

I therefore use AutoMapper with its AllowNullDestinationValues, AllowNullCollections, PreserveReferences(), NullSubstitute and ForAllPropertyMaps() features, to map all my domain classes to report specific classes and substitute all null references to either null objects (when mapping domain object null references to report objects) or reasonable default values (e.g. an empty string for null strings or the default value of the underlying primitive type for Nullable<PrimitiveType>).

Here is some sample code to demonstrate the approach:

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文