2013-03-23 53 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