【问题标题】:Automapper first IQueryable.ProjectTo projection is too slowAutomapper 先 IQueryable.ProjectTo 投影太慢
【发布时间】:2018-02-09 07:30:23
【问题描述】:

我有 Dto 课程:

public class PackageDto
{
    public MemberDto Receiver { get; set; }
    public MemberDto Sender { get; set; }
    public DocflowDto Docflow { get; set; }

    public DocflowTypeOption DocflowTypeId { get; set; }
    public string Code { get; set; }
    public Guid? DocGuid { get; set; }
}

public class LightPackageDto
{
    public DocflowDto Docflow { get; set; }
    public DocflowTypeOption DocflowTypeId { get; set; }
    public string Code { get; set; }
    public Guid? DocGuid { get; set; }        
}

public class MemberDto
{
    public string Prop1 { get; set; }
    public EntityTypeOption Prop2 { get; set; }
    public string Prop3 { get; set; }
    public string Prop4 { get; set; }
    public string Prop5 { get; set; }
    public PersonDto Director { get; set; }
    public string CompanyShortName { get; set; }
    public string CompanyFullName { get; set; }
    public string CompanyPrefix { get; set; }
}

DocflowDto 具有 3 个简单类型属性,通过展平进行投影。 PersonDto 有 3 个简单类型属性,也可以通过扁平化来投影

映射规则:

        CreateMap<Person, PersonDto>();
        CreateMap<Member, MemberDto>()
            .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => src.Employee.Prop1))
            .ForMember(dest => dest.Prop2, opt => opt.MapFrom(src => src.Prop2))
            .ForMember(dest => dest.Prop3, opt => opt.MapFrom(src => src.Employee.Prop3))
            .ForMember(dest => dest.Prop4, opt => opt.MapFrom(src => src.Employee.Prop4))
            .ForMember(dest => dest.Prop5, opt => opt.MapFrom(src => src.Employee.Prop5))
            .ForMember(dest => dest.Director, opt => opt.MapFrom(src => src.Employee.Director))
            .ForMember(dest => dest.CompanyShortName, opt => opt.MapFrom(src => src.Company.CompanyShortName))
            .ForMember(dest => dest.CompanyFullName, opt => opt.MapFrom(src => src.Company.CompanyFullName))
            .ForMember(dest => dest.CompanyPrefix, opt => opt.MapFrom(src => src.Company.Prefix))
            ;
        CreateMap<Data.Models.Docflow.Package, PackageDto>()
            .ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.Sender))
            .ForMember(dest => dest.Receiver, opt => opt.MapFrom(src => src.Receiver))
            .ForMember(dest => dest.Docflow, opt => opt.MapFrom(src => src.Docflow))
            .ForMember(dest => dest.DocGuid, opt => opt.MapFrom(src => src.Files.FirstOrDefault().DocGuid))
            ;



        CreateMap<Data.Models.Docflow.Package, LightPackageDto>()
            .ForMember(dest => dest.Docflow, opt => opt.MapFrom(src => src.Docflow))
            .ForMember(dest => dest.DocGuid, opt => opt.MapFrom(src => src.Files.FirstOrDefault().DocGuid));

然后测试:

//fast
        var lightEntity = await _db.Packages.Where(p => p.PackageGuid == packageGuid).ProjectTo<LightPackageDto>().FirstOrDefaultAsync();

        //fast (_db.GetPackage make query with all necessary includes)
        var entity = await _db.GetPackage(packageGuid);
        var entityDto = AutoMapper.Mapper.Map<Package, PackageDto>(entity);

        //first run too slow
        var result = await _db.Packages.AsNoTracking().Where(p => p.PackageGuid == packageGuid).ProjectTo<PackageDto>().FirstOrDefaultAsync();

1) 所以如果我投影到 LightPackageDto(没有成员投影)就可以了。

2) 如果我从 EF 手动返回包含包含的实体,然后我为“内存中”实体进行映射也可以

3) 如果我想要 Automapper 应用 ProjectTo 进行“选择”查询,我总是会冻结大约 2-3 秒。但是第一次冻结只出现一次。为什么?

还有其他问题。我在结果 sql select 语句中看到所有相关表的所有列。我应该只看到目标投影所需的列。为什么都在那里?

【问题讨论】:

  • 尝试MyGet 构建。
  • 我试过了。保存了不正确的行为((
  • 感谢您的复制。我认为 AM 生成的表达式没有任何问题。但我看到很多外部连接。所以我猜在你的应用程序中,时间花在从数据库中获取东西上。
  • this。大多数事情在第一次运行时都比较慢。 AM、EF 和 SQL Server 都会为第一次调用做额外的事情。在这种情况下,我会说它是 EF 和 SQL,但您必须对其进行分析才能确定。在您的复制中,它正在创建数据库。在现实生活中,这将是查询,因为所有这些外部连接。但你需要衡量。
  • @VitalyMosin 你找到这个问题的根源了吗?我遇到了同样的问题。第一次拨打ProjectTo 非常慢。查询本身很快,只有在我添加映射后它才会变慢。我也渴望在应用程序启动时编译我的映射,但这并没有帮助。

标签: performance entity-framework automapper


【解决方案1】:

这只是一些愚蠢的错误。它不是缓存或任何东西。因为触摸预期的投影并取消任务足以“清理路径”(解决方案1)。或者(有时)在获取第一个元素之前将查询转换为列表也可以(解决方案 2)。

解决方案 1:

每次都为我工作。 (请注意,当CancelAfter 方法被触发时,时间(在本例中为 1000 毫秒)开始计算。因此,几乎每次调试都会跳转到 catch 块。)

CancellationTokenSource cancelationTokenSource  = new CancellationTokenSource();
cancelationTokenSource.CancelAfter(1000);
PackageDto result = null;
 
try
 {
   result = await _db.Packages.AsNoTracking()
   .Where(p => p.PackageGuid == packageGuid).ProjectTo<PackageDto>()
   .FirstOrDefaultAsync(cancelationTokenSource.Token);//cancel this, when taking longer than 1s
 }
 catch (OperationCanceledException)
 { //do absolutely the same thing, but now it will be fast... 
  result = await _db.Packages.AsNoTracking()
   .Where(p => p.PackageGuid == packageGuid).ProjectTo<PackageDto>()
   .FirstOrDefaultAsync();
 }

解决方案 2:

!!!不能始终如一地工作。需要更多调查

PackageDto result = await _db.Packages.AsNoTracking()
  .Where(p => p.PackageGuid == packageGuid).ProjectTo<PackageDto>()
  .ToList().FirstOrDefault();//converting to list for some reason helps here..

是的。不是最漂亮的解决方案,但比等待 25 秒要好...

(顺便说一句 .net6,Automapper 10.1.1,AutoMapper.Collection.EntityFrameworkCore 7.1.3)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    • 1970-01-01
    相关资源
    最近更新 更多