1

我在我的 web api 项目中收到了一个 DTO,我想使用 AutoMapper 自动将我的 DTO 转换为我要插入数据库的实体。

这是 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 从数据库中获取部门的方法(使用 Entity Framework 5)。

使用自定义 ValueResolver 我可以将整数列表转换为部门列表,但我想从数据库中获取部门,而不是创建新部门。

这是我想出的解决方案,但我很确定有更好的方法来做到这一点:

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

...

有人可以帮我解决这个问题吗?

4

1 回答 1

5

使用 Automapper 从 DTO 数据中膨胀实体通常是一个坏主意。它非常适合朝着相反的方向前进——将数据从实体传递到视图模型、webapimodel 或一般的 DTO。但特别是对于 EntityFramework,在客户端到域的方向上使用它会变得混乱。

例如,您的实体上有 2 个属性不在您的视图模型上:idCreatedAt(为什么不一致的大小写顺便说一句?)。为了AutoMapper.Mapper.AssertConfigurationIsValid()不引发异常,这意味着CreateMap除了属性的忽略或自定义解析器之外,您还需要在调用中忽略或使用自定义解析器来处理这两个Departments属性。最后,唯一被自动映射的是name,这首先违背了使用 automapper 的目的。

您将 DTO 转换为实体的代码实际上非常简洁。老实说,我要改变的主要事情是删除自动映射器——在这种情况下,它真的没有必要。

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 多个其他属性,它们都是标量吗?它们中的任何一个是外键属性(用于管理非集合导航属性)吗?他们中有多少人会要求自定义解析器?

我绝对不会使用自动映射器来 DTO 集合导航属性 ( public virtual ICollection<SomeOtherEntity> OtherEntities { get; set; })。我也不会尝试将自动映射器用于不公开外键 ( public virtual SomeOtherEntity OtherEntity { get; set; }) 的 DTO 非集合导航属性。

这里的代码味道是,对于每个 DTO 到实体的CreateMap调用,您将至少有几个Ignores(忽略创建日期、最后修改日期等)。此外,如果您的非集合导航属性确实公开了外键属性,您可以自动映射 fk 属性并且它会起作用,但您最终会忽略实际的 ( virtual) 导航属性。

此外,当涉及到域代码时,即您的记录系统,它有助于在阅读时公开所有内容,而不是在 AutoMapper 后面隐藏一些细节。考虑以下内容——它更加明确,虽然有些冗长,但我认为这不一定是一件坏事,因为它在单个源文件中显示了所有域传输代码:

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

将您在此处的额外行数与您在引导程序中需要的所有额外行(忽略、自定义解析器等)进行比较CreateMap。最后是你的电话,希望这会有所帮助。

于 2013-03-23T06:57:58.187 回答