那么为什么 ObjectContext.SaveChanges 方法没有这样的选项呢?
我认为最简单的答案是因为 Linq-to-Sql、Entity Framework 和 WCF 数据服务都是由不同的团队实现的,这些团队之间的内部沟通并没有像我们希望的那样工作。我在以前的一个答案中描述了新 API 中缺少的一些有趣功能,但我不认为这是一个缺少的功能 - 我将在答案的第二部分中解释它。
WCF 数据服务有更多有趣的特性,它们也应该是实体框架的一部分。例如:
- 更改和查询拦截器
- 批处理多个查询和 SaveChanges 操作以对服务器进行一次调用
- 异步操作- 这将以 async/await 实现的形式出现在 EF6
是否存在任何模仿 Linq To SQL 行为的更新模式?
有一种模式可以解决这个问题,但你可能不会喜欢它。EFSaveChanges
作为工作单元工作。它要么保存所有更改,要么不保存。如果您的保存操作可能会导致仅保留部分更改而不是单个SaveChanges
调用来处理它。每个原子更改集都应该有自己的SaveChanges
调用:
using (var scope = new TransactionScope(...)) {
foreach (var entity in someEntitiesToModify) {
try {
context.SomeEntities.Attach(entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
context.SaveChanges();
catch (OptimisticConcurrencyException e) {
// Do something here
context.Refresh(e.StateEntries[0].Entity, RefreshMode.ClientWins);
context.SaveChanges();
}
}
scope.Complete();
}
我认为此功能不存在的原因是因为它不是通用的,并且如上所述它违反了工作单元模式。假设这个例子:
- 你加载一个实体
- 您将新的依赖实体添加到加载实体的导航属性
- 你在加载的实体上改变了一些东西
- 与此同时,其他人同时删除了您加载的实体
- 您可以通过轻松解决冲突来触发 SaveChanges
- EF 将尝试保存对主体实体的更改,但由于数据库中没有要更新的实体而发生冲突
- EF 将继续,因为冲突解决已放宽
- EF 将尝试插入依赖实体,但它会触发
SqlException
,因为数据库中不存在主体实体。此异常将破坏持久性操作,您将不知道为什么它抱怨引用完整性,因为您有一个主体实体。(由于上下文的内部状态不一致,此插入甚至可能不会发生,并且 EF 会触发另一个异常,但这取决于 EF 的内部实现)。
这立即使冲突解决的整体放松变得更加复杂。恕我直言,有三种解决方法:
- 根本不支持。如果您需要基于每个实体解决冲突,您仍然可以使用我上面展示的示例,但对于复杂的场景,它可能不起作用,因为复杂的场景很难解决。
- 每次发生冲突时重建数据库更改集——这意味着探索剩余的更改集,并从处理的持久性中排除与冲突实体及其关系等相关的所有实体。有一个问题:EF 无法从处理中排除任何已更改的实体。那会破坏工作单元的意义,我再重复一遍:放松冲突解决也会破坏工作单元的意义。
- 即使主体实体发生冲突,也让 EF 继续处理依赖关系。这需要处理数据库异常并了解其内容,以了解异常是由于主体冲突还是由于其他错误(应该立即使整个持久性操作失败)而触发的。在代码级别上理解数据库异常可能非常困难,而且它是针对每个受支持的数据库的提供程序特定的。
这并不意味着可能无法实现这样的功能,但它需要涵盖所有涉及关系的场景,这可能非常复杂。我不确定 Linq-to-Sql 是否可以处理这个问题。
您可以随时对Data UserVoice提出建议或查看代码并尝试自己实现。也许我觉得这个功能太复杂了,很容易实现。