#c_sharp #entity_framework #шаблоны_проектирования #проектирование
В EF DbContext и DbSet, вообще говоря, реализуют из коробки соответственно UnitOfWork и Repository. В интернете тысячи примеров как люди следуя четко по букварям оборачивает их руками в свои классы, которые реализуют свои интерфейсы.Что-то вроде этого: public interface IUnitOfWork : IDisposable { IRepositoryGetRepository () 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
Комментариев нет:
Отправить комментарий