我正在构建自己的自定义存储库,基于实体框架,我正在创建一些扩展方法,允许我将部分视图模型保存为实体模型,因此我正在构建自己的添加和更新方法。
目前,每个方法都有来自DbContext的SaveChanges(),在末尾调用,这意味着每个模型都会调用一个调用。
我正在为MVC4网站构建这个基本DAL模式,这意味着大多数时候我将访问1个模型,但事实并非如此。
在更新3个实体时为每个模型调用SaveChanges()是不是太糟糕了,或者我应该先将所有内容添加到对象上下文中,还是将SaveChanges()作为某种事务提交来进行?
答案 0 :(得分:65)
我知道这是一个迟到的答案,但我觉得分享很有用。
现在在 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 ^_^
}
}
}
有关详细信息,请查看this
再次出现在EF6 Onwards
答案 1 :(得分:6)
当相关实体应该在单个事务中持久化时,多次调用SaveChanges
(没有事务范围)是一种不好的做法。你创建的是漏洞抽象。创建一个单独的工作单元类或使用ObjectContext/DbContext
本身。
答案 2 :(得分:3)
我强烈建议不要在每个方法中调用SaveChanges()。使用存储库模式和工作单元是更好的前进方法。工作单元允许您更有效地使用db调用,并且还可以帮助您防止在某些数据无效时污染数据库(例如,用户详细信息正常,但地址失败)。
这是一个很好的教程来帮助你。
答案 3 :(得分:1)
这是使用我目前使用的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;
}
}
}
使用样品。
在下面的代码中找到我的评论
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();
}
}
}
答案 4 :(得分:0)
在这种情况下建议采用articulated here这种新的现代方法。
如果您熟悉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();
}
}
但这当然只能在创建DbContextScope
的方法中使用。如果您需要访问其他任何地方的环境DbContext
实例(例如,在存储库类中),您可以依赖于IAmbientDbContextLocator
,您可以这样使用:
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);
}
}