【问题标题】:Entity Framework 5 Unit of Work pattern - where should I call SaveChanges?实体框架 5 工作单元模式 - 我应该在哪里调用 SaveChanges?
【发布时间】:2012-12-09 12:14:45
【问题描述】:

如果这似乎是一个重复的问题,请提前道歉。 This question 是我能找到的最接近的,但它并不能真正解决我面临的问题。

我在 ASP.NET MVC4 应用程序中使用 Entity Framework 5 并尝试实现工作单元模式。

我的工作单元类实现 IDisposable 并包含我的 DbContext 派生对象上下文类的单个实例,以及许多存储库,每个存储库都派生自一个通用基础存储库类,该类公开所有通常的存储库功能。

对于每个 HTTP 请求,Ninject 都会创建一个 Unit of Work 类的实例并将其注入控制器,并在请求完成时自动释放它。

由于 EF5 抽象出数据存储并且 Ninject 管理对象上下文的生命周期,因此它似乎是使用代码访问内存中实体对象而无需显式管理其持久性的完美方式。换句话说,为了优化关注点分离,我设想我的控制器操作方法能够使用和修改存储库数据,而无需在之后显式调用SaveChanges

我第一次(天真的)尝试实现这个想法,在每个修改数据的存储库基类方法中调用SaveChanges。当然,我很快意识到这既不是性能优化(尤其是在对同一个方法进行多次连续调用时),也不能适应操作方法直接修改从存储库中检索到的对象的属性的情况。

因此,我改进了我的设计,以消除这些对SaveChanges 的过早调用,并在处置工作单元实例时将它们替换为单个调用。这似乎是 MVC 中工作单元模式最简洁的实现,因为工作单元自然地限定为请求。

不幸的是,在构建了这个概念之后,我发现了它的致命缺陷——在DbContext 中添加或删除的对象是not reflected, even locally,直到调用了SaveChanges

那么,对于消费代码应该能够在不显式持久化对象的情况下使用对象这一想法,您有何看法?而且,如果这个想法似乎有效,那么使用 EF5 实现它的最佳方法是什么?

非常感谢您的建议,

提姆

更新:根据@Wahid 的回复,我在下面添加了一些测试代码,这些代码显示了消费代码必须显式调用SaveChanges 的一些情况:

var unitOfWork = _kernel.Get<IUnitOfWork>();
var terms = unitOfWork.Terms.Entities;

// Purge the table so as to start with a known state 
foreach (var term in terms)
{
    terms.Remove(term);
}

unitOfWork.SaveChanges();

Assert.AreEqual(0, terms.Count());

// Verify that additions are not even reflected locally until committed.
var created = new Term { Pattern = "Test" };
terms.Add(created);
Assert.AreEqual(0, terms.Count());

// Verify that additions are reflected locally once committed.
unitOfWork.SaveChanges();
Assert.AreEqual(1, terms.Count());

// Verify that property modifications to entities are reflected locally immediately 
created.Pattern = "Test2";
var another = terms.Single(term => term.Id == created.Id);
Assert.AreEqual("Test2", another.Pattern);
Assert.True(ReferenceEquals(created, another));

// Verify that queries against property changes fail until committed
Assert.IsNull(terms.FirstOrDefault(term => term.Pattern == "Test2"));

// Verify that queries against property changes work once committed
unitOfWork.SaveChanges();
Assert.NotNull(terms.FirstOrDefault(term => term.Pattern == "Test2"));

// Verify that deletions are not even reflected locally until committed.
terms.Remove(created);
Assert.AreEqual(1, terms.Count());

// Verify that additions are reflected locally once committed.
unitOfWork.SaveChanges();
Assert.AreEqual(0, terms.Count());

【问题讨论】:

  • 我觉得我和你有同样的问题。来自 NHibernate,我习惯于让 INSERT 和 DELETE 语句直接进入数据库,因此当我稍后尝试 SELECT 对象(在同一个 DbContext 会话中)时,我实际上得到了我的结果。我希望 SaveChanges() 只提交事务,但似乎 EF 会延迟所有 INSERT/DELETE 语句,直到您调用 SaveChanges()。你有没有想办法让它做你(我认为,我们)想做的事?

标签: entity-framework asp.net-mvc-4 repository-pattern unit-of-work savechanges


【解决方案1】:

首先SaveChanges 应该永远不会在存储库中。因为那会导致您失去UnitOfWork 的好处。

其次你需要做一个特殊的方法来保存UnitOfWork中的更改。

如果您想自动调用此方法,则可以使用其他解决方案,例如 ActionFilter,或者让您的所有控制器都继承自 BaseController 类并处理其中的 SaveChanges

无论如何UnitOfWork 应该总是有SaveChanges 方法。

【讨论】:

  • 非常感谢您的回复。我的最新实现完成了您建议的所有事情,并且它已经包含了一种在 HTTP 请求结束时调用 SaveChanges 的机制(基于 Ninject 的 InRequestScope 绑定)。但这并不能解决对象上下文可能进入无效状态的问题,即使在单个工作单元的范围内,如果使用代码修改某些内容而没有显式调用 SaveChanges。我将用一些代码更新我的问题以显示这一点。
  • 在使用“服务层”的情况下,其中每个服务都有对UOW的引用。我应该在哪里调用SaveChanges,以便我可以在其他服务中重用特定的服务方法?
  • 对我来说,我传递了一个可选参数来确认保存服务层方法中的更改AddEmployee(Employee model, bool save = true) 如果我没有传递此参数,那么服务将保存更改。
猜你喜欢
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多