Добрый день!
Есть 3 интерефейса (слой инфрастуктуры SomeCompany.SomeProduct.Infrastructure):
public interface IRepository<TEntity> where TEntity : Entity
{
TEntity Insert(TEntity entity);
// ... other CRUD methods
}
public interface IUnitOfWork
{
void Commit();
void Rollback();
}
public interface IUnitOfWorkFactory
{
IUnitOfWork Create(Isolationlevel isolationLevel);
IUnitOfWork Create(); // For default isolationLevel = Isolationlevel.ReadCommited
}
И их реализация (SomeCompany.SomeProduct.DataAccess.EntityFramework):
public class RepositoryBase<TEntity> : IRepository<TEntity> where TEntity : Entity
{
protected readonly IDbContext _dbContext;
public RepositoryBase(IDbContext dbContext)
{
_dbContext = dbContext;
}
public virtual TEntity Insert(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
return _dbContext.Set<TEntity>().Add(entity);
}
// other methods
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbContext _dbContext;
private readonly IsolationLevel _isolationLevel;
protected DbContextTransaction _transaction;
private bool _isTransactionBeginer = false;
private bool _commited = false;
protected bool _disposed = false;
public UnitOfWork(IDbContext dbContext, IsolationLevel isolationLevel)
{
if (dbContext == null)
throw new ArgumentNullException("dbContext");
_dbContext = dbContext;
_isolationLevel = isolationLevel;
if (_dbContext.Database.CurrentTransaction == null)
{
_transaction = _dbContext.Database.BeginTransaction(_isolationLevel);
_isTransactionBeginer = true;
}
}
// Dispose ...
public void Commit()
{
_dbContext.SaveChanges();
if (_isTransactionBeginer && _transaction != null)
{
_transaction.Commit();
_transaction = _dbContext.Database.BeginTransaction(_isolationLevel);
_commited = true;
}
}
}
public class UnitOfWorkFactory<TDbContext> : IUnitOfWorkFactory<TDbContext> where TDbContext : IDbContext
{
private readonly TDbContext _dbContext;
public UnitOfWorkFactory(TDbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("dbContext");
_dbContext = dbContext;
}
public IUnitOfWork Create(IsolationLevel isolationLevel)
{
return new UnitOfWork(_dbContext, isolationLevel);
}
public IUnitOfWork Create()
{
return Create(IsolationLevel.ReadCommitted);
}
}
И самое главное работа со всем этим делом на уровне бизнес-логики (SomeCompany.SomeProduct.BusinessLogic):
public class CustomerService : ICustomerService
{
private readonly IUnitOfWorkFactory<IApplicationDbContext> _uowFactory;
private readonly ICustomerRepository _customerRepository;
public CustomerService(IUnitOfWorkFactory<IApplicationDbContext> uowFactory, ICustomerRepository _customerRepository)
{
_uowFactory = uowFactory;
_customerRepository = _customerRepository;
}
public void EditCustomer(int customerId, string customerName)
{
Customer customer = _customerRepository.FindbyId(customerId); // working with IApplicationDbContext (ApplicationDbContext) instance. Without transaction.
customer.ChangeName(customerName);
// some validation...
using(var uow = _uowFactory.Create()) // use same ApplicationDbContext instance, but start new transaction if not nested uow
{
_customerRepository.Update(customer);
uow.Commit(); // Transaction of ApplicationDbContext commited;
} // Close transaction if not nested uow
}
}
Остается самое главное - регистрация контекста в IoC контейнере:
builder.RegisterType<ApplicationDbContext>()
.As<IApplicationDbContext>()
.InstancePerRequest();
Таким образом, идея заключается в том, что на каждый запрос создается контекст данных, в данном случае ApplicationDbContext, и он расшаривается в соответствующие репозитории и фабрику UoW. Т.е. если необходимо выполнить запрос на чтение, то можно инжектировать ICustomerRepository и в сервисе просто выполнить _customerRepository.Find/Get/GetPaged. Без использования UnitOfWorkFactory.
Если же нужно записать что-то в базу, то Uow обеспечивает транзакцию и сохранение данных в БД (uow.Commit()).
Вопросы:
1) Является ли это решение потокобезопасным (thread safe)?
2) В рекомендациях использования DbContext (EF) - выполнить Dispose() как можно быстрее. В моем же примере получается, что экземпляр контекста создается и держится до выполнения HttpRequest'a. Это нормально?
3) При регистрации контекста в IoC нужно использовать InstancePerRequest() или InstancePerLifetimeScope() ?