我有一个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 whereever。
但是在使用自动创建的服务引用代理类中的类作为数据绑定源时,我看不到如何执行此操作。
我当然可以为数据绑定创建本地业务对象,然后在需要持久保存数据时使用这些对象来填充服务引用对象,或者我可以在UI中进行计算(例如在OnChange事件[或类似]中)数量或价格),但如果有更好的方法,我宁愿不要。两者都导致代码重复。
在这种情况下处理计算属性的好方法是什么?
答案 0 :(得分:1)
代理模型类将生成为partial
类。因此,您可以创建部分模型类并添加计算属性。例如:
namespace ProductServiceClient.ServiceReference1
{
public partial class Product
{
public decimal SomeProperty
{
get
{
return this.Price * 10;
}
}
}
}
命名空间是您在“添加引用”对话框中设置的应用程序默认命名空间+服务引用命名空间。
Here是为那些希望以最小的努力重现和解决问题的人创建服务和服务客户端的一个很好的例子。
答案 1 :(得分:1)
您可以使用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);
}
}
接下来,为了&#34;添加&#34;现有类的属性,您需要一个实现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
列)
答案 2 :(得分:0)
这是使用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; } }
}