【问题标题】:How can I improve this queries speed?如何提高此查询速度?
【发布时间】:2015-10-27 09:09:55
【问题描述】:

我有一张包含客户列表的表格。 一位客户有 0 个、1 个或多个合同。

我必须检索所有启用的客户,将它们设置在 DTO 中并将当前合同添加到此 DTO(如果有的话)

目前很慢(超过10分钟)。

代码

List<CustomerOverviewDto> result = new List<CustomerOverviewDto>();    
customers= context.Customers.Where(c => c.IsEnabled).ToList();
    foreach (Customer customer in customers)
    {
        CustomerOverviewDto customerDto = GetCustomer(customer);
        Framework.Contract contract =
            customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today)
                .FirstOrDefault();
        if (contract != null)
        {
            SetContract(customerDto, contract);
        }
        result.add(customerDto);
    }

【问题讨论】:

  • 显示生成的 SQL 查询
  • 客户表包含 36 列(Varchar 和 Int),共有 14478 行。你真的需要列名吗?
  • 我想看看 ORM 是如何生成查询的,也许它以非常低效的方式进行。顺便确保您的查询没有被其他事务阻止
  • 你有关于 IsEnabled、ContractEndDate 和 ContractStartDate 的索引吗?另外,SetContract 是做什么的?这些信息对于理解查询效率低下的原因非常重要
  • @lad2025 我怎样才能看到这个生成的查询?在视觉工作室?

标签: c# sql performance linq


【解决方案1】:

使用投影仅返回您使用“选择”处理的列。如果您有 36 列,这将为您提供更好的结果。

customers= context.Customers.Where(c => c.IsEnabled).Select(cust => new Customer
{
    Id = cust .Id
}).ToList();

https://www.talksharp.com/entity-framework-projection-queries

如果您有表扫描或索引扫描,请检查查询计划。尽量通过设置适当的索引来避免它们。

【讨论】:

  • 我无法在第一个查询中创建 CustomerOverviewDTO,因为我需要检索不在 CustomerOverviewDTO 中的客户合同
  • 您无需更改客户实现,只需更改查询即可。查看我对示例的编辑。
【解决方案2】:

我认为问题在于在循环内检索合同的查询。最好用一个查询来检索所有数据,如下所示:

var date = DateTime.Today;
var query =
    from customer in context.Customers
    where customer => customer.IsEnabled
    select new 
    {
        customer,
        contract = customer.Contracts.FirstOrDefault(c => c.ContractEndDate >= date && c.ContractStartDate <= date)
    };
var result = new List<CustomerOverviewDto>();
foreach (var entry in query)
{
    CustomerOverviewDto customerDto = GetCustomer(entry.customer);
    if (entry.contract != null)
        SetContract(customerDto, entry.contract);
    result.add(customerDto);
}

【讨论】:

    【解决方案3】:

    好的,首先,当您使用 .ToList() 时,您正在那里执行查询,并将 IsEnabled 的每一行拉回内存中进行处理。您想在数据库方面做更多事情。

    result = context.Customers.Where(c => c.IsEnabled); //be lazy
    

    其次,查询只有在有索引可以使用的情况下才能很好地执行并被执行引擎正确优化。

    在您正在执行比较的字段上添加一些索引。

    以这行代码为例

        customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && 
    c.ContractStartDate <= DateTime.Today).FirstOrDefault();
    

    您是否没有从客户到合同的外键,并且您在 ContractStartDate 和 ContractEndDate 上没有索引,它的性能会非常差,并且将为每个“IsEnabled”的客户运行一次

    【讨论】:

    • 他遍历customers,而不是result。在这个 sn-p 中,懒惰不会给他带来任何好处。
    • 好吧,好点,但结果仍然包含每一行(工作已完成,数据由 .toList() 返回到变量),我们也不知道客户是如何填充的
    • 是的。但是,OP 写道:“......我必须检索所有启用的客户......”没有办法避免加载作为输出一部分的所有数据。使用急切加载可能很有用,但我们需要更多信息才能给出有用的答案。
    • 是的,我同意,我们需要看看客户是如何填充的。很好看,我要去抽烟了!
    • 呃他的帖子刚刚改变了客户的结果
    【解决方案4】:

    似乎你只想在返回值时做一些事情。因此,您可以在初始查询中添加它,并包含合同:

    customers= context.Customers
                               .Include(c => c.Contracts)
                               .Where(c => c.IsEnabled
                                         && c.Contracts.Any(con => con.ContractEndDate >= DateTime.Today && con .ContractStartDate <= DateTime.Today))
                               .ToList();
    
     foreach (Customer customer in customers)  
     {
        CustomerOverviewDto customerDto = GetCustomer(customer);
        Framework.Contract contract =
        customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today)
            .First();
        SetContract(customerDto, contract);
     }
    

    【讨论】:

    • 错了。即使他们没有当前合同,我也必须检索所有客户
    • 这在您的代码中并不清楚,但是当您包含合同时,它会减少到您的数据库的往返次数。
    • 我不能在 where 之后调用 Include
    【解决方案5】:

    由于我不知道您的域模型结构是什么样的,或者您为什么不使用导航属性将 CURRENT 合同映射到客户,您可以这样做。

    通过具体化所有客户和合同,然后将它们在内存中映射到您的 DTO 对象,您只需对数据库进行 2 次往返即可。假设您将 CustomerId 作为 FK,将 Customer.Id 作为 PK。

    List<CustomerOverviewDto> result = new List<CustomerOverviewDto>();    
    
    customers = context.Customers.Where(c => c.IsEnabled).ToList();
    contracts = context.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today).ToList();
    
    foreach (Customer customer in customers)
    {
        var customerDto = GetCustomer(customer);
        var contract = contracts.Where(c => c.CustomerId == customer.Id).FirstOrDefault();
        if (contract != null)
        {
            SetContract(customerDto, contract);
        }
    
        result.add(customerDto);
    }
    

    【讨论】:

      【解决方案6】:

      我终于通过使用1个查询和投影解决了这个问题

      context.Customers.Where(c => c.IsEnabled).Select(c => new CustomerOverviewDto{...}).ToList();
      

      我在创建 CustomerOverviewDto 时直接检索合同

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-29
        • 1970-01-01
        • 2019-10-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多