34

我的存儲庫處理併爲豐富的域模型提供持久性。我不想將貧血的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。如果我想在獲取Company時從CompanyRepository急切加載這些文件,那麼我將在哪裏定義DepartmentDepartmentDataEntity之間的映射?

我可以在CompanyRepository中提供更多的映射方法,但這很快就會變得混亂。系統中很快會出現重複的映射方法。

什麼是更好的方法來解決上述問題?

+0

1.根據企業經營理念stricly定義的公司。 2.對實體用例進行建模3.請注意,如果執行得當,您將不會遇到現在存在的問題。並忽略任何與持久性相關的東西 – MikeSW

+2

_AutoMapper可用於相反的情況[...],但不能在創建域模型時使用._爲什麼不可能_both_方式? –

+1

@ThomasWeller,存在通過AutoMapper,Jimmmy博加德的創造者的答案 - [用於AutoMapper雙向映射的情況下(http://lostechies.com/jimmybogard/2009/09/18/the-case-for-雙向映射功能於automapper /)。 –

回答

22

我的存儲庫處理併爲豐富的域模型提供持久性。我不想將貧血的Entity Framework數據實體暴露給我的業務層,所以我需要在它們之間進行映射。

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

最近我已經回答了類似問題"Advice on mapping of entities to domain objects"

我一直在使用NHibernate,並知道在實體框架中,您還可以指定從數據庫表到您的POCO對象的映射規則。通過實體框架實體開發另一個抽象層是額外的工作。讓ORM負責所有mappings,狀態跟蹤,unit of workidentity map實現等。現代ORM知道如何處理所有這些問題。

AutoMapper可用於相反的情況(映射到數據實體),但不能在創建域模型時使用。

你是完全正確的。

當一個實體可以映射到另一個實體而無需其他依賴關係(例如存儲庫,服務等)時,Automapper非常有用。

...我在哪裏定義DepartmentDepartmentDataEntity之間的映射?

我會把它放入DepartmentRepository並加入方法IList<Department> FindByCompany(int companyId)以便檢索公司的部門。

我可以在CompanyRepository中提供更多的映射方法,但這很快就會變得混亂。系統中很快會出現重複的映射方法。

什麼是對上述問題的一個更好的辦法?

如果需要得到的Department榜單的另一個實體,一種新的方法應加DepartmentRepository,只是用在需要的地方。

+9

我討厭不同意,但我發現EF沒有正確映射到豐富域模型,但最適合貧血POCO類。與其他ORM一樣,EF也帶有許多與業務規則相沖突的限制,例如設置者的可見性或對構造者的要求。爲了創建單一的實體類以實現業務規則和EF目的,我建議不要這樣做。使用數據層模型和業務/域層模型,並根據需要在它們之間進行映射。這會將EF(ORM)特定的代碼從域邏輯中分離出來,並將促進SRP。沒有減去。 –

+1

我確實同意「ORMs帶有許多與業務規則衝突的限制」。儘管對於某些模型來說,這些限制並不重要,特別是當你的團隊受到紀律處分時。每個決定都應該基於上下文。 –

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。現在,在我的項目中,我需要將這些數據實體映射到我的業務邏輯中使用的Domain模型對象。爲了完成映射,我定義了擴展方法......我知道這是一種反模式,因爲它是語言特定的,但好處是它隔離並打破了彼此之間的依賴關係......是的,這就是它的美妙之處。所以,映射器定義如下...

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

我很想做這樣的事情,但是我的領域模型與我的基礎架構層相結合。 – davenewza

+2

我認爲你在這裏稍微強調_coupling_的想法 - 無論如何你的基礎設施和域名都是「耦合」的(在你的示例代碼中)。只要映射代碼駐留在一個不是域模型而不是實體的位置,一切都很好。 –

+1

@davenewza您的域模型如何與您的基礎架構層耦合?領域模型是調用方法還是實例化基礎架構層的具體對象?我還沒有提出這樣的建議......我在談論域模型和數據實體 – Leo

3

我喜歡使用定製的擴展方法做實體和域對象之間的映射。

  • 當其他實體在 位於包含實體內時,您可以輕鬆調用其他實體的擴展方法。
  • 通過創建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 
     } 

並不完美,但很容易跟蹤和很容易重用。

2

隨着實體框架,跨所有層IMHO,它一般是一個壞主意從實體模型轉換到模型其它形式(域模型,值對象,視圖模型等),反之亦然,除了僅在該應用程序層,因爲您將失去許多隻能通過實體對象實現的EF功能,例如失去更改跟蹤和丟失LINQ可查詢。

這是更好地做你的資料庫層和應用層之間的映射。將實體模型保存在存儲庫層中。

1

像以前的帖子說。可能最好等到倉庫後才能進行實際映射。但是我喜歡使用自動映射器。它提供了一種將對象映射到其他對象的非常簡單的方法。對於一些問題的分離,您也可以在單獨的項目中定義映射。這些映射也是通用/類型爲基礎的:

  1. 您指定映射中的基本類型,並把它定義如何填充目標類型
  2. 在您需要的映射,你只需要調用Mapper.Map(baseTypeObject, DestinationTypeObject)
  3. Automapper應該處理剩下的

這可能做的伎倆,如果我理解正確你的問題。