Entity framework 4 实体框架中的多个SaveChanges调用
我正在基于实体框架构建自己的自定义存储库,并且我正在创建一些扩展方法,允许我将部分视图模型保存为实体模型,因此我正在构建自己的添加和更新方法 目前,每个方法都在末尾调用了DbContext中的SaveChanges(),这意味着对于每个模型,都将调用一个调用 我正在为MVC4站点构建这个基本DAL模式,这意味着大多数时候我将访问1个模型,但情况并非如此Entity framework 4 实体框架中的多个SaveChanges调用,entity-framework-4,transactions,transactionscope,Entity Framework 4,Transactions,Transactionscope,我正在基于实体框架构建自己的自定义存储库,并且我正在创建一些扩展方法,允许我将部分视图模型保存为实体模型,因此我正在构建自己的添加和更新方法 目前,每个方法都在末尾调用了DbContext中的SaveChanges(),这意味着对于每个模型,都将调用一个调用 我正在为MVC4站点构建这个基本DAL模式,这意味着大多数时候我将访问1个模型,但情况并非如此 更新时为每个模型调用SaveChanges()(即3个实体)是否太糟糕?还是我应该首先将所有内容添加到对象上下文,然后再将SaveChanges
更新时为每个模型调用SaveChanges()(即3个实体)是否太糟糕?还是我应该首先将所有内容添加到对象上下文,然后再将SaveChanges()作为某种事务提交?多次调用
SaveChanges
是一种糟糕的做法(没有事务范围)当相关实体应在单个事务中持久化时。您创建的是一个泄漏的抽象。创建单独的工作类单元或使用ObjectContext/DbContext
本身。我强烈建议不要在每个方法中调用SaveChanges()。使用存储库模式和工作单元是更好的方法。工作单元,使您能够更高效地处理数据库调用,并在某些数据无效时帮助您避免污染数据库(例如,用户详细信息正常,但地址失败)
这里有一个很好的教程可以帮助你
我知道这个答案有点晚,但我觉得分享一下很有用 现在在EF6中,使用
dbContext.Database.BeginTransaction()
像这样:
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
// do your changes
context.SaveChanges();
// do another changes
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception ex)
{
//Log, handle or absorbe I don't care ^_^
}
}
}
有关更多信息,请参阅
同样,这是在EF6以后的版本中一种新的现代方法,在这种情况下被提倡
如果您熟悉TransactionScope
类,那么您已经知道如何使用DbContextScope
。它们本质上非常相似-唯一的区别是DbContextScope
创建和管理DbContext
实例,而不是数据库事务。但就像TransactionScope
,DbContextScope
是环境的,可以嵌套,可以禁用其嵌套行为,并且可以与异步执行流一起正常工作
public void MarkUserAsPremium(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
dbContextScope.SaveChanges();
}
}
在DbContextScope
中,可以通过两种方式访问作用域管理的DbContext
实例。您可以通过DbContextScope.DbContexts
属性获取它们,如下所示:
public void SomeServiceMethod(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = dbContextScope.DbContexts.Get<MyDbContext>.Set<User>.Find(userId);
[...]
dbContextScope.SaveChanges();
}
}
public class UserRepository : IUserRepository
{
private readonly IAmbientDbContextLocator _contextLocator;
public UserRepository(IAmbientDbContextLocator contextLocator)
{
if (contextLocator == null) throw new ArgumentNullException("contextLocator");
_contextLocator = contextLocator;
}
public User Get(Guid userId)
{
return _contextLocator.Get<MyDbContext>.Set<User>().Find(userId);
}
}
这是使用我当前使用的UnitOfWork处理多个
context.SaveChanges()
的另一种方法
我们将按住所有context.SaveChanges()
方法,直到调用最后一个方法为止
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace DataAccess
{
public class UnitOfWork : IUnitOfWork
{
private readonly Context context;
private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private int beginChangeCount;
private bool selfManagedTransaction = true;
public UnitOfWork(Context context)
{
this.context = context;
}
//Use generic repo or init the instance of your repos here
public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntityModel
{
if (repositories.Keys.Contains(typeof(TEntity)))
return repositories[typeof(TEntity)] as IGenericRepository<TEntity>;
var repository = new Repository<TEntity>(context);
repositories.Add(typeof(TEntity), repository);
return repository;
}
public void SaveChanges()
{
if (selfManagedTransaction)
{
CommitChanges();
}
}
public void BeginChanges()
{
selfManagedTransaction = false;
Interlocked.Increment(ref beginChangeCount);
}
public void CommitChanges()
{
if (Interlocked.Decrement(ref beginChangeCount) > 0)
{
return;
}
beginChangeCount = 0;
context.SaveChanges();
selfManagedTransaction = true;
}
}
}
我熟悉UnitOfWork模式,但我发现它已经过时了,因为DbContext本身就是UnitOfWork(将鼠标悬停在它上面并阅读描述)。实现UnitOfWork是在已有的抽象之上进行抽象,而抽象的目的是相同的,这只会使事情变得更复杂,而不是更好。此外,我的模型验证是在控制器级别上进行的,而不是在数据库提交上进行的,因此在执行添加/更新之前,确保数据有效。我最担心的是性能问题,也就是说,将所有事务作为一个事务发送还是将多个单独的小事务发送是有区别的。是的
DbContext
是一个工作单元,是一次性的。但是,如果希望最小化数据库调用,则需要实现工作单元方法。您的IUnitOfWork
将有一个SaveChanges
方法,您的UnitOfWork
将继承IUnitOfWork
和IDisposable
或。。。使用TransactionScope。有时,在处理旧数据库时,您没有其他选项可以多次调用SaveChanges
。例如,您的数据库有一个存储过程或一个触发器,它生成一个非常特定的格式化标识符,并且需要在工作单元中间返回该标识符,这样您就可以将标识符指派给其他实体。因此,您需要先调用SaveChanges
将更改持久化到数据库并获取该标识符,然后再次调用以保存最终的实体。@JustAMartin很好。然而,糟糕的遗留技术不应导致糟糕的体系结构选择。在上面的示例中,如果第二个保存崩溃或者服务器在保存的中间重新启动,数据库将处于不一致的状态。应用程序必须足够智能,使数据库始终保持干净和一致。@GETah当然,我们在所有SaveChanges
调用中使用显式事务,以尽可能避免不一致。不幸的是,我生活在一个预算常常胜过质量的国家,特别是在大型政府项目中。客户不接受改进遗留数据结构的想法,因为这可能需要重新设计与其他系统的现有集成,这将导致计划外开支。我们的应用程序必须使用已经存在的数据,并尽可能少地进行更改。无需调用Rollback,它会在异常发生时自动调用……因为使用
将在事务离开作用域时处理该事务,如果提交
尚未对其成功调用,它将回滚。下面是证据:@TrilokChandra抱歉,我刚刚注意到了你们的评论。你们是对的,我根据它更新了我的答案。@DFTR这是一个相当笼统的评论-你们能更具体地说明什么不适用于Azure吗?为什么要在构造函数中检查(contextLocator==null)?你在开玩笑吗?IAmbientDbContextLocator应该是什么范围:单态还是瞬态?在.NET内核中
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace BusinessServices.Domain
{
public class AService : BaseBusinessService, IAService
{
private readonly IBService BService;
private readonly ICService CService;
private readonly IUnitOfWork uow;
public AService (IBService BService, ICService CService, IUnitOfWork uow)
{
this.BService = BService;
this.CService = CService;
this.uow = uow;
}
public void DoSomeThingComplicated()
{
uow.BeginChanges();
//Create object B - already have uow.SaveChanges() inside
//still not save to database yet
BService.CreateB();
//Create object C - already have uow.SaveChanges() inside
//still not save to databse yet
CService.CreateC();
//if there are no exceptions, all data will be saved in database
//else nothing in database
uow.CommitChanges();
}
}
}