使用Automapper优化相关子查询

时间:2018-09-17 13:10:04

标签: c# linq entity-framework-core automapper

更新: 自动映射器already adds a ToList()在简单的情况下会自动应用。我所看到的导致我提出这个问题的问题原来是一个更为复杂的问题(SoftwareIds memberN+1的元凶。请参阅this。)


在EF Core 2.1中,我们获得了在LINQ子查询上添加ToList()的支持,以缓冲结果并避免进行N + 1个数据库查询。 (Docs)在针对DbContext的普通LINQ查询上非常有用。

但是,如果我有一个自动映射程序配置文件,该配置文件会导致N + 1次查询:

    public MyMappingProfile() =>
        CreateMap<MyEntity, MyDto>().ForMember(e => e.MyCollectionProp, o => o.MapFrom(l => l.MyCollectionPropMany.Select(la => la.MyCollectionEntity)))

添加ToList()会引发异常:

    public MyMappingProfile() =>
        CreateMap<MyEntity, MyDto>().ForMember(e => e.MyCollectionProp, o => o.MapFrom(l => l.MyCollectionPropMany.Select(la => la.MyCollectionEntity).ToList()))
  

System.NotSupportedException:'无法解析表达式   'MyDto.MyCollectionPropMany.Select(la =>   la.MyCollectionEntity).ToList()':方法的重载   当前不支持“ System.Linq.Enumerable.ToList”。

是否可以在Automapper配置文件中启用子查询缓冲?

型号:

public class MyEntity
{
    public int Id { get; set; }
    public ICollection<MyCollectionPropMany> MyCollectionPropManys { get; set; }
    ...
}

public class MyCollectionPropMany
{
    public int MyEntityId { get; set; }
    public MyEntity MyEntity { get; set; }
    public int MyCollectionPropId { get; set; }
    public MyCollectionProp MyCollectionProp { get; set; }
}

public class MyCollectionProp
{
    public int Id { get; set; }
    public ICollection<MyCollectionPropMany> MyCollectionPropManys { get; set; }
    ...
}

public class MyDto
{
    public int Id { get; set; }
    public IEnumerable<MyCollectionPropDto> MyCollectionPropDtos { get; set; }
    ...
}

public class MyCollectionPropDto
{
    public string Name { get; set; }
    ...
}

Automapper v7.0.1

真实的场景(我试图简化/使SO通用):Source在此真实示例中,目前正在通过多对多的方式生成LanguagesTags成员N + 1个查询。

1 个答案:

答案 0 :(得分:2)

事实证明,当映射可枚举类型时,AutoMapper有时有时自动向投影表达式添加ToList / ToArray

规则似乎如下。如果可以从源表达式类型直接分配目标可枚举类型,则AutoMapper将直接使用源表达式。换句话说,如果以下分配有效(伪代码):

dst.Member = src.Expression;

在这种情况下,由您自己决定是否在映射表达式中包含ToList(因此可以选择加入EF Core相关查询优化)。

在所有其他情况下,AutoMapper会根据需要执行可枚举的元素映射,然后添加ToArrayToList。无法选择退出。

简而言之,如果目标可枚举元素类型为Dto(需要映射),则不要在源LINQ表达式中包含ToList(如果它是原始类型或实体类型), 包括ToList可以避免N + 1个查询。如果目标集合类型为IEnumerable<T>,则所有这些都适用。其他任何派生的集合类型,例如IReadOnlyCollection<T>IReadOnlyList<T>ICollection<T>IList<T>List<T>T[]等,都会由AutoMapper自动处理源表达式返回IEnumerable<TSource>的情况。