存储库模式和域模型与实体框架之间的映射

Dav*_*New 42 .net domain-driven-design entity-framework repository-pattern onion-architecture

我的存储库处理并为丰富的域模型提供持久性.我不想将贫血的Entity Framework数据实体暴露给我的业务层,所以我需要一些在它们之间进行映射的方法.

在大多数情况下,从数据实体构造域模型实例需要使用参数化构造函数和方法(因为它很丰富).它不像属性/字段匹配那么简单.AutoMapper可用于相反的情况(映射到数据实体),但不能用于创建域模型.

以下是我的存储库模式的核心.

EntityFrameworkRepository类的工作有两个泛型类型:

  • TDomainModel:丰富的域模型
  • TEntityModel:实体框架数据实体

定义了两种抽象方法:

  • ToDataEntity(TDomainModel):转换为数据实体(for 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);
}
Run Code Online (Sandbox Code Playgroud)

以下是存储库实现的基本示例:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:

A Company可能由许多人组成Departments.如果我想从CompanyRepository获取时急切地加载这些Company,那么我将在哪里定义a Department和a 之间的映射DepartmentDataEntity

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

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

Ily*_*kin 30

我的存储库处理并为丰富的域模型提供持久性.我不想将贫血的Entity Framework数据实体暴露给我的业务层,所以我需要一些在它们之间进行映射的方法.

如果您使用实体框架,它可以映射Rich Domain Model本身.

我最近回答了类似的问题"关于实体到域对象的映射的建议".

我一直在使用NHibernate,并且知道在Entity Framework中你也可以指定从DB表到POCO对象的映射规则.在Entity Framework实体上开发另一个抽象层是一项额外的工作.让ORM负责所有映射,状态跟踪,工作单元身份映射实现等.现代ORM知道如何处理所有这些问题.

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

你完全正确.

当一个实体可以映射到另一个实体而没有额外的依赖(例如存储库,服务,...)时,自动映射非常有用.

...我在哪里定义a Department和a 之间的映射DepartmentDataEntity

我会把它加入DepartmentRepository并添加方法IList<Department> FindByCompany(int companyId)以便检索公司的部门.

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

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

如果需要获取Department另一个实体的s 列表,则应添加一个新方法,DepartmentRepository并在需要的地方使用.

  • 我讨厌不同意,但我发现EF没有正确地映射到富域模型,但最好用贫穷的POCO类.与其他ORM一样,EF存在许多与业务规则冲突的限制,例如setter的可见性或构造函数的要求.在为业务规则和EF目的创建单个实体类的道路上,我会建议反对它.使用数据层模型和业务/域层模型,并根据需要在它们之间进行映射.这将从域逻辑中分离EF(ORM)特定代码,并将促进SRP.虽然没有减去. (14认同)
  • 我确实同意“ ORM带有许多与业务规则相冲突的限制”。尽管对于某些模型,这些限制并不重要,尤其是在团队纪律严明的情况下。每个决定都应基于上下文。 (2认同)

Leo*_*Leo 5

假设您有以下数据访问对象...

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

返回历史日志条目数据实体.该数据实体定义如下......

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; }
}
Run Code Online (Sandbox Code Playgroud)

您可以看到该属性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
            };
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是它.所有依赖项都在一个地方.然后,当从业务逻辑层调用数据对象时,我会让LINQ其余的...

var da = new AssetDA();
var entry =  da.GetHistoryRecord(1234);
var domainModelEntry = entry.FromDataObject();
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为你在这里稍微过分强调_coupling_的想法 - 无论如何你的基础设施和你的域都是"耦合的"(也在你的示例代码中).只要映射代码驻留在一个不是域模型而不是实体的位置,一切都很好. (3认同)
  • 我很想做这样的事情,但后来我的域模型与我的基础设施层相结合. (2认同)

Ron*_*ald 5

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

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