DevExpress_XAF_根据集合(Collection)计算属性值(例子:产品列表统计订单明细)
事先声明,本文大部分内容根据官方文件得到,有些翻译不准确的还望见谅。
什么叫基于集合计算属性值?根据下图我们可以知道。在订单关系中,订单数量(Orders Count)、订单金额总计(Orders Total)、订单最大值 (Maximum Order),由下方列表(在业务类中用集合表示)经过统计获得。
初始化类的实现(没有统计功能时的源码)
订单(Order)和产品(Product)关系为多对多。
产品(Product)源码:
1 [DefaultClassOptions] 2 public class Product : BaseObject { 3 public Product(Session session) : base(session) { } 4 private string fName; 5 public string Name { 6 get { return fName; } 7 set { SetPropertyValue("Name", ref fName, value); } 8 } 9 [Association("Product-Orders"), Aggregated] 10 public XPCollection<Order> Orders { 11 get { return GetCollection<Order>("Orders"); } 12 } 13 }
订单源码:
1 [DefaultClassOptions] 2 public class Order : BaseObject { 3 public Order(Session session) : base(session) { } 4 private string fDescription; 5 public string Description { 6 get { return fDescription; } 7 set { SetPropertyValue("Description", ref fDescription, value); } 8 } 9 private decimal fTotal; 10 public decimal Total { 11 get { return fTotal; } 12 set { SetPropertyValue("Total", ref fTotal, value); } 13 } 14 private Product fProduct; 15 [Association("Product-Orders")] 16 public Product Product { 17 get { return fProduct; } 18 set { SetPropertyValue("Product", ref fProduct, value); } 19 } 20 }
计算属性
下面的代码片段演示了三个计算属性的实现——订单数量(Orders Count)、订单金额总计(Orders Total)、订单最大值 (Maximum Order)。
1 [DefaultClassOptions] 2 public class Product : BaseObject { 3 // ... 4 private int? fOrdersCount = null; 5 public int? OrdersCount { 6 get { 7 if(!IsLoading && !IsSaving && fOrdersCount == null) 8 UpdateOrdersCount(false); 9 return fOrdersCount; 10 } 11 } 12 private decimal? fOrdersTotal = null; 13 public decimal? OrdersTotal { 14 get { 15 if(!IsLoading && !IsSaving && fOrdersTotal == null) 16 UpdateOrdersTotal(false); 17 return fOrdersTotal; 18 } 19 } 20 private decimal? fMaximumOrder = null; 21 public decimal? MaximumOrder { 22 get { 23 if(!IsLoading && !IsSaving && fMaximumOrder == null) 24 UpdateMaximumOrder(false); 25 return fMaximumOrder; 26 } 27 } 28 }
以下方法是三个计算属性值具体方法。
1 [DefaultClassOptions] 2 public class Product : BaseObject { 3 // ... 4 public void UpdateOrdersCount(bool forceChangeEvents) { 5 int? oldOrdersCount = fOrdersCount; 6 fOrdersCount = Convert.ToInt32(Evaluate(CriteriaOperator.Parse("Orders.Count"))); 7 if (forceChangeEvents) 8 OnChanged("OrdersCount", oldOrdersCount, fOrdersCount); 9 } 10 public void UpdateOrdersTotal(bool forceChangeEvents) { 11 decimal? oldOrdersTotal = fOrdersTotal; 12 decimal tempTotal = 0m; 13 foreach (Order detail in Orders) 14 tempTotal += detail.Total; 15 fOrdersTotal = tempTotal; 16 if (forceChangeEvents) 17 OnChanged("OrdersTotal", oldOrdersTotal, fOrdersTotal); 18 } 19 public void UpdateMaximumOrder(bool forceChangeEvents) { 20 decimal? oldMaximumOrder = fMaximumOrder; 21 decimal tempMaximum = 0m; 22 foreach (Order detail in Orders) 23 if (detail.Total > tempMaximum) 24 tempMaximum = detail.Total; 25 fMaximumOrder = tempMaximum; 26 if (forceChangeEvents) 27 OnChanged("MaximumOrder", oldMaximumOrder, fMaximumOrder); 28 } 29 }
注意,fOrdersCount是在客户端使用UpdateOrdersCount方法中从内部XPO缓存加载的对象来计算的。可以使用下面的代码在服务器端计算fOrdersCount,这样就不会考虑未提交的对象。
1 fOrdersCount = Convert.ToInt32(Session.Evaluate<Product>(CriteriaOperator.Parse("Orders.Count"), 2 CriteriaOperator.Parse("Oid=?", Oid)));
在Order类的Total和Product属性设置器中,当Order对象的属性值发生变化且对象当前没有初始化时,UI将被更新:
1 [DefaultClassOptions] 2 public class Order : BaseObject { 3 // ... 4 private decimal fTotal; 5 public decimal Total { 6 get { return fTotal; } 7 set { 8 bool modified = SetPropertyValue("Total", ref fTotal, value); 9 if(!IsLoading && !IsSaving && Product != null && modified) { 10 Product.UpdateOrdersTotal(true); 11 Product.UpdateMaximumOrder(true); 12 } 13 } 14 } 15 private Product fProduct; 16 [Association("Product-Orders")] 17 public Product Product { 18 get { return fProduct; } 19 set { 20 Product oldProduct = fProduct; 21 bool modified = SetPropertyValue("Product", ref fProduct, value); 22 if(!IsLoading && !IsSaving && oldProduct != fProduct && modified) { 23 oldProduct = oldProduct ?? fProduct; 24 oldProduct.UpdateOrdersCount(true); 25 oldProduct.UpdateOrdersTotal(true); 26 oldProduct.UpdateMaximumOrder(true); 27 } 28 } 29 } 30 }
在Product类中,OnLoaded方法被覆盖,因为在使用“延迟”计算时需要重置缓存的值。
1 [DefaultClassOptions] 2 public class Product : BaseObject { 3 // ... 4 protected override void OnLoaded() { 5 Reset(); 6 base.OnLoaded(); 7 } 8 private void Reset() { 9 fOrdersCount = null; 10 fOrdersTotal = null; 11 fMaximumOrder = null; 12 } 13 // ...
将计算的值属性存储到数据库中
非持久性属性值时影响性能的。为了避免这个问题,可以将计算值存储到数据库中。可以使用PersistentAttribute将值保存到数据库中。
1 [DefaultClassOptions] 2 public class Product : BaseObject { 3 // ... 4 [Persistent("OrdersCount")] 5 private int? fOrdersCount = null; 6 [PersistentAlias("fOrdersCount")] 7 public int? OrdersCount { 8 // ... 9 } 10 [Persistent("OrdersTotal")] 11 private decimal? fOrdersTotal = null; 12 [PersistentAlias("fOrdersTotal")] 13 public decimal? OrdersTotal { 14 // ... 15 } 16 [Persistent("MaximumOrder")] 17 private decimal? fMaximumOrder = null; 18 [PersistentAlias("fMaximumOrder")] 19 public decimal? MaximumOrder { 20 // ... 21 } 22 // ...
从主订单类中移除OnLoaded方法重载。
参考文献
有志者,事竟成,破釜沉舟,百二秦关终属楚; 苦心人,天不负,卧薪尝胆,三千越甲可吞吴。