2013-03-23 52 views
1

我在我的web api項目中收到一個DTO,我想使用AutoMapper將我的DTO自動轉換爲我要插入數據庫的實體。如何使用AutoMapper「膨脹」實體

這裏是DTO的簡化和實體:

class RegistrationDTO 
{ 
    string name; 
    ICollection<int> Departments; 
} 

class Registration 
{ 
    int id; 
    DateTime CreatedAt; 
    string name; 
    virtual ICollection<Department> Departments; 
} 

class Department 
{ 
    int id; 
    string name; 
    virtual ICollection<Registration> Registrations; 
} 

的問題是,RegistrationDTO只有各部門的ID,我不能找到一個辦法讓AutoMapper從中獲取部門數據庫(使用實體框架5)。

使用一個自定義的ValueResolver我可以將一個int列表轉換爲一個Departments列表,但我想從數據庫中獲取Departments,而不是創建新的Departments。

這是我想出了一個解決方案,但我敢肯定,有一個更好的方式來做到這一點:

var reg= Mapper.Map<Registration>(dto); 

reg.Departments = new List<int>(dto.Departments).ConvertAll(input => Context.Departments.Find(input)); 

if(reg.Departments.Contains(null)) //a department provided does not exist in the database 
    return Request.CreateResponse(HttpStatusCode.BadRequest, "invalid department"); 

... 

任何人都可以幫助我呢?

回答

5

使用Automapper從DTO數據中擴充實體通常是一個糟糕的主意。向相反的方向發展很好 - 將數據從實體傳遞給viewmodels,webapimodels或DTO。但特別是對於EntityFramework,在客戶端到域的方向使用它可能會變得混亂。

例如,你有你的實體2個屬性是不是在您的視圖模型:(?爲什麼外殼不一致BTW)idCreatedAt。爲了使AutoMapper.Mapper.AssertConfigurationIsValid()不引發異常,這意味着您需要忽略或使用您的CreateMap調用中這兩個屬性的自定義解析器,以及Departments屬性的忽略或自定義解析器。最終,自動映射的唯一東西是name,這首先破壞了使用automapper的目的。

將DTO轉換爲實體的代碼實際上非常簡潔。說實話,我想改變的重點是去掉automapper--在這種情況下,它並不是必需的。

var reg = new Registration { name = dto.name }; // less code than with automapper 

reg.Departments = new List<int>(dto.Departments) 
    .ConvertAll(input => Context.Departments.Find(input)); 

if(reg.Departments.Contains(null)) //a department provided does not exist in the database 
    return Request.CreateResponse(HttpStatusCode.BadRequest, "invalid department"); 

你可能會去嘗試,這樣的財產以後:

Mapper.CreateMap<RegistrationDTO, Registration>() 
    .ForMember(d => d.id, o => o.Ignore()) 
    .ForMember(d => d.CreatedAt, o => o.UseValue(DateTime.Now)) 
    .ForMember(d => d.Departments, o => o.MapFrom(s => 
    { 
     var dbContext = new MyDbContext(); 
     var departments = new List<int>(s.Departments) 
      .ConvertAll(input => dbContext.Departments.Find(input)); 
     return departments; 
    })) 
; 

這不會起作用,因爲在委託塊DbContext是不一樣的DbContext您將使用到Registration實體添加到(dbContext.Registrations.Add(reg))並調用SaveChanges。當您將實體連接到不同的上下文時,最終會在數據庫中出現重複的Department實體(或者由於重複的主鍵可能導致SQL異常)。

更新

我去AutoMapper,因爲我的兩個實體和DTO有超過15場,是數據庫具體的東西 我的實體擁有,喜歡的ID兩者之間 唯一的區別,建立日期,最後修改日期等。 考慮到我的實體比我在此處發佈的簡化 大很多,您是否會保持您在此情況下不使用AutoMapper的建議 ?

這取決於。對於你的15+其他屬性,它們都是標量嗎?他們中的任何一個是外鍵屬性(暴露於管理非集合導航屬性)?有多少人會要求自定義解析器?

我絕對不會使用automapper去DTO集合導航屬性(public virtual ICollection<SomeOtherEntity> OtherEntities { get; set; })。我也不會嘗試將automapper用於不公開外鍵的DTO非集合導航屬性(public virtual SomeOtherEntity OtherEntity { get; set; })。

這裏的代碼味道是,對於每個DTO實體CreateMap調用,您至少會有幾個Ignore(忽略創建日期,最後修改日期等)。此外,如果您的非集合導航屬性確實公開外鍵屬性,則可以自動映射fk屬性並且它將起作用,但最終會對實際(virtual)導航屬性產生另一個忽略。

此外,當涉及到域代碼時,這是您的記錄系統,它有助於在閱讀時將所有內容全部公開,而不是在AutoMapper後面隱藏一些細節。考慮以下 - 這是更明確的,雖然它是有點冗長,我不認爲這不一定是壞事,因爲它顯示了一個源文件所有域名轉移代碼:

var reg = new Registration 
{ 
    name = dto.name, 
    prop1 = dto.prop1, 
    prop2 = dto.prop2, 
    ... 
    propN = dto.propN 
}; 

比較一下你在這裏需要多少額外的行和所有額外的行(忽略,自定義解析器等),你需要在引導程序中使用CreateMap。最後這是你的電話,希望這有助於。

+0

感謝您的幫助!我去了AutoMapper,因爲我的實體和DTO都有15個字段,兩者之間的唯一區別就是我的實體具有的數據庫特定的東西,比如id,創建日期,最後修改日期等。您是否保留不使用在這種情況下,AutoMapper考慮到我的實體比我在這裏發佈的簡化大得多? PS。不一致的外殼是一個錯字:P – Adabada 2013-03-24 13:54:17

+0

我在回答中回覆了你的評論。 – danludwig 2013-03-24 14:15:11

+0

感謝您的更新夥計,我明白了你的觀點,這很有道理。所有的屬性都是標量的,是的,所有的屬性映射到實體模型1:1,不需要自定義的解析器或任何東西。我以前爲我的模型實體設置了接受DTO的構造函數,類似於您的建議,但是用於AutoMapper,因爲這是一個我正在使用的遺留系統,並且有幾百個實體需要處理。 – Adabada 2013-03-24 15:08:27