Automapper嵌套映射以及投影

时间:2017-12-21 10:11:13

标签: entity-framework automapper

关于我的博客数据模型:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Text { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; }
}

此外,相应的DTO以这种方式定义:

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<PostDto> Posts { get; set; }
}

public class PostDto
{
    public int Id { get; set; }
    public string Text { get; set; }
    public int BlogId { get; set; }

    public BlogDto Blog { get; set; }
}

然后,初始化映射:

Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<Blog, BlogDto>();
            cfg.CreateMap<Post, PostDto>();
        });

最后,我尝试使用Automapper.EF6将帖子投影到PostDtos:

List<PostDto> result = null;
using (var db = new BloggingContext())
{
    result = db.Set<Post>().ProjectToList<PostDto>();
}

但是我遇到了这个错误:

The type 'AutoMapperSample.Model.PostDto' appears in two structurally
incompatible initializations within a single LINQ to Entities query. A type
can be initialized in two places in the same query, but only if the same
properties are set in both places and those properties are set in the same
order.

这是生成的表达式:

.Call System.Linq.Queryable.Select(
.Call 

.Constant<System.Data.Entity.Core.Objects.ObjectQuery`1[AutoMapperSample.Model.Post]>(System.Data.Entity.Core.Objects.ObjectQuery`1[AutoMapperSample.Model.Post]).MergeAs(.Constant<System.Data.Entity.Core.Objects.MergeOption>(AppendOnly))
    ,
    '(.Lambda #Lambda1<System.Func`2[AutoMapperSample.Model.Post,AutoMapperSample.Model.PostDto]>))

.Lambda #Lambda1<System.Func`2[AutoMapperSample.Model.Post,AutoMapperSample.Model.PostDto]>(AutoMapperSample.Model.Post $dto)
{
    .New AutoMapperSample.Model.PostDto(){
        Id = $dto.Id,
        Text = $dto.Text,
        Blog = .If ($dto.Blog != null) {
            .New AutoMapperSample.Model.BlogDto(){
                Id = ($dto.Blog).Id,
                Name = ($dto.Blog).Name,
                Posts = .Call System.Linq.Enumerable.ToList(.Call System.Linq.Enumerable.Select(
                        ($dto.Blog).Posts,
                        .Lambda #Lambda2<System.Func`2[AutoMapperSample.Model.Post,AutoMapperSample.Model.PostDto]>))
            }
        } .Else {
            null
        }
    }
}

.Lambda #Lambda2<System.Func`2[AutoMapperSample.Model.Post,AutoMapperSample.Model.PostDto]>(AutoMapperSample.Model.Post $dto)
{
    .New AutoMapperSample.Model.PostDto(){
        Id = $dto.Id,
        Text = $dto.Text
    }
}

我需要知道结构是否错误,例如循环,还是需要考虑其他因素。

1 个答案:

答案 0 :(得分:1)

EF 6的预测有两个问题

1-您无法投影“设置为客户”本身。

例如

public class Customer
{
     public int Id {get;set;}
     public string Name {get;set;}
     [NotMapped]
     public int OrdersCount {get;set;}
     public List<Order> Orders {get;set;}
}

List<Customer> customers = await DbContext.Customers.Select(cust => 
     new Customer
     {
          Id = cust.Id,
          Name = cust.Name,
          OrdersCount = cust.Orders.Count
     }).ToListAsync(); // error!

2-您不能同时投影关系的两端


public class Customer // as like as previous example

public class Order
{
     public int Id {get;set;}
     public int CustomerId {get;set;}
     public Customer Customer {get;set;}
}

List<CustomerDto> customers = await DbContext.Customers.Select(cust => 
     new CustomerDto
     {
          Id = cust.Id,
          Name = cust.Name,
          OrdersCount = cust.Orders.Count,
          Orders = cust.Orders.Select(ord => new OrderDto {
               Id = ord.Id,
               CustomerId = ord.CustomerId,
               Customer = new CustomerDto { ... }
          }).ToList()
     }).ToListAsync(); // error!

第一个问题无法解决,但是对于第二个问题,您可以编写一种忽略关联一侧的解决方法(例如,在客户查询中返回带有订单的客户,但是在订单查询中不返回客户!)

bool MapperPropConfigurationCondition(PropertyMap p)
            {
                return (p.DestinationMember.GetCustomAttribute<ForeignKeyAttribute>() != null || p.DestinationMember.GetCustomAttribute<InversePropertyAttribute>() != null)
                       && !typeof(IEnumerable).IsAssignableFrom(p.DestinationMember.ReflectedType)
                       && typeof(IDto).IsAssignableFrom(p.DestinationMember.ReflectedType);
            }

            mapperConfigExpression.ForAllPropertyMaps(MapperPropConfigurationCondition, (p, member) =>
            {
                p.Ignored = true;
            });

最终解决方案:迁移到EF Core 5,它具有您需要的一切,而且一切顺利! ; D