【问题标题】:How to "inflate" entity with AutoMapper如何使用 AutoMapper “膨胀”实体
【发布时间】:2013-03-24 22:15:02
【问题描述】:

我在我的 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");

...

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

【问题讨论】:

    标签: c# entity-framework asp.net-web-api automapper


    【解决方案1】:

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

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

    您将 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 与您将用于将 Registration 实体添加到 (dbContext.Registrations.Add(reg)) 并调用 SaveChangesDbContext 不同。当您有附加到不同上下文的实体时,您的数据库中最终会出现重复的 Department 实体(或者由于重复的主键可能导致 SQL 异常)。

    更新

    我选择 AutoMapper 是因为我的实体和 DTO 都有 15 个以上的字段, 两者之间的唯一区别是数据库特定的东西 我的实体有,如 id、创建日期、最后修改日期等。 在这种情况下,您是否会保留不使用 AutoMapper 的建议 考虑到我的实体比简化大得多 我在这里发帖?

    这取决于。对于您的 15 多个其他属性,它们都是标量吗?它们中的任何一个是外键属性(暴露于管理非集合导航属性)吗?其中有多少人会要求自定义解析器?

    我绝对不会将自动映射器用于 DTO 集合导航属性 (public virtual ICollection&lt;SomeOtherEntity&gt; OtherEntities { get; set; })。我也不会尝试将 automapper 用于不公开外键 (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 引导程序中需要的所有额外行(忽略、自定义解析器等)的额外行数。最后是你的电话,希望这会有所帮助。

    【讨论】:

    • 感谢您的帮助!我选择 AutoMapper 是因为我的实体和 DTO 都有 15 个以上的字段,两者之间的唯一区别是我的实体具有的数据库特定的东西,比如 id、创建日期、最后修改日期等。你会保持你不使用的建议吗AutoMapper 在这种情况下考虑到我的实体比我在这里发布的简化要大得多?附言。不一致的大小写是一个错字:P
    • 我在回答中回复了您的评论。
    • 谢谢你的更新,我明白你的意思了。所有的属性都是标量的,是的,它们都 1:1 映射到实体模型,不需要自定义解析器或任何东西。我之前为接受 DTO 的模型实体设置了构造函数,类似于您的建议,但选择了 AutoMapper,因为这是我正在使用的遗留系统,并且有几百个实体要处理。
    • 那么听起来你明白了。使用 AM,只是不要尝试自定义解析您的导航属性、集合或其他。在自动映射器代码之外使用您的 DbContext 手动执行此操作(只要您验证它们,就可以自动映射公开的 fk 属性)。
    猜你喜欢
    • 1970-01-01
    • 2012-09-29
    • 2020-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-08
    相关资源
    最近更新 更多