Ответ 1
Я могу сказать, что этот код достаточно хорош в первый раз, но у него есть некоторые места для улучшения.
Пропустите некоторые из них.
1. Инъекция зависимостей (DI) и использование IoC.
Вы используете простейшую версию шаблон локатора службы - сам экземпляр container
.
Я предлагаю вам использовать "инъекцию конструктора". Вы можете найти дополнительную информацию здесь (вставка ASP.NET MVC 4 Dependency Injection).
public class CustomerController : Controller
{
private readonly IUnitOfWork unitOfWork;
private readonly ICustomerRepository customerRepository;
public CustomerController(
IUnitOfWork unitOfWork,
ICustomerRepository customerRepository)
{
this.unitOfWork = unitOfWork;
this.customerRepository = customerRepository;
}
public ActionResult List()
{
return View(customerRepository.Query());
}
[HttpPost]
public ActionResult Create(Customer customer)
{
customerRepository.Add(customer);
unitOfWork.SaveChanges();
return RedirectToAction("List");
}
}
2. Область работы (UoW).
Я не могу найти стиль жизни IUnitOfWork
и ICustomerRepository
. Я не знаком с Unity, но msdn говорит, что TransientLifetimeManager используется по умолчанию. Это означает, что вы будете получать новый экземпляр каждый раз при разрешении типа.
Итак, следующий тест не выполняется:
[Test]
public void MyTest()
{
var target = new UnityContainer();
target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
target.RegisterType<ICustomerRepository, CustomerRepository>();
//act
var unitOfWork1 = target.Resolve<IUnitOfWork>();
var unitOfWork2 = target.Resolve<IUnitOfWork>();
// assert
// This Assert fails!
unitOfWork1.Should().Be(unitOfWork2);
}
И я ожидаю, что экземпляр UnitOfWork
в вашем контроллере отличается от экземпляра UnitOfWork
в вашем репозитории. Иногда это может быть вызвано ошибками. Но он не выделяется в ASP.NET MVC 4 Dependency Injection как проблема для Unity.
В Castle Windsor PerWebRequest образ жизни используется для совместного использования одного и того же экземпляра типа в одном HTTP-запросе.
Это обычный подход, когда UnitOfWork
является компонентом PerWebRequest. Пользовательский ActionFilter
может использоваться для вызова Commit()
во время вызова метода OnActionExecuted()
.
Я бы также переименовал метод SaveChanges()
и назвал его просто Commit
, как он вызывается в , а в PoEAA.
public interface IUnitOfWork : IDisposable
{
void Commit();
}
3.1. Зависимости от репозиториев.
Если ваши репозитории будут "пустыми", нет необходимости создавать для них определенные интерфейсы. Можно разрешить IRepository<Customer>
и иметь следующий код в контроллере
public CustomerController(
IUnitOfWork unitOfWork,
IRepository<Customer> customerRepository)
{
this.unitOfWork = unitOfWork;
this.customerRepository = customerRepository;
}
Есть тест, который проверяет его.
[Test]
public void MyTest()
{
var target = new UnityContainer();
target.RegisterType<IRepository<Customer>, CustomerRepository>();
//act
var repository = target.Resolve<IRepository<Customer>>();
// assert
repository.Should().NotBeNull();
repository.Should().BeOfType<CustomerRepository>();
}
Но если вы хотите иметь репозитории, которые являются "уровнем абстракции над слоем отображения, где сконфигурирован код построения запроса". (PoEAA, репозиторий)
Репозиторий выступает посредником между слоями отображения домена и данных, действуя подобно коллекции объектов домена в памяти. Объекты клиента конструировать спецификации запросов декларативно и представить их Репозиторий для удовлетворения.
3.2. Наследование на EntityFrameworkRepository.
В этом случае я бы создал простой IRepository
public interface IRepository
{
void Add(object item);
void Remove(object item);
IQueryable<T> Query<T>() where T : class;
}
и его реализация, которая знает, как работать с инфраструктурой EntityFramework и может быть легко заменена другой (например, AzureTableStorageRepository
).
public class EntityFrameworkRepository : IRepository
{
public readonly EntityFrameworkUnitOfWork unitOfWork;
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;
if (entityFrameworkUnitOfWork == null)
{
throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
}
this.unitOfWork = entityFrameworkUnitOfWork;
}
public void Add(object item)
{
unitOfWork.GetDbSet(item.GetType()).Add(item);
}
public void Remove(object item)
{
unitOfWork.GetDbSet(item.GetType()).Remove(item);
}
public IQueryable<T> Query<T>() where T : class
{
return unitOfWork.GetDbSet<T>();
}
}
public interface IUnitOfWork : IDisposable
{
void Commit();
}
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
private readonly DbContext context;
public EntityFrameworkUnitOfWork()
{
this.context = new CustomerContext();
}
internal DbSet<T> GetDbSet<T>()
where T : class
{
return context.Set<T>();
}
internal DbSet GetDbSet(Type type)
{
return context.Set(type);
}
public void Commit()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
И теперь CustomerRepository
может быть прокси-сервером и ссылаться на него.
public interface IRepository<T> where T : class
{
void Add(T item);
void Remove(T item);
}
public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
protected readonly IRepository Repository;
protected RepositoryBase(IRepository repository)
{
Repository = repository;
}
public void Add(T item)
{
Repository.Add(item);
}
public void Remove(T item)
{
Repository.Remove(item);
}
}
public interface ICustomerRepository : IRepository<Customer>
{
IList<Customer> All();
IList<Customer> FindByCriteria(Func<Customer, bool> criteria);
}
public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
public CustomerRepository(IRepository repository)
: base(repository)
{ }
public IList<Customer> All()
{
return Repository.Query<Customer>().ToList();
}
public IList<Customer> FindByCriteria(Func<Customer, bool> criteria)
{
return Repository.Query<Customer>().Where(criteria).ToList();
}
}