В EF DbContext и DbSet, вообще говоря, реализуют из коробки соответственно UnitOfWork и Repository. В интернете тысячи примеров как люди следуя четко по букварям оборачивает их руками в свои классы, которые реализуют свои интерфейсы.Что-то вроде этого:
public interface IUnitOfWork : IDisposable
{
IRepository<T> GetRepository<T>() where T : class;
void SaveAllChanges();
}
public interface IRepository<T> : IDisposable
where T : class
{
IQueryable<T> 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?
Если пропустить промежуточные стадии, то вариантов два (с половиной):
Если вы не пишете тесты, то вам вообще не нужна абстракция IRepository
в таком виде. Создавайте контекст на самом верху, в методе, который у вас соответствует одной бизнес-операции, связывайте объекты друг с другом, потом один раз вызывайте SaveChanges
- и EF сам разберется.
Это идеология, заложенная в EF, и попытки пойти против нее вызывают много кода и боль.
Вариант, привычный со времен NHibernate - сделать контекст на запрос (от пользователя), положить в HttpContext
(напрямую или через IoC) и более-менее споконо жить до момента, когда вам придется провести две раздельных операции за один запрос. Или записать ошибку операции в лог. Когда момент наступит - переписать на [ThreadStatic]
/ <AsyncLocal>
и жить дальше.
Если вам нужны и UoW, и тесты, и репозиторий (ради тестов и ради локализации запросов), то придется наворачивать что-то вроде:
Интерфейс IoW в качестве точки доступа к репозиториям:
public interface IUnitOfWork : IDisposable
{
IEntity1Repository Entity1Repository { get; }
IEntity2Repository Entity2Repository { get; }
void Save();
}
Его реализацию в виде
public class UnitOfWork : IUnitOfWork
{
static AsyncLocal<UnitOfWork> _root = new AsyncLocal<UnitOfWork>();
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<Entity1> GetAllEntitiesList()
{
using (var uow = UoWFactory.Create())
{
// добавить вызов Save в тех методах, которые действительно что-то меняют
return uow.Entity1Repository.GetAll();
}
}
}
и мокать примерно так:
var list = new List<Entity1> { new Entity1() };
var repo = Mock.Of<IEntity1Repository>(repo => repo.GetAll() == list);
var uow = Mock.Of<IUnitOfWork>(u => u.Entity1Repository == repo);
var uowFactory = Mock.Of<IUnitOfWorkFactory>(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
Можно передавать в конструктор DbContext
который будет возвращать какой-нибудь DI-контейнер. В самом контейнере уже настроить жизненный цикл объекта
private DbContext db;
public EntityService(DbContext context)
{
db = context;
}
В контейнерах регистрируем MyContext
для DbContext
. Если уверенны, что тип вашего зарегистрированного контекста не поменяется, то можно еще и приведение сделать
private MyContext db;
public EntityService(DbContext context)
{
db = (MyContext)context;
}
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Из одной формы запускается другая при помощи подобного кода (по сути, стандартного):
Есть button Play и button PauseХочу объединить их в одну кнопку, чтобы когда композиция уже играет при нажатии ставилась пауза, а если стоит пауза то при...
Есть код который открывает xml-файл: