C# 具有服务引用的计算属性

C# 具有服务引用的计算属性,c#,winforms,asp.net-web-api,data-binding,odata,C#,Winforms,Asp.net Web Api,Data Binding,Odata,我有一个WinForms应用程序,它使用Web API(ODATA)服务器作为数据源。我正在将网格和表单绑定到服务引用的代理类 在某些UI字段中,我需要显示计算值。如果我正在编写一个“标准”WinForms应用程序(不使用服务引用及其代理类),我将绑定到从SQL填充自身的业务对象,并让这些业务对象公开计算出的属性,以便我可以从UI绑定到它们。例如: public class OrderLine { public string ItemNo { get; set; } (many

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

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

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; } }
}
namespace ProductServiceClient.ServiceReference1
{
    public partial class Product
    {
        public decimal SomeProperty
        {
            get
            {
                return this.Price * 10;
            }
        }
    }
}
现在,我可以在需要时将数据绑定到Total

但当使用自动创建的服务引用代理类中的类作为数据绑定源时,我看不到如何做到这一点

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


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

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

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; } }
}
namespace ProductServiceClient.ServiceReference1
{
    public partial class Product
    {
        public decimal SomeProperty
        {
            get
            {
                return this.Price * 10;
            }
        }
    }
}
名称空间是您在“添加引用”对话框中设置的应用程序默认名称空间+服务引用名称空间

是一个很好的示例,用于为那些希望以最小的努力再现和解决问题的人创建服务和服务客户端。

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

为了做到这一点,您需要一些助手类

首先,自定义计算属性的泛型类:

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(); }
}
通用部分就这些了。最后,您只需调用
CustomPropertyTypeDescriptor。在应用程序启动时为每个需要计算属性的类注册一次
,使用
CalculatedProperty.Create
方法提供这些属性

以下是一个例子:

型号:(注意没有
总计
属性)

应用程序:

结果:(注意
Total
列)


这是一个使用StructureTypes的纯ODataV4 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; } }
}
您可以使用StructureTypes修改模型并添加回只读属性。逻辑/计算属性将不存在于物理数据库中,但仍将显示在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());
然后添加属性以将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; } }
}

至少您可以使用部分模型类。但您所说的自动创建的服务引用代理类是什么意思?如何使用ASP.NET Web Api服务?或者您正在使用WCF服务?@Reza Aghei您可以在Visual Studio中为实现ODATA v3的Web API服务添加服务引用,就像您使用WCF一样。例如:@reza aghaei部分模型类听起来是个好主意!让我来试一试……@雷泽·阿盖伊:是的,这应该行得通。注意:您的答案中缺少“partial”关键字。@reze aghaei:即使此解决方案在客户端有效,它在服务器端也会引发一个异常:“属性SomeProperty在类型上不存在。请确保仅使用由类型定义的属性名。”我搜索了一个可用于不将添加的属性从分部类发送到服务器的属性或类似属性,但似乎没有此类属性。@reze aghaei:ODATA v4似乎有一个解决方案(开放类型):。我对必须向一堆类添加Dictionary属性并不感到兴奋,但至少它应该可以解决这个问题。我只需要看看我是否可以使用ODataV4(我目前使用的是v3)。但如果你能想出更好的办法,我洗耳恭听。谢谢。@Lars335动态属性未键入。它们是
对象
,您应该自己取消装箱。动态属性是这样定义的:
公共IDictionary属性{get;set;}
。似乎您无法定义计算的动态属性。@reze aghaei:我得到的印象是,非类型化字典用于包含在客户机上定义的、在实际模型中不存在的任何属性,但我想情况并非如此。所以它将不起作用(我还发现我无论如何都不能使用ODataV4)。似乎真的没有办法计算客户端属性,因此我似乎必须在UI事件处理程序中进行计算。不漂亮。。。
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());
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[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; } }
}