Подтвердить что ты не робот

Несколько вызовов SaveChanges в инфраструктуре сущностей

Я создаю свой собственный репозиторий на основе структуры сущности, и я создаю некоторые методы расширения, которые позволяют мне сохранять модели с частичным представлением в качестве моделей сущностей, поэтому я создаю свои собственные методы добавления и обновления.

В настоящее время каждый метод имеет SaveChanges() из DbContext, который вызывается в конце, что означает для каждой модели, будет вызван один вызов.

Я создаю этот базовый шаблон DAL для сайтов MVC4, что означает, что большую часть времени я буду обращаться к 1 модели, но это не обязательно.

Является ли слишком плохой практикой вызывать SaveChanges() для каждой модели при обновлении, т.е. 3 сущности, или я должен добавить все сначала в контекст объекта, а не как SaveChanges() как какую-то транзакцию?

4b9b3361

Ответ 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);
    }
}