如何使用AutoMapper"膨胀"实体

Ada*_*ada 1 c# entity-framework automapper asp.net-web-api

我在我的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;
}
Run Code Online (Sandbox Code Playgroud)

问题是RegistrationDTO只有部门的ID,我找不到让AutoMapper从数据库中获取部门的方法(使用Entity Framework 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");

...
Run Code Online (Sandbox Code Playgroud)

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

dan*_*wig 5

使用Automapper从DTO数据中膨胀实体通常是个坏主意.这是相反的方向 - 通常将数据从实体传递到视图模型,webapimodel或DTO.但特别是对于EntityFramework,在Client-to-Domain方向使用它可能会变得混乱.

例如,您的实体上有2个属性不在您的viewmodel上:idCreatedAt(为什么不一致的外壳btw?).为了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");
Run Code Online (Sandbox Code Playgroud)

你可能想尝试这样的事情:

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;
   }))
;
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为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用于不公开外键(public virtual SomeOtherEntity OtherEntity { get; set; })的DTO非集合导航属性.

这里的代码气味是,对于每个DTO到实体的CreateMap调用,您将至少有几个Ignores(忽略创建日期,最后修改日期等).此外,如果您的非收集导航属性确实公开了外键属性,您可以自动执行fk属性并且它将起作用,但您最终会对actual(virtual)nav属性进行另一次忽略.

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

var reg = new Registration
{
    name = dto.name,
    prop1 = dto.prop1,
    prop2 = dto.prop2,
    ...
    propN = dto.propN
};
Run Code Online (Sandbox Code Playgroud)

比较一下你在CreateMap引导程序中需要的所有额外行(忽略,自定义解析器等)所需的额外行数.最后这是你的电话,希望这会有所帮助.