Шаблон репозитория и сопоставление между моделями домена и Entity Framework
Мои хранилища касаются и обеспечивают постоянство для модели с богатым доменом. Я не хочу раскрывать анонимный объект данных Entity Framework для своих бизнес-слоев, поэтому мне нужен способ сопоставления между ними.
В большинстве случаев создание экземпляра модели домена из объекта данных требует использования параметризованных конструкторов и методов (поскольку оно богато). Это не так просто, как совпадение свойств/полей. AutoMapper можно использовать для противоположной ситуации (сопоставление с объектами данных), но не при создании моделей домена.
Ниже ядро моего шаблона репозитория.
Класс EntityFrameworkRepository
работает с двумя родовыми типами:
-
TDomainModel
: модель богатого домена
-
TEntityModel
: объект данных Entity Framework
Определены два абстрактных метода:
-
ToDataEntity(TDomainModel)
: для преобразования в объекты данных (для методов Add()
и Update()
)
-
ToDomainModel(TEntityModel)
: построить модели домена (для метода Find()
).
Конкретные реализации этих методов определяли бы отображение, необходимое для рассматриваемого репозитория.
public interface IRepository<T> where T : DomainModel
{
T Find(int id);
void Add(T item);
void Update(T item);
}
public abstract class EntityFrameworkRepository<TDomainModel, TEntityModel> : IRepository<TDomainModel>
where TDomainModel : DomainModel
where TEntityModel : EntityModel
{
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
// ...
}
public virtual TDomainModel Find(int id)
{
var entity = context.Set<TEntityModel>().Find(id);
return ToDomainModel(entity);
}
public virtual void Add(TDomainModel item)
{
context.Set<TEntityModel>().Add(ToDataEntity(item));
}
public virtual void Update(TDomainModel item)
{
var entity = ToDataEntity(item);
DbEntityEntry dbEntityEntry = context.Entry<TEntityModel>(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
context.Set<TEntityModel>().Attach(entity);
dbEntityEntry.State = EntityState.Modified;
}
}
protected abstract TEntityModel ToDataEntity(TDomainModel domainModel);
protected abstract TDomainModel ToDomainModel(TEntityModel dataEntity);
}
Вот базовый пример реализации репозитория:
public interface ICompanyRepository : IRepository<Company>
{
// Any specific methods could be included here
}
public class CompanyRepository : EntityFrameworkRepository<Company, CompanyTableEntity>, ICompanyRepository
{
protected CompanyTableEntity ToDataEntity(Company domainModel)
{
return new CompanyTable()
{
Name = domainModel.Name,
City = domainModel.City
IsActive = domainModel.IsActive
};
}
protected Company ToDomainModel(CompanyTableEntity dataEntity)
{
return new Company(dataEntity.Name, dataEntity.IsActive)
{
City = dataEntity.City
}
}
}
Проблема:
A Company
может состоять из многих Departments
. Если я хочу с нетерпением загрузить их из CompanyRepository
при извлечении Company
, то где бы я определял отображение между a Department
и a DepartmentDataEntity
?
Я мог бы предоставить больше методов сопоставления в CompanyRepository
, но это скоро станет беспорядочным. Скоро будут дублированные методы сопоставления в системе.
Каков наилучший подход к вышеуказанной проблеме?
Ответы
Ответ 1
Мои хранилища касаются и обеспечивают постоянство для модели с богатым доменом. Я не хочу раскрывать анонимный объект данных Entity Framework для своих бизнес-слоев, поэтому мне нужен способ сопоставления между ними.
Если вы используете Entity Framework, он может отображать модель самого богатого домена.
Недавно я ответил на аналогичный вопрос "Советы по отображению объектов на объекты домена" .
Я использую NHibernate и знаю, что в Entity Framework вы также можете указать правила сопоставления из таблиц БД в свои объекты POCO. Это дополнительное усилие для разработки другого уровня абстракции над объектами Entity Framework. Пусть ORM отвечает за все сопоставления, отслеживание состояния, единица работы и идентификационная карта и т.д. Современные ORM знают, как справляться со всеми этими проблемами.
AutoMapper можно использовать для противоположной ситуации (сопоставление с объектами данных), но не при создании моделей домена.
Вы совершенно правы.
Automapper полезен, когда один объект может быть отображен в другой без дополнительных зависимостей (например, Хранилища, Сервисы,...).
... где бы я определял отображение между a Department
и a DepartmentDataEntity
?
Я бы поместил его в DepartmentRepository
и добавил метод IList<Department> FindByCompany(int companyId)
, чтобы отработать отделы компании.
Я мог бы предоставить больше методов сопоставления в CompanyRepository
, но это скоро станет беспорядочным. Скоро будут дублированные методы сопоставления в системе.
Каков наилучший подход к вышеуказанной проблеме?
Если требуется получить список Department
для другого объекта, новый метод следует добавить в DepartmentRepository
и просто использовать там, где это необходимо.
Ответ 2
Скажем, у вас есть следующий объект доступа к данным...
public class AssetDA
{
public HistoryLogEntry GetHistoryRecord(int id)
{
HistoryLogEntry record = new HistoryLogEntry();
using (IUnitOfWork uow = new NHUnitOfWork())
{
IReadOnlyRepository<HistoryLogEntry> repository = new NHRepository<HistoryLogEntry>(uow);
record = repository.Get(id);
}
return record;
}
}
который возвращает объект данных записи журнала истории. Этот объект данных определяется следующим образом:
public class HistoryLogEntry : IEntity
{
public virtual int Id
{ get; set; }
public virtual int AssetID
{ get; set; }
public virtual DateTime Date
{ get; set; }
public virtual string Text
{ get; set; }
public virtual Guid UserID
{ get; set; }
public virtual IList<AssetHistoryDetail> Details { get; set; }
}
Вы можете видеть, что свойство Details
ссылается на другой объект данных AssetHistoryDetail
. Теперь в моем проекте мне нужно сопоставить эти объекты данных с объектами модели домена, которые используются в моей бизнес-логике. Чтобы сделать сопоставление, я определил методы расширения... Я знаю, что это анти-шаблон, поскольку он специфичен для языка, но хорошо, что он изолирует и ломает зависимости между собой... да, это красота его. Итак, преобразователь определяется следующим образом:
internal static class AssetPOMapper
{
internal static HistoryEntryPO FromDataObject(this HistoryLogEntry t)
{
return t == null ? null :
new HistoryEntryPO()
{
Id = t.Id,
AssetID = t.AssetID,
Date = t.Date,
Text = t.Text,
UserID = t.UserID,
Details = t.Details.Select(x=>x.FromDataObject()).ToList()
};
}
internal static AssetHistoryDetailPO FromDataObject(this AssetHistoryDetail t)
{
return t == null ? null :
new AssetHistoryDetailPO()
{
Id = t.Id,
ChangedDetail = t.ChangedDetail,
OldValue = t.OldValue,
NewValue = t.NewValue
};
}
}
и это в значительной степени. Все зависимости находятся в одном месте. Затем, когда вы вызываете объект данных из уровня бизнес-логики, я бы позволил LINQ
сделать все остальное...
var da = new AssetDA();
var entry = da.GetHistoryRecord(1234);
var domainModelEntry = entry.FromDataObject();
Обратите внимание, что вы можете определить то же самое для сопоставления объектов модели домена с объектами данных.
Ответ 3
Мне нравится использовать настраиваемые методы расширения для сопоставления объектов Entity и Domain.
- Вы можете легко вызвать методы расширения для других объектов, когда они
находятся в пределах содержащего объекта.
- Вы можете легко справиться с
коллекции путем создания расширений IEnumerable < > .
Простой пример:
public static class LevelTypeItemMapping
{
public static LevelTypeModel ToModel(this LevelTypeItem entity)
{
if (entity == null) return new LevelTypeModel();
return new LevelTypeModel
{
Id = entity.Id;
IsDataCaptureRequired = entity.IsDataCaptureRequired;
IsSetupRequired = entity.IsSetupRequired;
IsApprover = entity.IsApprover;
Name = entity.Name;
}
}
public static IEnumerable<LevelTypeModel> ToModel(this IEnumerable<LevelTypeItem> entities)
{
if (entities== null) return new List<LevelTypeModel>();
return (from e in entities
select e.ToModel());
}
}
... и вы используете их вот так.....
using (IUnitOfWork uow = new NHUnitOfWork())
{
IReadOnlyRepository<LevelTypeItem> repository = new NHRepository<LevelTypeItem>(uow);
record = repository.Get(id);
return record.ToModel();
records = repository.GetAll(); // Return a collection from the DB
return records.ToModel(); // Convert a collection of entities to a collection of models
}
Не идеальный, но очень простой в использовании и очень простой в использовании.
Ответ 4
С инфраструктурой сущностей, во всех слоях IMHO, как правило, плохая идея конвертировать из моделей сущностей в другую модель (модели домена, объекты значений, модели представления и т.д.) и наоборот, за исключением только в прикладном уровне потому что вы потеряете много возможностей EF, которые вы можете достичь только через объекты сущности, такие как потеря отслеживания изменений и потеря запросов LINQ.
Лучше сделать сопоставление между вашим уровнем репозитория и уровнем приложения. Храните модели сущностей в слое репозитория.
Ответ 5
Как и предыдущие сообщения. Вероятно, лучше всего ждать, пока репозитории не сделают фактическое сопоставление. НО мне нравится работать с auto-mapper. Он обеспечивает очень простой способ сопоставления объектов с другими объектами. Для некоторого разделения проблем вы также можете определить сопоставления в отдельном проекте. Эти сопоставления также основаны на типе:
- Вы указываете базовый тип в сопоставлении и определяете, как он заполняет тип адресата
- Если вам нужно сопоставление, вы просто вызываете Mapper.Map(baseTypeObject, DestinationTypeObject)
- Automapper должен обрабатывать остальные
Это может сделать трюк, если я правильно понял ваш вопрос.
Ответ 6
Мне не нравится сопоставлять вручную, поэтому для сопоставления я использую http://valueinjecter.codeplex.com/