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

Entity Framework 6 и подразделение работы... Где, когда? Это как транзакции в ado.net?

Создание нового проекта MVC и идея создания репозиториев в слое данных, поэтому я их реализовал. Я также создал слой Service для обработки всей бизнес-логики и проверки, этот слой, в свою очередь, использует соответствующий репозиторий. Что-то вроде этого (я использую простой инжектор для инъекций)

DAL LAYER

public MyRepository() {

    private DbContext _context;
    public MyRepository(DbContext context) {
        _context = context;
    }    

    public MyEntity Get(int id)
    {
        return _context.Set<MyEntity>().Find(id);
    }

    public TEntity Add(MyEntity t)
    {
        _context.Set<MyEntity>().Add(t);
        _context.SaveChanges();
        return t;
    }

    public TEntity Update(MyEntity updated, int key)
    {
        if (updated == null)
            return null;

        MyEntity existing = _context.Set<MyEntity>().Find(key);
        if (existing != null)
        {
            _context.Entry(existing).CurrentValues.SetValues(updated);
            _context.SaveChanges();
        }
        return existing;
    }

    public void Delete(MyEntity t)
    {
        _context.Set<MyEntity>().Remove(t);
        _context.SaveChanges();
    }
}

СЕРВИСНЫЙ СЛОЙ

public MyService() {
    private MyRepository _repository;

    public MyService(MyRepository repository) {
        _repository = repository;    
    }

    public MyEntity Get(int id)
    {
        return _repository.Get(id);
    }

    public MyEntity Add(MyEntity t)
    {
        _repository.Add(t);

        return t;
    }

    public MyEntity Update(MyEntity updated)
    {
        return _repository.Update(updated, updated.Id);
    }

    public void Delete(MyEntity t)
    {
        _repository.Delete(t);
    }
}

Теперь это очень просто, поэтому я могу использовать следующий код для обновления объекта.

MyEntity entity = MyService.Get(123);
MyEntity.Name = "HELLO WORLD";
entity = MyService.Update(entity);

Или это для создания объекта

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);
// entity.Id is now populated

Теперь скажите, что мне нужно обновить элемент, основанный на идентификаторе создания другого, я мог бы использовать код, прежде всего отлично, но что произойдет, если произошла ошибка? Мне нужна какая-то транзакция/откат. Это то, что, по-видимому, решит модель "Единица работы"?

Итак, я думаю, мне нужно иметь DbContext в моем объекте UnitOfWork, поэтому я создаю такой объект?

public UnitOfWork() : IDisposable {

    private DbContext _context;

    public UnitOfWork(DbContext context) {
        _context = context;
    }

    public Commit() {
        _context.SaveChanges();
    }

    public Dispose() {
        _context.Dispose();
    }

}

Хорошо, снова, это довольно просто. UnitOfWork также поддерживает контекст (во всяком случае, я использую такой же контекст во всех репозиториях), и он вызывает метод SaveChanges(). Затем я удалил вызов метода SaveChanges() из моего репозитория. Поэтому для добавления я бы сделал следующее:

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);

uow.Commit();

Но что, если мне нужно создать объект, а затем обновить другие объекты на основе этого Идентификатора, это теперь не сработает, потому что Id не будет создан, пока я не назову Commit на uow. Пример

UnitOfWork uow = new UnitOfWork(new DbContext()); // i would inject this somehow

MyEntity entity = new MyEntity();
MyEntity.Name = "HELLO WORLD";
entity = MyService.Add(entity);
// entity.Id is NOT populated

MyEntity otherEntity = MyService.Get(123);
otherEntity.OtherProperty = entity.Id;
MyService.Update(otherEntity);

uow.Commit();  // otherEntity.OtherProperty is not linked.....?

Итак, у меня такое чувство, что этот класс UnitOfWork не прав... может быть, я пропустил понимание чего-то.

Мне нужно иметь возможность добавлять сущность и получать этот идентификатор и использовать его на другом объекте, но если возникает ошибка, я хочу "откат", как это сделала бы транзакция ado.net.

