Несколько вызовов SaveChanges в инфраструктуре сущностей
Я создаю свой собственный репозиторий на основе структуры сущности, и я создаю некоторые методы расширения, которые позволяют мне сохранять модели с частичным представлением в качестве моделей сущностей, поэтому я создаю свои собственные методы добавления и обновления.
В настоящее время каждый метод имеет SaveChanges() из DbContext, который вызывается в конце, что означает для каждой модели, будет вызван один вызов.
Я создаю этот базовый шаблон DAL для сайтов MVC4, что означает, что большую часть времени я буду обращаться к 1 модели, но это не обязательно.
Является ли слишком плохой практикой вызывать SaveChanges() для каждой модели при обновлении, т.е. 3 сущности, или я должен добавить все сначала в контекст объекта, а не как SaveChanges() как какую-то транзакцию?
Ответы
Ответ 1
Я знаю это как-то вроде позднего ответа, но я нашел полезным поделиться.
Теперь в 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)
{
dbContextTransaction.Rollback();
}
}
}
для более подробной информации смотрите this
снова в EF6 Вперед
Ответ 2
Плохая практика вызывать SaveChanges
несколько раз (без области транзакции), когда связанные объекты должны сохраняться в одной транзакции. То, что вы создали, - это нечеткая абстракция. Создайте отдельный класс Work of Work или используйте сам ObjectContext/DbContext
.
Ответ 3
Я бы настоятельно советовал не вызывать SaveChanges() в каждом методе. Использование шаблона репозитория и единицы работы - лучший путь вперед. Единица работы позволяет вам быть более эффективной с вашими вызовами db, а также помогает вам не загрязнять ваш db, если некоторые данные недействительны (например, данные пользователя в порядке, но адрес не работает).
Вот хороший учебник, который поможет вам.
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Ответ 4
Это еще один подход к обработке нескольких context.SaveChanges()
с использованием UnitOfWork, который я использую в настоящее время.
Мы будем удерживать весь метод 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();
}
}
}
Ответ 5
В таких сценариях рекомендуется использовать новый современный подход как сформулированный здесь.
Если вы знакомы с классом 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
. Если вам нужно получить доступ к экземплярам ambient 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);
}
}