Repository EF模式中的Update函数的作用是什么



我正在使用EF中的存储库模式,使用我在网上找到的Update函数

public class Repository<T> : IRepository<T> where T : class
{
public virtual void Update(T entity)
{
var entry = this.context.Entry(entity);
this.dbset.Attach(entity);
entry.State = System.Data.Entity.EntityState.Modified;
}
}

然后我在DeviceService中使用它,就像这样:

public void UpdateDevice(Device device)
{
this.serviceCollection.Update(device);
this.uow.Save();
}

我已经意识到,这实际上是在更新设备的所有信息,而不仅仅是更新更改的属性。这意味着在多线程环境中,更改可能会丢失。

测试后,我意识到我可以更改Device,然后调用uow.Save(),这既保存了数据,又不会覆盖任何现有的更改。

所以我的问题实际上是——Update()函数的意义是什么?它几乎出现在我在网上找到的所有存储库模式中,但它似乎具有破坏性。

我通常不会将这种通用的Update方法称为"破坏性的",但我同意它的用例有限,在这些存储库实现中很少讨论。该方法是否有用取决于您想要应用它的场景。

在"附加场景"(例如Windows窗体应用程序)中,从数据库加载实体,在实体仍附加到EF上下文时更改某些属性,然后保存更改,该方法是无用的,因为上下文无论如何都会跟踪所有更改,并在最后知道哪些列必须更新或不更新。在这种情况下,您根本不需要Update方法(提示:DbSet<T>(它是一个通用存储库)因此没有Update方法)。在并发情况下,它是具有破坏性的,是的。

然而,目前还不清楚"跟踪变化的更新"有时也不会具有破坏性。如果两个用户将同一属性更改为不同的值,则两个用户的更改跟踪更新将保存新列值,最后一个用户获胜。这是否可以取决于应用程序以及它希望进行更改的安全性。如果应用程序不允许在保存更改之前编辑不是数据库中最后一个版本的对象,则不能允许最后一次保存获胜。它必须停止,强制用户重新加载最新版本,并在输入更改之前查看最后的值。为了处理这种情况,并发令牌是必要的,它可以检测到其他人在此期间更改了记录。但是,这些并发检查与跟踪更改的更新或将实体状态设置为Modified时的工作方式相同。并发异常阻止了这两种方法的潜在破坏性。但是,将状态设置为Modified仍然会产生不必要的开销,因为它会将未更改的列值写入数据库。

在"分离场景"(例如Web应用程序)中,更改跟踪更新不可用。如果不想将整个实体设置为Modified,则必须从数据库(在新上下文中)加载最新版本,复制UI中的属性,然后再次保存更改。然而,这并不能防止另一个用户在此期间所做的更改被覆盖,即使这些更改是对不同属性的更改。假设两个用户同时将同一客户实体加载到web表单中。用户1编辑客户名称并保存。用户2编辑客户的银行账号并在几秒钟后保存。如果实体被加载到新上下文中以执行用户2的更新,EF将只看到数据库中的客户名称(已经包括用户1的更改)与用户2发送回的客户名称不同(仍然是旧客户名称)。如果复制客户名称值,则属性将标记为Modified,旧名称将写入数据库并覆盖User 1的更改。此更新将与将整个实体状态设置为"已修改"一样具有破坏性。为了避免这个问题,您必须在客户端实现一些自定义更改跟踪,以识别用户2是否更改了客户名称,如果没有,则不将值复制到加载的实体。或者您将不得不再次使用并发令牌。

您在问题中没有提到这个Update方法的最大限制,即它不更新任何相关实体。例如,如果您的Device实体有一个相关的Parts集合,并且您将在分离的UI中编辑此集合(添加/删除/修改项),则将父Device的状态设置为Modified不会将这些更改保存到数据库中。它只会影响父Device本身的标量(和复数)属性。当我使用这种repos时,我将更新方法命名为FlatUpdate,以在方法名称中更好地表明这种限制。我从未见过通用的"DeepUpdate"。处理复杂的对象图总是一件非通用的事情,必须根据具体情况按实体类型单独编写。(幸运的是,像GraphDiff这样的库可以限制此类图形更新所需的代码量。)

长话短说:

  • 对于附加的场景,Update方法是多余的,因为EF的自动更改跟踪会完成所有必要的工作,将正确的UPDATE语句写入数据库,包括相关对象图中的更改
  • 对于分离的场景,这是一种在没有关系的情况下执行简单实体更新的舒适方式
  • 在分离的场景中更新具有父实体和子实体的对象图不能用这样一个简化的Update方法来完成,并且需要更多的(非通用的)工作
  • 安全并发控制需要更复杂的工具,比如启用EF提供的乐观并发检查,并以用户友好的方式处理由此产生的并发异常

在Slauma非常深刻和实用的回答之后,我想深入了解一些基本原则。

在MSDN的这篇文章中,有一个重要的句子

存储库将业务逻辑与底层数据源或Web服务的交互分离开来。

简单的问题。业务逻辑与Update有什么关系?

Fowler将存储库模式定义为

使用类似集合的接口在域和数据映射层之间进行中介,用于访问域对象。

就业务逻辑而言,存储库只是一个集合。集合语义是关于添加和删除对象,或者检查对象是否存在。主要操作为AddRemoveContains。查看ICollection<T>接口:没有Update方法。

对象是否应该标记为"已修改"并不是业务逻辑关心的问题。它只是修改对象,并依赖于其他层来检测和保持更改。暴露Update方法

  • 使业务层负责跟踪和报告其更改。很快,所有类型的if构造都将悄悄进入,以检查值是否发生了更改
  • 打破了持久性无知,因为存储更新不是存储新对象,这只是一个数据层细节
  • 阻止数据访问层正常工作。事实上,你展示的实现是破坏性的。虽然数据访问层可能完全能够感知和保持粒度变化,但这种方法将整个对象标记为已修改,并强制滑动UPDATE

最新更新