Отображение сущностей базы данных в сущности домена через репозитории - статическое свойство или отдельный сервис?
Я работаю над приложением, в котором моя схема базы данных плохо согласуется с моделью моего домена, а изменения схемы базы данных не находятся под моим контролем. Из-за этого я в конечном итоге выполняю много сопоставлений в своих вызовах репозитория, и у меня есть много конкретных репозиториев для обработки сопоставления с базой данных и из нее (с использованием Entity Framework Database-first). То, что я ищу, - это элегантный способ звонить в мои репозитории на основе типа объекта сущности домена. Пока что сама модель предметной области все еще очень анемична, поскольку мы все еще находимся в процессе определения бизнес-правил.
В другом месте я видел пример (не могу вспомнить ссылку), когда вызовы репозитория передавались через доменные объекты через статическое свойство, но я не знаю, будут ли это вызывать проблемы с потоками или нарушает ли это какие-либо принципы модели домена, особенно если мы решим внедрить DI / IoC в будущем.
Вот пример того, что я имею до сих пор. Для краткости я упростил сопоставление вызовов к базе данных, поскольку сопоставление в реальном приложении более сложное.
Пример репозитория:
public interface IRepository<T>
{
T GetById(int id);
void Save(T entity);
}
public abstract class RepositoryFactory<T> : IRepository<T>
{
protected MyDbContext db;
protected int userId;
protected RepositoryFactory()
{
this.db = new MyDbContext();
this.userId = WebSecurity.GetCurrentUser().UserId;
}
public abstract T GetById(int id);
public abstract void Save(T entity);
}
public class CustomerRepository : RepositoryFactory<Customer>
{
public override void Save(Customer customer)
{
var entity = db.customers.FirstOrDefault(p => p.customerid == customer.Id && p.users.userid == userId);
if (entity == null) return; // TODO: Add error trapping
// Mapping (lots of reshaping happening here)
entity.customername = customer.Name;
entity.customeraddress = customer.Address;
// ...
// Save changes to DB
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
public override Customer GetById(int id)
{
var entity = db.customers.FirstOrDefault(p => p.customerid == id && p.users.userid == userId);
if (entity == null) return null; // TODO: Add error trapping
return new Customer
{
Name = entity.customername,
Address = entity.customeraddress,
// ...
};
}
}
Пример сущности домена:
public class Entity { public int Id { get; set; } }
public class Customer : Entity
{
public string Name { get; set; }
public string Address { get; set; }
// Does this violate any domain principles?
public static IRepository<Customer> Repository
{
get { return new CustomerRepository(); }
}
}
С этим кодом из моего контроллера я могу сделать что-то вроде:
Customer customer = Customer.Repository.GetById(id);
Вместо:
IRepository<Customer> repo = new CustomerRepository();
Customer customer = repo.GetById(id);
Это выглядит как очень элегантное решение моей проблемы, и это также избавляет меня от необходимости включать пространство имен репозитория в мои контроллеры (MVC). Если это пахнет смешно, и есть лучший способ справиться с этим, я хотел бы узнать. Единственное, о чем я могу думать, - это создать отдельный сервис crud для обработки моих вызовов в репозиторий, но я предполагаю, что для этого мне понадобится словарь или хеш-таблица для сопоставления моих конкретных репозиториев с объектами моей доменной модели, и кажется, что быть кошмаром обслуживания.
1 ответ
Я бы предложил использовать контейнер инверсии управления (внедрение зависимостей) и внедрить ваши репозитории в ваши контроллеры или где-либо еще. Таким образом, вы можете использовать их так:
public class HomeController : Controller
{
private readonly IRepository<Customer> _customerRepository;
public HomeController(IRepository<Customer> customerRepository)
{
_customerRepository = customerRepository;
}
public ActionResult Index(int id)
{
var customer = _customerRepository.GetById(id)
return View(customer);
}
}
Таким образом, если вам когда-либо понадобится заменить класс CustomerRepository или вам нужно иметь несколько версий (скажем, CustomerRepositoryEntityFramework или CustomerRepositoryNHibernate), вы можете просто заменить новый класс, унаследованный от IRepository, и ваш код контроллера по-прежнему будет работать без изменений.
Я рекомендую использовать Castle Windsor, Ninject или один из многих других контейнеров IoC.
Кроме того, вы, как правило, хотите сохранить свои доменные объекты как poco (Plain Old CLR Object). Это означает выделение всего из ваших сущностей, включая проверку, бизнес-правила и т. Д. И просто наличие только его свойств. Это позволяет вам легче передавать объекты вашего домена через конвейер, особенно если вы находитесь на ранних стадиях разработки. Это предложит вам наибольшую гибкость в будущем.