前言

首先抛出一个问题,在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

XAF中XPO与EFCore的探讨的更多相关文章

  1. How to: Use XPO Upcasting in XAF 如何:在 XAF 中使用 XPO 强制转换

    In this topic, you will learn how to use the Upcasting feature of XPO in XAF. It is useful when you ...

  2. WPF中两条路径渐变的探讨

    原文:WPF中两条路径渐变的探讨 我们在WPF中,偶尔也会涉及到两条路径作一些“路径渐变 ”.先看看比较简单的情形:如下图(关键点用红色圆点加以标识):(图1) 上面图1中的第1幅图可以说是最简单的路 ...

  3. 关于XAF中ListView慢的总结与改善

    关于XAF中ListView慢的总结与改善: 一.数据访问模式改善:ListView中DataAccessMode中的改变:默认模式是Client,这在大多数情况下,适当的使用Server,Serve ...

  4. XAF中多对多关系 (XPO)

    In this lesson, you will learn how to set relationships between business objects. For this purpose, ...

  5. [个人原创]关于java中对象排序的一些探讨(一)

    有的时候我们需要将自己定义的对象,有序输出.因为一般我们程序的中间结果需要存储在容器里,那么怎样对容器中的对象按照一定次序输出就是程序员经常需要考虑的问题.本片文章探讨了怎样有序化输出容器中的对象的问 ...

  6. How to: Use the Entity Framework Code First in XAF 如何:在 XAF 中使用EF CodeFirst

    This topic demonstrates how to create a simple XAF application with a business model in a DbContext ...

  7. How to: Use the Entity Framework Model First in XAF 如何:在 XAF 中使用EF ModelFirst

    This topic demonstrates how to use the Model First entity model and a DbContext entity container in ...

  8. MYSQL中的where ‘1=1‘ 探讨

    在学习MySQL时候,关于MySQL注入的例子 首先针对以下代码,实现的是关于sql注入时,一个普通登录所产生的的问题 package com.java.lesson02; import com.ja ...

  9. 【JavaScript】javascript中伪协议(javascript:)使用探讨

    javascript:这个特殊的协议类型声明了URL的主体是任意的javascript代码,它由javascript的解释器运行. 比如下面这个死链接: <a href="javasc ...

  10. XAF进修二:在XAF中打开自定义的WinForm

    在建造WinForm时须要加上一机关函数和Show办法 using System; using System.Collections.Generic; using System.ComponentMo ...

随机推荐

  1. 同步协程的必备工具: WaitGroup

    1. 简介 本文将介绍 Go 语言中的 WaitGroup 并发原语,包括 WaitGroup 的基本使用方法.实现原理.使用注意事项以及常见的使用方式.能够更好地理解和应用 WaitGroup 来协 ...

  2. 2020寒假学习笔记12------Python基础语法学习(一)

    代码的组织和缩进 Python 语言直接通过缩进来组织 代码块."缩进"成为了 Python 语法强制的规定.缩进时,几个空格都是允许的,但是数目必须统一.我们通常采用" ...

  3. mac tip---->开发的tip

    delete webstorm Besides we delete the Webstorm App, We also need to delete related config or log dir ...

  4. 一条SQL语句在MySQL中如何执行

    一条SQL语句在MySQL中如何执行 本篇文章会分析一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的. 在分析之前我会 ...

  5. blender资源库 【自用】

    1 https://www.threedscans.com A Website with a lot of photo-scanned sculptures which are free to use ...

  6. 电商AARRR模型分析(一)——R语言

    在2010年,互联网创业者增长黑客之父肖恩·埃利斯(Sean Ellis)就创造了增长黑客(Growth hacker)这样一个概念.2015年,范冰撰写的一本新书<增长黑客>确立了Gro ...

  7. jinjia2基本用法

    前言这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题 于是乎,我自己开发了一款面试宝典,希望 ...

  8. [数据库/MySQL]数据类型:enum 枚举类型

    1 需求描述 场景 性别(gender) :男 / 女 / 保密 2 基本语法 enum(枚举值 1,枚举值 2...); 枚举值列表在 255 个以内,使用 1 个字节来存储 枚举值列表超过 255 ...

  9. Python简易学生管理系统

    目录结构: 1. 学生文件 student.py # 学生类 class Student(object): # 存放学生信息 student_info = {} # 学生初始化方法 def __ini ...

  10. python从shp文件中读取经纬度数据

    python从shp文件中读取经纬度数据 没有接触过GIS的人来说shp文件很陌生而且很难打开查看,好在python可以从中提取出自己想要的数据 pyshp库的安装 python的pyshp库可以实现 ...