【问题标题】:Calculated properties with service reference具有服务参考的计算属性
【发布时间】:2016-10-03 16:58:14
【问题描述】:

我有一个 WinForms 应用程序,它使用 Web API (ODATA) 服务器作为其数据源。我将网格和表单绑定到服务引用的代理类。

在某些 UI 字段中,我需要显示计算值。如果我正在编写一个“标准”WinForms 应用程序(不使用服务引用及其代理类),我将绑定到从 SQL 填充自身的业务对象,并让这些业务对象公开计算的属性,以便我可以从用户界面。例如:

public class OrderLine
{
    public string ItemNo { get; set; }
    (many other properties here...)
    public int Quantity { get; set; }
    public decimal Price { get; set; }
    public decimal Total { get { return Quantity * Price; } }
}

我现在可以根据需要将数据绑定到 Total。

但是当使用自动创建的服务引用代理类中的类作为数据绑定源时,我看不到如何执行此操作。

我当然可以为数据绑定创建本地业务对象,然后在需要持久化数据时使用它们来填充服务引用对象,或者我可以在 UI 中进行计算(例如在 OnChange 事件 [或类似事件]对于数量或价格),但如果有更好的方法,我宁愿不这样做。两者都会导致代码重复。

在这种情况下,处理计算属性的好方法是什么?

【问题讨论】:

  • 至少可以使用部分模型类。
  • 但是自动创建的服务引用代理类是什么意思?您如何使用 ASP.NET Web Api 服务?还是您在使用 WCF 服务?
  • @Reza Aghei 您可以在 Visual Studio 中为实现 ODATA v3 的 Web API 服务添加服务引用,就像使用 WCF 一样。例如:localhost:63957/odata
  • @reza-aghaei 部分模型类听起来是个好主意!让我试一试……

标签: c# winforms asp.net-web-api data-binding odata


【解决方案1】:

代理模型类将生成为partial 类。因此,您可以创建部分模型类并添加计算属性。例如:

namespace ProductServiceClient.ServiceReference1
{
    public partial class Product
    {
        public decimal SomeProperty
        {
            get
            {
                return this.Price * 10;
            }
        }
    }
}

命名空间是您在添加引用对话框中设置的应用程序默认命名空间 + 服务引用命名空间。

Here 是一个很好的例子,可以为那些希望以最小的努力重现和解决问题的人创建服务和服务客户​​端。

【讨论】:

  • @reze-aghaei:是的,这应该可以。注意:您的答案中缺少“部分”关键字。
  • @reze-aghaei:即使此解决方案适用于客户端,它也会在服务器端引发异常:“类型上不存在 SomeProperty 属性确保仅使用符合以下条件的属性名称由类型定义。”我搜索了一个属性或类似的,可以用来不将添加的属性从分部类发送到服务器,但似乎没有这样的属性。
  • @reze-aghaei:ODATA v4 似乎有一个解决方案(开放类型):asp.net/web-api/overview/odata-support-in-aspnet-web-api/…。我对必须将 Dictionary 属性添加到一堆类并不感到兴奋,但至少它应该可以解决问题。我只需要确定是否可以使用 ODATA v4(我目前使用 v3)。但如果你能想到更好的事情,我会全力以赴。谢谢。
  • @Lars335 未键入动态属性。他们是object,你应该自己拆箱。动态属性以这种方式定义:public IDictionary<string, object> Properties { get; set; }。您似乎无法定义计算的动态属性。
  • @reze-aghaei:我的印象是,无类型字典用于包含在客户端上定义但实际模型中不存在的任何属性,但我想情况并非如此。所以它不起作用(而且我还发现我无论如何都不能使用 ODATA v4)。似乎真的没有办法计算客户端属性,所以看来我将不得不在 UI 事件处理程序中进行计算。不漂亮……
【解决方案2】:

您可以利用TypeDescriptor 服务来扩展具有计算属性的模型类。

为此,您需要一些辅助类。

首先,自定义计算属性的通用类:

public class CalculatedProperty<TComponent, TValue> : PropertyDescriptor
{
    private Func<TComponent, TValue> func;
    public CalculatedProperty(string name, Func<TComponent, TValue> func)
        : base(name, null)
    {
        this.func = func;
    }
    public override Type ComponentType { get { return typeof(TComponent); } }
    public override bool IsReadOnly { get { return true; } }
    public override Type PropertyType { get { return typeof(TValue); } }
    public override bool CanResetValue(object component) { return false; }
    public override object GetValue(object component) { return func((TComponent)component); }
    public override void SetValue(object component, object value) { throw new InvalidOperationException(); }
    public override bool ShouldSerializeValue(object component) { return false; }
    public override void ResetValue(object component) { throw new InvalidOperationException(); }
}

