【问题标题】:Repository pattern and mapping between domain models and Entity Framework领域模型和实体框架之间的存储库模式和映射
【发布时间】:2014-01-23 13:20:19
【问题描述】:

我的存储库处理并为富域模型提供持久性。我不想将贫血的 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
        }
    }
}

问题:

一个Company 可能由许多Departments 组成。如果我想在获取Company 时从CompanyRepository 急切地加载这些,那么我将在哪里定义DepartmentDepartmentDataEntity 之间的映射?

我可以在CompanyRepository 中提供更多映射方法,但这很快就会变得混乱。很快就会在整个系统中出现重复的映射方法。

解决上述问题的更好方法是什么?

【问题讨论】:

  • 1.严格按照经营理念定义公司。 2. 为实体用例建模 3. 请注意,如果做得正确,您将不会遇到现在遇到的问题。并忽略与持久性相关的任何内容
  • AutoMapper 可以用于相反的情况[...],但在创建域模型时却不行。 为什么不可能both 方式?
  • @ThomasWeller,AutoMapper 的创建者 Jimmmy Bogard 给出了答案 - The case for two-way mapping in AutoMapper
  • @davenewza,您是决定使用问题中描述的方法还是直接映射到域模型?

标签: .net entity-framework domain-driven-design repository-pattern onion-architecture


【解决方案1】:

我的存储库处理并为富域模型提供持久性。我不想将贫血的 Entity Framework 数据实体暴露给我的业务层,因此我需要某种方式在它们之间进行映射。

如果您使用实体框架,它可以映射富域模型本身。

我最近回答了类似的问题"Advice on mapping of entities to domain objects"

我一直在使用 NHibernate,并且知道在 Entity Framework 中您还可以指定从 DB 表到 POCO 对象的映射规则。在 Entity Framework 实体之上开发另一个抽象层是一项额外的工作。让 ORM 负责所有的 mappings、状态跟踪、unit of workidentity map 实现等。现代 ORM 知道如何处理所有这些问题。

AutoMapper 可用于相反的情况(映射到数据实体),但不能用于创建领域模型。

你完全正确。

当一个实体可以映射到另一个实体而不需要额外的依赖项(例如存储库、服务等)时,Automapper 很有用。

...我将在哪里定义DepartmentDepartmentDataEntity 之间的映射?

我会将其放入DepartmentRepository 并添加方法IList&lt;Department&gt; FindByCompany(int companyId) 以检索公司的部门。

我可以在CompanyRepository 中提供更多映射方法,但这很快就会变得混乱。很快就会在整个系统中出现重复的映射方法。

解决上述问题的更好方法是什么?

如果需要获取另一个实体的Departments 列表,则应在DepartmentRepository 中添加一个新方法,并在需要的地方简单地使用。

【讨论】:

  • 我不愿意不同意,但我发现 EF 不能正确映射到富域模型,但最适合贫乏的 POCO 类。 EF 与其他 ORM 一样,带有许多与业务规则相冲突的限制,例如 setter 的可见性或对构造函数的要求。在为业务规则和 EF 目的创建单个实体类之后,我建议不要这样做。使用数据层模型和业务/领域层模型,并根据需要在它们之间进行映射。这会将特定于 EF(ORM) 的代码从域逻辑中分离出来,并将促进 SRP。不过没有减号。
  • 我同意“ORM 带有许多与业务规则相冲突的限制”。尽管对于某些模型,这些限制并不重要,尤其是当您的团队纪律严明时。每个决定都应该基于上下文。
【解决方案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。现在,在我的项目中,我需要将这些数据实体映射到我的业务逻辑中使用的域模型对象。为了进行映射,我定义了扩展方法……我知道这是一种反模式,因为它是特定于语言的,但好处是它隔离并打破了彼此之间的依赖关系……是的,这就是它的美妙之处。所以,mapper的定义如下...

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();

请注意,您可以定义相同的内容将域模型对象映射到数据实体。

【讨论】:

  • 我很想做这样的事情,但是我的领域模型与我的基础设施层相结合。
  • 我认为您在这里稍微过分强调了 耦合 的想法 - 您的基础架构和域无论如何都是“耦合”的(也在您的示例代码中)。只要映射代码驻留在一个不是域模型和实体的位置,一切都很好。
  • @davenewza 您的域模型将如何与您的基础架构层耦合?领域模型是调用方法还是实例化基础设施层的具体对象?我还没有提出这样的建议……我说的是域模型和数据实体之间的映射
  • +1,扩展方法不会让你的领域层依赖于基础设施。它们可以在基础设施本身或另一个单独的模块中声明。这相当于使用驻留在基础设施层中的 Mapper 对象,只是更加简洁和特定于技术。
【解决方案3】:

对于实体框架,恕我直言,从实体模型转换为其他形式的模型(域模型、值对象、视图模型等)通常是一个坏主意,反之亦然,除非仅在应用层因为您将失去很多只能通过实体对象实现的 EF 功能,例如丢失更改跟踪和丢失 LINQ 可查询。

最好在存储库层和应用程序层之间进行映射。将实体模型保存在存储库层中。

【讨论】:

    【解决方案4】:

    我喜欢使用定制的扩展方法来做实体和域对象之间的映射。

    • 您可以轻松调用其他实体的扩展方法 位于包含实体内。
    • 您可以轻松应对 通过创建 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
            }
    

    并不完美,但很容易遵循并且很容易重复使用。

    【讨论】:

      【解决方案5】:

      就像以前的帖子所说的那样。最好等到存储库之后进行实际映射。但我喜欢使用自动映射器。它提供了一种将对象映射到其他对象的非常简单的方法。对于某些关注点分离,您还可以在单​​独的项目中定义映射。这些映射也是通用的/基于类型的:

      1. 您在映射中指定基本类型并定义它如何填充目标类型
      2. 在需要映射的地方,只需调用 Mapper.Map(baseTypeObject, DestinationTypeObject)
      3. Automapper 应该处理剩下的事情

      如果我正确理解了您的问题,这可以解决问题。

      【讨论】:

        【解决方案6】:

        我不喜欢手动映射,所以我使用http://valueinjecter.codeplex.com/进行映射

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多