使用大型记录集优化实体框架查询

时间:2016-02-09 17:46:01

标签: c# asp.net-mvc entity-framework-6

我需要优化以下EF查询,因为最初我将从CustomerWidgets表中恢复所有内容。现在该表有100,000行,它开始变慢。我正在查询sql server dbase。

我想我不需要先把所有东西都带回去,那么修改下面代码的最佳方法是什么?

        public List<CustomerWidget> SearchWidgets(string surname, string firstname, string ZipCode)
    {
        // i don't need to bring back everything here!!  optimize it I say!!
        var widgetSearchResults = _context.CustomerWidgets.Where(x => x.IsDeleted == false).ToList();

        if (!string.IsNullOrEmpty(surname))
        {
            widgetSearchResults =
                widgetSearchResults.Where(x => x.Surname.ToUpper().Contains(surname.ToUpper())).ToList();
        }


        if (!string.IsNullOrEmpty(firstname))
        {
            widgetSearchResults =
                widgetSearchResults.Where(x => x.Forename.ToUpper().Contains(firstname.ToUpper())).ToList();
        }

        if (!string.IsNullOrEmpty(ZipCode))
        {
            widgetSearchResults =
                widgetSearchResults.Where(
                    x => x.ZipCode.Replace(" ", "").ToUpper().Contains(ZipCode.Replace(" ", "").ToUpper()))
                    .ToList();
        }

        return widgetSearchResults;
    }

1 个答案:

答案 0 :(得分:4)

仅在结尾处致电ToList(),并在之前使用IQueryable

public List<CustomerWidget> SearchWidgets(string surname, string firstname, string ZipCode)
{
    var widgetSearchResults = _context.CustomerWidgets.Where(x => x.IsDeleted == false);

    if (!string.IsNullOrEmpty(surname))
    {
        widgetSearchResults =
            widgetSearchResults.Where(x => x.Surname.ToUpper().Contains(surname.ToUpper()));
    }


    if (!string.IsNullOrEmpty(firstname))
    {
        widgetSearchResults =
            widgetSearchResults.Where(x => x.Forename.ToUpper().Contains(firstname.ToUpper()));
    }

    if (!string.IsNullOrEmpty(ZipCode))
    {
        widgetSearchResults =
            widgetSearchResults.Where(
                x => x.ZipCode.Replace(" ", "").ToUpper().Contains(ZipCode.Replace(" ", "").ToUpper()));
    }

    /********************
      Call ToList() only here
      *******************/

    return widgetSearchResults.ToList();
}

EF在从数据库中实际检索元素时构造整个SQL查询,因此使用您的方法,您首先检索:

SELECT * FROM CustomerWidgets WHERE IsDeleted = false

然后其余的查询将从内存列表(100.000项)中执行。

通过移除ToList()次调用直到最后,您使用IQueryable,只是添加Where子句,只有在您调用{{1}时才构造和检索所以你的SQL查询最终会像:

ToList()

哪个应该只检索您要查询的确切项目

对于额外的优化,您可能希望删除所有SELECT * FROM CustomerWidgets WHERE IsDeleted = false AND UPPER(Surname) LIKE UPPER('%Something%') AND UPPER(FirstName) LIKE UPPER('%Something%') AND UPPER(ZipCode) LIKE UPPER('%Something%') -- plus all the replacing 并在SQL服务器中设置不区分大小写的排序规则(名称中包含ToUpper的排序规则):具有正确的索引,这应该 MUCH 比在查询中将字符串转换为大写更快。

此外,您的CI替换(适用于ZipCode" ")最好退出""子句,以便您可以这样做:

Where

这仍然会在列tho上调用 if (!string.IsNullOrEmpty(ZipCode)) { ZipCode = ZipCode.Replace(" ", ""); widgetSearchResults = widgetSearchResults.Where( x => x.ZipCode.Replace(" ", "").ToUpper().Contains(ZipCode.ToUpper())); } SQL函数(不在REPLACE子句上)。你不能在那里替换任何东西:SQL Server检查额外的空白空间比为每一行调用一个函数要快。但是警告:这可能会改变功能:使用当前代码,“01 05”将匹配“0 105”;没有替换,“01 05”将匹配“01 05”。如果这对您不起作用,请保留替换。

PS:我没有测试你的查询是否会直接转换为SQL Server(如果不这样做可能会引发异常)......乍一看,我认为它们应该转换得很好,但如果他们没有,首先应该很容易弄清楚这些表达。

除此之外,如果您的应用程序允许,您可以使用分页(在Linq中使用whereSkip),如果您想一次检索更少的元素...但我没有看到你可以采取的优化措施。

回顾一下:

  1. 使用Take子句构造查询,而不是检索所有内容,然后在内存中过滤。这是通过在末尾调用WHERE来完成的。
  2. 删除所有不必要的每行功能(在本例中为ToList())。如果您可以更改列排序规则,SQL Server将执行不区分大小写的搜索。
  3. 无需在记录和查询子句上用ToUpper替换" "。如果你不这样做会更快。检查答案文本上的警告。
  4. 在SQL Server上创建正确的索引
  5. 如果这还不够,请使用分页,并且您的应用程序支持分页