和一个工厂(使其更易于使用):

public static class CalculatedProperty
{
    public static PropertyDescriptor Create<TComponent, TValue>(string name, Func<TComponent, TValue> func)
    {
        return new CalculatedProperty<TComponent, TValue>(name, func);
    }
}

接下来,为了向现有类“添加”属性,您需要一个实现ICustomTypeDescriptor 接口并通过自定义TypeDescriptionProvider 公开它的类。这个过程有点复杂,所以我封装在下面的类中:

public class CustomPropertyTypeDescriptor : CustomTypeDescriptor
{
    public static void Register(Type type, params PropertyDescriptor[] customProperties)
    {
        var baseProvider = TypeDescriptor.GetProvider(type);
        var typeDescriptor = new CustomPropertyTypeDescriptor(baseProvider.GetTypeDescriptor(type), customProperties);
        TypeDescriptor.AddProvider(new Provider(baseProvider, typeDescriptor), type);
    }
    PropertyDescriptor[] customProperties;
    private CustomPropertyTypeDescriptor(ICustomTypeDescriptor baseDescriptor, PropertyDescriptor[] customProperties)
        : base(baseDescriptor)
    {
        this.customProperties = customProperties;
    }
    public override PropertyDescriptorCollection GetProperties() { return GetProperties(null); }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return new PropertyDescriptorCollection(base.GetProperties(attributes).Cast<PropertyDescriptor>().Concat(customProperties).ToArray());
    }
    private class Provider : TypeDescriptionProvider
    {
        private CustomPropertyTypeDescriptor typeDescriptor;
        public Provider(TypeDescriptionProvider baseProvider, CustomPropertyTypeDescriptor typeDescriptor)
            : base(baseProvider)
        {
            this.typeDescriptor = typeDescriptor;
        }
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            return typeDescriptor;
        }
    }
}

这就是通用部分的全部内容。最后,您只需在应用程序启动时为每个需要计算属性的类调用一次CustomPropertyTypeDescriptor.Register,并使用CalculatedProperty.Create 方法提供它们。

这是一个例子:

型号:(注意没有Total属性)

public class OrderLine
{
    public string ItemNo { get; set; }
    (many other properties here...)
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

应用:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        CustomPropertyTypeDescriptor.Register(typeof(OrderLine),
            CalculatedProperty.Create("Total", (OrderLine source) => source.Quantity * source.Price)
        );

        var form = new Form();
        var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
        dg.DataSource = Enumerable.Range(1, 10).Select(n => new OrderLine
        {
            ItemNo = "Item#" + n,
            Quantity = n,
            Price = 10 * n
        }).ToList();

        Application.Run(form);
    }
}

结果:(注意Total 列)

【讨论】:

    【解决方案3】:

    这是一个使用 StructuralTypes 的纯 OData v4 Web API。

    鉴于海报原来的POCO模型

    public class OrderLine
    {
        public string ItemNo { get; set; }
        (many other properties here...)
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public decimal Total { get { return Quantity * Price; } }
    }
    

    您可以使用 StructuralTypes 来修改模型并添加回只读属性。逻辑/计算属性不会存在于物理数据库中,但仍会显示在 OData 服务中。

    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    
    builder.EntitySet<OrderLine>("OrderLines");
    
    builder.StructuralTypes
        .First(t => t.ClrType == typeof(OrderLine))
        .AddProperty(typeof(OrderLine).GetProperty("Total"));
    
    config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
    

    如果您的源(现有)数据库与计划的 OData 架构的业务需求不匹配,则可以进一步执行此操作。首先将以下使用添加到您的 POCO 类中。

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    

    然后添加属性以将您的 POCO 类映射到数据库架构、表名和列名。

    [Table("Order_Line", Schema = "dbo")]
    public class OrderLine
    {
        [Key]
        [Column("ItemNo")]
        public string Id { get; set; }
        (many other properties here...)
    
        [Column("Qty")]
        public int Quantity { get; set; }
    
        [Column("Price")]
        public decimal Price { get; set; }
    
        public decimal Total { get { return Quantity * Price; } }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-19
      • 1970-01-01
      • 2016-07-28
      • 2014-09-12
      • 2020-04-20
      • 2020-10-25
      • 1970-01-01
      相关资源
      最近更新 更多