在《信息系统开发平台OpenExpressApp - 性能相关》中提到:
CSLA目前通用做法是整个对象在网络上传输,客户端更新时不管是更新了多少内容,它会把整个对象返回到服务器端。如果这个对象有成百上千条记录,而只更新了一两条记录,那么回传整个对象无疑是一个很大的浪费。在业务逻辑不需要整个对象的情况下,我们可以做差异更新,只回传增加、更新或删除的数据到服务器端。
OpenExpressApp现在支持差异更新保存命令,单据类对象的保存默认都是按照差异更新来处理。下面简单介绍一下如何结合OpenExpressApp来修改CSLA框架实现此功能。
CSLA自带一个差异更新的示例Csla.DiffGram,但是它的例子需要每个类库再继承写一个支持获取差异数据和保存以及回传的类,这对框架来说无疑是很不合适的,我不希望差异保存功能这样侵蚀以前写的类,也不想再去重新实现匹配的类,所以我想是否能够按照一种统一的方式,由外部调用来完成此功能,最好直接调用一个Command解决,如下代码所示:
[Command(CommandNames.Save_Bill, Label = "保存", ToolbarType = ToolbarType.Main, ModuleType = ModuleType.Bill, ToolTip = "保存记录")]
public class SaveBillCommand : WPFViewCommand
{
public override void Execute(ObjectView view)
{
DiffSaveCommand.Execute(view.CurrentObject as BusinessBase);
}
}
如果通过DiffSaveCommand实现差异更新,那么必须把差异的内容作为一个对象传送回服务器端,而又不想重新写类,最好重用以前类的保存逻辑,现在是把保存的对象clone一个出来,然后删除这个对象中未更改的子对象,这样传送回服务器端的既是一个只保留更新记录的原始对象类,也自然可以调用原始对象的Save方法了。
[Serializable]
public class DiffSaveCommand: CommandBase
{
public static void Execute(BusinessBase businessBase)
{
DiffSaveCommand cmd = new DiffSaveCommand(businessBase);
cmd = DataPortal.Execute<DiffSaveCommand>(cmd);MakeOldRecurChildObject(businessBase); //记住:调用成功后,把原始对象状态设置为Old
}//设置原始对象状态
private static void MakeOldRecurChildObject(BusinessBase businessBase)
{
businessBase.MarkOld();
BusinessObjectInfo boInfo = ApplicationModel.GetBusinessObjectInfo(businessBase.GetType());foreach (var item in boInfo.BOsPropertyInfos)
{
//如果是懒加载属性,并且没有加载数据时,不需要遍历此属性值
if (!businessBase.FieldExists(item.Name)) continue;
IList childs = businessBase.GetPropertyValue(item.Name) as IList;
((childs as IEditableCollection).GetDeletedList() as IList).Clear();
for (int i = childs.Count - 1; i >= 0; i--)
{
BusinessBase child = childs[i] as BusinessBase;
if (child.IsDirty) MakeOldRecurChildObject(child);
}
}
}private BusinessBase diffBo;
private DiffSaveCommand(BusinessBase businessBase)
{
diffBo = (businessBase as ICloneable).Clone() as BusinessBase;
//抱着传送对象只包含更新过的对象
DeleteNotDirtyData(diffBo);
}protected override void DataPortal_Execute()
{
(diffBo as ISavable).Save();
}//删除没有更改的子对象
private void DeleteNotDirtyData(BusinessBase businessBase)
{
BusinessObjectInfo boInfo = ApplicationModel.GetBusinessObjectInfo(businessBase.GetType());//删除没有变化的子对象
foreach (var item in boInfo.BOsPropertyInfos)
{
//如果是懒加载属性,并且没有加载数据时,不需要遍历此属性值
if (!businessBase.FieldExists(item.Name)) continue;
IList childs = businessBase.GetPropertyValue(item.Name) as IList;
for (int i = childs.Count - 1; i >= 0; i--)
{
BusinessBase child = childs[i] as BusinessBase;
if (!child.IsDirty)
{
(childs as IEditableCollection).RemoveChild(child);
((childs as IEditableCollection).GetDeletedList() as IList).Remove(child);
}
else
DeleteNotDirtyData(child as BusinessBase);
}}
}
}
从上面Command实现可以看出,我给BusinessBase类添加了一个FieldExists方法,这个方法用来判断懒加载属性是否已经加载了对象,如果没有加载的话,上面Command删除非更新子对象时就不用去遍历这个懒加载属性了,否则会触发一次客户端到服务器的请求。
/// 是否存在属性值,懒加载还没加载时不存在
public bool FieldExists(string propertyName)
{
PropertyInfoList list = PropertyInfoManager.GetRegisteredProperties(this.GetType());
foreach(var item in list)
{
if (item.Name == propertyName)
return FieldManager.FieldExists(item);
}
return false;
}
在《信息系统开发平台OpenExpressApp - 性能相关》也提到,CSLA目前对象保存时会从服务器端传回一个对象,如果更新后回传的对象和本地的完全一样,则我们可以不必要回传这个对象。现在通过以上差异更新时,回传回来的只是差异更新的对象,而对于不需要回传保存后数据库信息的仍旧可以使用以前保存的那个对象,所以只需要在Command保存后对原始对象设置Old状态即可,这样也不需要重新刷新对象了,保存后界面也保持保存前一致。
public static void Execute(BusinessBase businessBase)
{
DiffSaveCommand cmd = new DiffSaveCommand(businessBase);
cmd = DataPortal.Execute<DiffSaveCommand>(cmd);MakeOldRecurChildObject(businessBase); //记住:调用成功后,把原始对象状态设置为Old
}