Страницы

Поиск по вопросам

суббота, 21 декабря 2019 г.

Как использовать один экземпляр DbContext?(IUnitOfWork)

#c_sharp #entity_framework #шаблоны_проектирования #проектирование


В EF DbContext и DbSet, вообще говоря, реализуют из коробки соответственно UnitOfWork
и Repository. В интернете тысячи примеров как люди следуя четко по букварям оборачивает
их руками в свои классы, которые реализуют свои интерфейсы.Что-то вроде этого:

public interface IUnitOfWork : IDisposable
{
    IRepository GetRepository() where T : class;
    void SaveAllChanges();
}
 public interface IRepository : IDisposable
    where T : class
{
    IQueryable Entities();
    void Update(T entity);
    void Add(T entity);
    void Remove(T entity);
    bool Contains(T entity);
}


Получается своего рода абстракция над абстракцией, во многих местах пишут что этого
не следует делать, но нигде примера реализации как правильно нет. Я имею проект на
трехслойке, изначально сделал так же через repo и uow, позже убрал репозитории и uow,
но при добавлении объектов из разных классов к друг другу ловлю:
System.InvalidOperationException: "Не удалось определить связь между двумя объектами,
поскольку они привязаны к разным объектам ObjectContext."
 Как я понимаю, это из за того что я каждый раз объявляю новый экземпляр контекста
данных, вот что у меня щас в БЛЛ:

 public class EntityService : IEntityService
 {
    private MyContext db;
    public EntityService(string connectionString)
    {
        db = new MyContext(connectionString);
     }
    ...
}


Как мне правильно передавать один экземпляр контекста данных вовсе подобные сервисы
без явной повторной реализации UoW?
    


Ответы

Ответ 1



Можно передавать в конструктор DbContext который будет возвращать какой-нибудь DI-контейнер. В самом контейнере уже настроить жизненный цикл объекта private DbContext db; public EntityService(DbContext context) { db = context; } В контейнерах регистрируем MyContext для DbContext. Если уверенны, что тип вашего зарегистрированного контекста не поменяется, то можно еще и приведение сделать private MyContext db; public EntityService(DbContext context) { db = (MyContext)context; }

Ответ 2



Если пропустить промежуточные стадии, то вариантов два (с половиной): Вариант 1 Если вы не пишете тесты, то вам вообще не нужна абстракция IRepository в таком виде. Создавайте контекст на самом верху, в методе, который у вас соответствует одной бизнес-операции, связывайте объекты друг с другом, потом один раз вызывайте SaveChanges - и EF сам разберется. Это идеология, заложенная в EF, и попытки пойти против нее вызывают много кода и боль. Вариант 1.01 Вариант, привычный со времен NHibernate - сделать контекст на запрос (от пользователя), положить в HttpContext (напрямую или через IoC) и более-менее споконо жить до момента, когда вам придется провести две раздельных операции за один запрос. Или записать ошибку операции в лог. Когда момент наступит - переписать на [ThreadStatic] / и жить дальше. Вариант 2 Если вам нужны и UoW, и тесты, и репозиторий (ради тестов и ради локализации запросов), то придется наворачивать что-то вроде: Интерфейс IoW в качестве точки доступа к репозиториям: public interface IUnitOfWork : IDisposable { IEntity1Repository Entity1Repository { get; } IEntity2Repository Entity2Repository { get; } void Save(); } Его реализацию в виде public class UnitOfWork : IUnitOfWork { static AsyncLocal _root = new AsyncLocal(); private readonly SomeModel _context; public UnitOfWork() { if (_root.Value == null) { this._context = new SomeModel(); _root.Value = this; } else { this._context = _root.Value._context; } } public void Save() { if (_root.Value == this) { _context.SaveChanges(); } } public IEntity1Repository Entity1Repository => new Entity1Repository(_context); public void Dispose() { if (_root.Value == this) { _context.Dispose(); _root.Value = null; } } } Т.к. вам захочется мокать репозитории и контекст, то придется добавить интерфейс для создания UoW: public interface IUnitOfWorkFactory { IUnitOfWork Create(); } вставлять его в виде зависимостей в сервисы и использовать примерно так: public class SomeService : ISomeService { [Dependency] public IUnitOfWorkFactory UoWFactory { get; set; } public List GetAllEntitiesList() { using (var uow = UoWFactory.Create()) { // добавить вызов Save в тех методах, которые действительно что-то меняют return uow.Entity1Repository.GetAll(); } } } и мокать примерно так: var list = new List { new Entity1() }; var repo = Mock.Of(repo => repo.GetAll() == list); var uow = Mock.Of(u => u.Entity1Repository == repo); var uowFactory = Mock.Of(f => f.Create() == uow); var service = new SomeService() { UoWFactory = uowFactory }; var result = service.GetAllEntitiesList(); CollectionAssert.AreEqual(list, result); Ссылка по теме, с кучей других вариантов вставки: Survey of Entity Framework Unit of Work Patterns

Комментариев нет:

Отправить комментарий