Подтвердить что ты не робот

Шаблон репозитория и сопоставление между моделями домена и 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, но это скоро станет беспорядочным. Скоро будут дублированные методы сопоставления в системе.

Каков наилучший подход к вышеуказанной проблеме?

4b9b3361

Ответ 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/