XAF中XPO与EFCore的探讨

  • XAF中XPO与EFCore的探讨已关闭评论
  • 152 次浏览
  • A+
所属分类:.NET技术
摘要

首先抛出一个问题,在XAF项目中,我们现在可不可以选择EFCore?每个人可能都有自己的答案,这也没有什么标准答案。下面是我的个人看法,在刚接触XAF时,如何选择ORM,我也是犹豫了许久,最终选择了XPO,主要基于以下几点考虑


前言

首先抛出一个问题,在XAF项目中,我们现在可不可以选择EFCore?每个人可能都有自己的答案,这也没有什么标准答案。下面是我的个人看法,在刚接触XAF时,如何选择ORM,我也是犹豫了许久,最终选择了XPO,主要基于以下几点考虑

1.XPO是DEV的产品,支持力度及倾向性要比EFCore高
2.XPO是XAF最开始支持的ORM,XAF中的各个模块对XPO的支持更好(有个别模块不支持EFCore)
3.在XAF的社区中,关于XPO的各个方面的问题都有相应的解答,相对来说比EFCore更有优势

我想应该也有部分小伙伴可能与我的考虑是一致的,但为啥要抛出这个问题呢,是因为XAF的一篇文章(https://docs.devexpress.com/eXpressAppFramework/404186/why-we-recommend-ef-core-over-xpo)

DEV建议在新的XAF项目中优先采用EFCore,文章中对比了XPO与EFCore,总体来说它们各有特点,也有很多相似之处。DEV建议优先选择EFCore的原因可能与XPO一直处于维护状态有关,此外,EFCore的发展势头也越来越强劲,在XAF中使用EFCore已成为大势所趋。但是将XPO迁移到EFCore并不容易,由于XAF最初的ORM是XPO,因此某些功能是基于XPO或倾向于XPO,所以在EFCore上的效果可能会有所不同。

探讨

虽然XPO和EFCore在语法上有许多不同,但在思想上基本一致。大部分XPO中的特性都被EFCore支持,但是有一个EFCore没有的特性,即NestedUnitOfWork,在EFCore中对应NestedDbContext,但EFCore没有。XAF利用XPO的这个特性构建了XPNestedObjectSpace,使父子表(或主从表)之间的操作更加自然和灵活。

由于EFCore没有这个特性,因此使用起来与XPO的操作不一致,甚至会给人一种不可接受的感觉。下面通过刨析它们内部的原理,来讲解它们行为不一致的原因,同时不可接受的地方,通过了解后是否可以接受它。

首先讲一下,在XPO中习以为常的功能,在EFCore中却会出现不一样的行为。这主要出现在父子表中,或者说聚合实体中,这里假设一个场景,用户表(User)中包含一个用户标记(UserTag)列表(Tags),它是聚合的(Aggregated),这是一个一对多的关系。当我们新建User后,再向Tags列表中添加UserTag时,在EFCore下,会自动保存User,而在XPO中不会有这样的操作,这样会造成一个问题,如果此时,我想放弃当前新建的User,我把User关闭后,列表中会多一条刚才放弃的User,这种现象在XPO中是没有的,为什么会有这样不一致的行为呢。这就是前面提到的XPNestedObjectSpace,而EFCore没有这样的一个实现。

这里要引出XPO中的Session,它与EFCore的DbContext是对应的(确切的说与UnitOfWork更像),Session有多个子类如UnitOfWork,NestedUnitOfWork,ExplicitUnitOfWork,通过名称我们也可以了解它们的用途,我们平时在创建BO类时,都要有一个包含Session参数的构造函数,这里的Session一般都是它的子类UnitOfWork,Session中的事务比较简单,我们在调用BO对象中的Save方法时,就会直接提交到数据库。UnitOfWork又多了一层,调用BO对象中的Save方法时,并不会立即提交到数据库,我们需要调用UnitOfWork中的CommitChanges方法,它会将工作单元中的BO对象一并提交到数据库,而ExplicitUnitOfWork会启用一个显式事务(也称作数据库级事务),我们通过BeginTransaction方法开启事务,通过CommitTransaction方法提交事务,在没有提交之前,你可以多次调用CommitChanges方法,如果其中一个提交出现异常或其它原因放弃提交,你可以通过RollbackTransaction方法回滚事务,这样就可以将之前的所有提交都进行回滚。

NestedUnitOfWork是我们需要重点关注的,它就是嵌套工作单元,你也可以把它看作是UnitOfWork的子工作单元,它有一个好处就是,它可以直接访问到父级中的对象也就UnitOfWork中的对象,同时它提交时,并不会直接提交到数据库,而是等待父级的UnitOfWork一起提交,它们的提交是放在同一事务中的,如果NestedUnitOfWork不提交,而父级UnitOfWork进行了提交,此时提交的内容不会包含NestedUnitOfWork中的对象。

以上NestedUnitOfWork的特点带来许多好处,如可以使父子表间的编辑变的简单,父表提交时,可以一起提交子表,父表放弃后,子表也会放弃,子表也可以随时单独放弃等等,当与XAF的主从视图结合时,就会有很不错的操作体验。

如果没有NestedUnitOfWork,将会发生什么呢,首先两个UnitOfWork是不能直接通信的,其中一个UnitOfWork创建的对象,如果另一个UnitOfWork想访问,第一个UnitOfWork需要先提交到数据库,第二个UnitOfWork再从数据库中读取,而NestedUnitOfWork可以直接通过GetObject方法访问到父级UnitOfWork中的对象,这样就减少了一次提交与读取的过程。

再回到前面创建User的示例,由于UserTag与User是聚合关系,也就是UserTag只属于一个User,同时UserTag中的User外键还不能为空,也就是说UserTag脱离User,它本身就没有意义,相当于订单与订单明细的关系。在这种情况下,我们在User中新增UserTag时,UserTag首先需要一个User,而如果此时User也是新建的,并且它们分别在不同的UnitOfWork中时(在XAF的主从视图中,如果新建从表数据,XAF会单独创建一个ObjectSpace,也就是当前User与UserTag会在不同的ObjectSpace中),我们能想到的就是保存User,在UserTag所在的UnitOfWork中从数据库中读取User。

看到这里的小伙伴应该明白了,XAF为什么会在新增UserTag时需要保存User了吧。我们刚才谈到的是聚合关系,如果不是聚合的一对多关系是否会自动保存父级呢,不会的,因为不是聚合,那它的外键可以为空,也就是UserTag的User外键可以为空,这样在新建时,可以不必事先获取父级,那也就没必要保存父级了。

前面讲的都是新建的User,如果是已存在的User,也就是从列表中打开的User,如果在User中新增UserTag会不会自动保存呢,也不会,因为User已存在于数据库中,在UserTag的UnitOfWork中就可以从数据库中获取到User,这样也没有必要保存User。如果仔细观察,在没有修改User的情况下,新增UserTag,保存按钮还是处于禁用状态,这是由于它们处在不同的UnitOfWork中互不干扰。

在EFCore中,将上面的UnitOfWork替换为DbContext,也就知道了为什么EFCore会出现与XPO不一致的行为了,这里的关键点就是NestedUnitOfWork,而EFCore没有对应的实现,那是不是EFCore就不是一个好的选择呢,我倒觉得不是,通过上面的示例,我们可以看到,除了在新建具有聚合关系的对象时,会出现与XPO不一致的行为,其它的地方与XPO基本保持一致。

随手创建,随手放弃,可能是我们日常测试时经常操作的,但是在上线运行时,日常操作会有相关的约束,不会频繁出现这样的行为,再加上XAF自动保存时也会进行数据校验,校验不通过也是无法进行自动保存的,同时由于Blazor自身的特点,对于一些需要长时间录入的页面,我们还需要自己加入自动保存的功能,已防止录入数据的丢失。

我曾经试图去解决这个问题,为EFCore创建一个EFCoreNestedObjectSpace,EFCoreNestedObjectSpace直接采用父级的DbContext,这样可以实现在一个事务中提交,但最后发现解决一个问题,引出一堆问题,最后还是放弃了,在底层没有支持的情况下,是无法实现与XPO相似效果的。

在了解了上面的内容后,我们发现,XAF中使用EFCore,与XPO相比出现的不一致行为,也不这么神秘了。但这不是全部,EFCore与XPO还是有很多不同的,如果习惯了XPO,你会发现在XPO中有许多默认行为,在EFCore中需要手动的配置,比如延迟删除、乐观并发等,这些需要注意。

关于XPO中的延迟删除(DeferredDeletionAttribute),有些小伙伴会把它当成软删除(SoftDelete),这是不正确的,它与软删除还是不一样的,虽然延迟删除不会删除记录,但它会清除记录中的外键,它的存在是为了解决XPO删除记录时,由于数据库的外键约束造成的失败,但本质还是删除,只是保留了记录,事后需要自己清理。当你试图恢复延迟删除的记录,你会发现记录与其它记录的关系没有了,这也是为什么不能用作软删除的原因。EFCore中实现软删除比较简单,使用HasQueryFilter配置实体就能实现软删除的功能,事后我会在XAF中实现一个EFCore版的软删除分享给大家。

最后

随着XAF中使用EFCore的深入,可能还会发现与XPO的不同,或出现一些无法解决的问题,我都会更新到文章中。当我完全切换到EFCore时,可能今后就要远离XPO了,有些不舍,但总要去面对,新技术总会取代旧的技术。

https://www.cnblogs.com/haoxj/p/17416380.html