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

时间:2014-01-06 07:13:00

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

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

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

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

EntityFrameworkRepository类使用两种泛型类型:

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

定义了两种抽象方法:

  • 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组成。如果我想在获取CompanyRepository时急切地从Company加载这些内容,那么我将在哪里定义DepartmentDepartmentDataEntity之间的映射?

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

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

6 个答案:

答案 0 :(得分:28)

  

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

如果您使用Entity Framework,它可以映射Rich Domain Model本身。

我最近回答了类似的问题"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<Department> FindByCompany(int companyId),以便审核公司的部门。

  

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

     

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

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

答案 1 :(得分: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;
    }
}

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

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

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

答案 2 :(得分:4)

使用实体框架,跨越所有层IMHO,从实体模型转换为其他形式的模型(域模型,值对象,视图模型等)通常是一个坏主意,反之亦然,除了仅在应用程序层中因为你将失去很多只能通过实体对象实现的EF功能,比如丢失变更跟踪和丢失LINQ可查询。

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

答案 3 :(得分:3)

我喜欢使用自定义的Extension方法来实现Entity和Domain对象之间的映射。

  • 您可以轻松地为其他实体调用扩展方法 在一个包含的实体内。
  • 你可以轻松应对 通过创建IEnumerable&lt;&gt;创建集合扩展。

一个简单的例子:

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 :(得分:2)

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

  1. 您可以在映射中指定基本类型,并定义它如何填充目标类型
  2. 在需要映射的地方,只需调用Mapper.Map(baseTypeObject,DestinationTypeObject)
  3. Automapper应该处理其余的
  4. 如果我理解你的问题,这可以解决问题。

答案 5 :(得分:0)

我不想手动映射,以便我使用http://valueinjecter.codeplex.com/

进行映射