使用Automapper优化相关子查询

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Automapper优化相关子查询相关的知识,希望对你有一定的参考价值。

更新:Automapper在简单的情况下自动应用它,因为它already adds a ToList()。我看到的这个问题导致我打开这个问题变成了一个更复杂的问题(SoftwareIds memberN+1的罪魁祸首。见this。)。


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

但是,如果我有一个Automapper配置文件导致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个查询。

答案

事实证明,AutoMapper有时会在映射可枚举类型时自动将ToList / ToArray添加到投影表达式,有时则不会。

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

dst.Member = src.Expression;

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

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

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

以上是关于使用Automapper优化相关子查询的主要内容,如果未能解决你的问题,请参考以下文章

查询优化。重复子查询

如何使用 AutoMapper 将父引用分配给子属性

INEXISTS的相关子查询用INNER JOIN 代替--性能优化

MySQL高级第八篇:关联查询子查询和排序相关优化

Oracle SQL如何优化IN不相关子查询

Oracle 优化——奇怪的执行计划左加入一个不相关的子查询