• 简单关系型结构的依赖运算模型


    简介:本文阐述了在数据型应用程序中,对各种运算的关系的分析,简化这些应用程序的开发以及提高性能的方法。

     概述

    在数据型的应用程序中,我们经常面对关系型的数据结构,即经典的表、字段和关系的结构。在这种关系型结构下,我们需要在某个字段或关系的数据发生改变后,作出相应的反应。

    这些反应可能是数据处理的,例如当单价发生改变后,需要对金额进行重算,金额=单价*数量。也有可能这种反应是界面上的,例如未录入物料时,单价和数量均不能录入(Enabled=false),但是,一旦录入后,界面作出反应,其单价和数量就可以录入了。我们称这种反应为计算单元。

    我们注意到,这些计算单元,依赖某个字段或关系的数据更改,例如计算金额的计算单元,依赖单价或数量字段更改,而计算单价和数量是否有效的计算单元,依赖物料字段。我们也可以理解为,计算单元依赖的数据未发生改变时,无需重新执行这些计算单元。

    我们理解这个“简单关系型结构的依赖运算模型”就是:在数据装载或发生改变后,智能的执行对应的计算单元的一套模型。

     数据结构

    既然我们在这个标题中已经限定了其是:关系型结构,那么一切的基础就是关系型的数据结构。在这个模型中,我们需要定义表、字段和关系。

    表包含很多的字段,这些字段都是简单的数据结构,不能再拆分。在当前模型中,我们只能做到有且只有一个根表,所有的表都是此根表的子表或间接子表,以后将取消此限制。

    关系描述了表与表之间的关系,一端是父表,一端是子表,在目前的模型中,我们只能支持固定的关联关系,那些动态的关联关系还未考虑。多对多的关系也还需要全面评估。

    在实际的应用中,这个数据结构最佳实践应该是能够支持动态的数据结构,例如在某些界面中,基于性能的考虑,可能希望在用户点击某个页签时,才加载某个关系,关于此问题我们在其他的文章再讨论。

    还需要注意的是,目前的应用程序,很多都是使用面向对象的数据结构,我们今天还使用关系型结构,是不是老套了。事实上,我一开始就是基于面向对象的数据结构设计此模型,但随着经验的增加,我发现,复杂的对象只会把事情越搞越复杂,而对象的结构最终都可以映射到关系型结构的模型上。因此,我将对象的结构映射到关系型结构上,从而使用关系型的结构处理对象的复杂情况。

    计算单元

    有了数据结构,我们就可以基于此定义计算单元,每个计算单元都包含一个批量执行的方法,在需要的时候,引擎负责调用此方法以便完成计算。

    为简化具体的计算单元编写,要求计算单元必须提供一个TargetTable属性,他指向一个表,引擎执行计算单元时,将永远提供此表的数据。

    假设在销售订单中,单据根表包含一个折扣字段,明细上包含金额和折后金额,当金额或折扣字段发生改变后,我们期望能够执行这个计算单元,以便更新折后金额。在传统的编程模型中,当折扣发生改变后,我们需要自己编码获取所有的明细,以便更新全部明细,而用户仅更改了某行明细的金额时,我们又需要仅计算此行的折后金额。

    TargetTable属性就是向引擎声明:不论更改的字段在哪里,请帮我智能的转换成此Table上的数据。这样编写计算单元的程序员就很简单了,循环计算就是了。在这个例子中,当折扣发生改变时,引擎将提供所有明细给计算单元,而某行的金额发生改变后,引擎将提供仅包含发生行一条记录的集合。

    在这里例子中,你也许注意到,此计算单元仅希望折扣或金额字段发生改变后才执行,其它字段改变就不要让我再执行了。我们称这种特性为依赖字段,此计算单元的DependencyFields为折扣和金额字段。

    但你需要注意的是,计算单元不仅可以依赖字段,还可以依赖关系,一个典型的场景是:Items.Count 公式的计算,依赖关系Items的变更,当Items关系有新增、删除或重置时,需要重算此公式。当然,为统一概念,我们认为关系也是一个字段,称为集合字段,就像面向对象中一个集合属性一样。

    与DependencyFields相对应的,当计算单元计算完毕后,可能会影响某些字段和关系,在这个例子中,此计算单元完成计算后将更新“折后金额”字段,引擎需要这样的信息,以便将依赖“折后金额”字段的计算单元放在此计算单元之后。我们称此特性为ApplyToFields。

    与阐述DependencyFields一样,ApplyToFields也可以更改影响关系。

    工作区及引擎

    所有的数据结构定义及计算单元都被存储在一起,即一个工作区,引擎是工作区平行的部分,他负责调度这些计算单元。

    在一个工作区中,会包含很多的计算单元,此模型的引擎在某些字段或关系数据发生改变后,分析哪些计算单元需要执行、执行的先后顺序,以及避免在批量处理时的重复计算。

    引擎在初始化时,分析每个计算单元的DependencyFields属性,并建立数据结构以便在某个字段(或关系)发生更改后快速的找到其对应的计算单元。另外,还分析计算单元的ApplyToFields属性,建立数据结构以便快速的决定计算单元的执行顺序。

    由于各种实体的更改通知机制不同,所以引擎本身并不包含对实体更改的追踪部分,而是让外界手动的通知引擎。可以建立一个适配器跟踪实体的更改,然后通知引擎。

    更改通知包括字段的DataChanged和关系的CollectionChanged俩种,字段DataChanged比较容易理解。关系的CollectonChanged包括三种更改方式:插入、删除和重置。

    引擎在收到更改通知后,将启动一个事物(事物处理由外界管理),以便能够在后续的计算发生错误后回滚现场。

    根据此通知信息找到所有依赖更改字段的计算单元。找到的计算单元可能有重复,可以很容易的先去除重复的部分。然后,引擎再根据之前的数据结构很容易再推算出执行的先后顺序。

    由于计算单元有TargetTable属性,所以引擎一种重要的工作就是分析出对应的目标数据。更改的字段所在表称为EventSouceTable,由于EventSourceTable与TargetTable存在某种联系,因此,可以通过EventSourceTable的数据推导出TargetTable的数据,这种运算很像执行一个SQL查询,例如:

    Select TargetField1 From TagetTable Left Join EventSourceTable On XX=YY Where EventField = '01'

    此算法有些复杂,我们将在另外的文章讨论此算法。

    在CollectionChanged中,有一个叫“插入”的事件类型,这个过程很像我们的单据的新增或者新增某行明细,我们通常会在此过程中初始化一些字段的默认值。

    当我们获取了需要执行的计算单元、先后顺序,以及各个计算单元对应的数据,我们就可以调用它们了。最后提交事务。

    在执行计算单元时,某些计算单元不会影响其它的字段,即AppToFields为空,这种计算单元都可以最后执行,甚至异步执行。例如界面样式的计算、校验等。

    另外一种特殊的通知是装载初始化通知,他发生在初始化数据或切换数据阶段,你可以理解为执行所有的计算单元,但事实上,计算单元还有一个属性指示是否在初始化时执行,大多数的计算单元此属性为false,因为当重新加载一个单据时,你并不希望重新再计算一遍折后金额,因为它们已经计算过了。但是那一定希望重新计算某个控件的Enabled值,因为数据已经切换了,锁定性也需要重新计算了。

    引擎另外一个非常重要的特性是批量处理,Excel导入、剪贴板的粘贴、单据转换、邮件导入都是批量处理的典型应用。我认为,我们应该将所有的输入统一在一个模型上,看起来是一个“神键手”快速的录入数据,这样可以大大减少开发的工作量。

    批量处理的重要点在于排除重复的执行,这可以通过分析最终的计算单元和对应的目标数据来完成。另外一个重点是,需要考虑数据录入的先后顺序,例如在销售订单中,必须先录入客户,再录入商品,因为商品要根据客户来决定他的销售策略。

    此模型是一个复杂的项目,本文仅阐述其基本的组成及执行原理,更多的知识我们在后续的文章中介绍。

  • 相关阅读:
    3.学习Dispatcher
    2学习Application
    学习WPF-1
    Content-Type说明
    AspNet Core定时任务
    Asp.Net Core跨域配置
    学习Emmet
    Asp.Net Core存储Cookie不成功
    服务端编码和解码
    C#7特性
  • 原文地址:https://www.cnblogs.com/tansm/p/2237193.html
Copyright © 2020-2023  润新知