Возможно ли использование этой функции с использованием Entity Framework и репозиториев?

4b9b3361

Ответ 1

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


Во-первых, DbContext сам реализует Единицу рабочего шаблона. Вызов SaveChanges делает создание транзакции БД, поэтому каждый запрос, выполненный против БД, будет откат, что-то не так.

Теперь в текущем проекте есть серьезная проблема: ваш репозиторий вызывает SaveChanges на DbContext. Это означает, что вы делаете XXXRepository ответственным за принятие всех изменений, внесенных вами на единицу работы, а не только изменений в объектах XXX, за которые отвечает ваш репозиторий.

Другое дело, что DbContext тоже сам репозиторий. Поэтому абстрагирование использования DbContext внутри другого репозитория просто создает еще одну абстракцию на существующую абстракцию, что слишком много кода IMO.

Кроме того, вам может потребоваться доступ к объектам XXX из репозитория YYY и YYY-объектов из репозитория XXX, поэтому, чтобы избежать циклических зависимостей, вы получите бесполезный MyRepository : IRepository<TEntity>, который просто дублирует все методы DbSet.

Я бы сбросил весь слой репозитория. Я бы использовал DbContext непосредственно внутри уровня сервиса. Конечно, вы можете учитывать все сложные запросы, которые вы не хотите дублировать на уровне сервиса. Что-то вроде:

public MyService()
{
    ...
    public MyEntity Create(some parameters)
    {
        var entity = new MyEntity(some parameters);
        this.context.MyEntities.Add(entity);

        // Actually commits the whole thing in a transaction
        this.context.SaveChanges();

        return entity;
    }

    ...

    // Example of a complex query you want to use multiple times in MyService
    private IQueryable<MyEntity> GetXXXX_business_name_here(parameters)
    {
        return this.context.MyEntities
            .Where(z => ...)
            .....
            ;
    }
}

С помощью этого шаблона каждый публичный вызов класса сервиса выполняется внутри транзакции, благодаря DbContext.SaveChanges, являющемуся транзакционным.

Теперь для примера, который у вас есть с идентификатором, который требуется после первой вставки объекта, одним из решений является не использование идентификатора, а самого объекта. Таким образом, вы разрешаете Entity Framework и свою собственную реализацию шаблона работы.

Итак, вместо:

var entity = new MyEntity();
entity = mydbcontext.Add(entity);
// what should I put here?
var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherPropertyId = entity.Id;

uow.Commit();

у вас есть:

var entity = new MyEntity();
entity = mydbcontext.Add(entity);

var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherProperty = entity;     // Assuming you have a navigation property

uow.Commit();

Если у вас нет свойства навигации или у вас более сложный случай использования, решение заключается в использовании хорошей транзакции золота внутри вашего метода общедоступной службы:

public MyService()
{
    ...
    public MyEntity Create(some parameters)
    {
        // Encapuslates multiple SaveChanges calls in a single transaction
        // You could use a ITransaction if you don't want to reference System.Transactions directly, but don't think it really useful
        using (var transaction = new TransactionScope())
        {
            var firstEntity = new MyEntity { some parameters };
            this.context.MyEntities.Add(firstEntity);

            // Pushes to DB, this'll create an ID
            this.context.SaveChanges();

            // Other commands here
            ...

            var newEntity = new MyOtherEntity { xxxxx };
            newEntity.MyProperty = firstEntity.ID;
            this.context.MyOtherEntities.Add(newEntity);

            // Pushes to DB **again**
            this.context.SaveChanges();

            // Commits the whole thing here
            transaction.Commit();

            return firstEntity;
        }
    }
}

Вы можете даже вызвать метод нескольких служб внутри области транзакций, если это необходимо:

public class MyController()
{
    ...

    public ActionResult Foo()
    {
        ...
        using (var transaction = new TransactionScope())
        {
            this.myUserService.CreateUser(...);
            this.myCustomerService.CreateOrder(...);

            transaction.Commit();
        }
    }
}