Linq查询超时,如何简化查询

时间:2016-11-17 20:22:00

标签: c# .net entity-framework linq

我们的前端用户界面有一个过滤系统,在后端运行数百万行。它使用一个在逻辑过程中构建的IQueryable,然后一次执行。每个单独的UI组件都进行AND运算(例如,Dropdown1和Dropdown2将仅返回具有两个共同选择内容的行)。这不是问题。但是,Dropdown3中有两种类型的数据,并且检查的项目需要一起进行ORd,然后与查询的其余部分进行AND运算。

由于它运行的行数很多,因此可以保持超时。由于需要进行一些额外的连接,因此有点棘手。这是我的代码,替换了表名:

//The end list has driver ids in it--but the data comes from two different places. Build a list of all the driver ids.
driverIds = db.CarDriversManyToManyTable.Where(
                        cd =>
                            filter.CarIds.Contains(cd.CarId) && //get driver IDs for each car ID listed in filter object
                            ).Select(cd => cd.DriverId).Distinct().ToList();

driverIds = driverIds.Concat(
                    db.DriverShopManyToManyTable.Where(ds => filter.ShopIds.Contains(ds.ShopId)) //Get driver IDs for each Shop listed in filter object
                        .Select(ds => ds.DriverId)
                        .Distinct()).Distinct().ToList();
//Now we have a list solely of driver IDs

//The query operates over the Driver table. The query is built up like this for each item in the UI. Changing from Linq is not an option.
query = query.Where(d => driverIds.Contains(d.Id));

如何简化此查询,以便我不必将成千上万的ID检索到内存中,然后将它们反馈回SQL?

3 个答案:

答案 0 :(得分:3)

有几种方法可以生成单个SQL查询。他们需要保留IQueryable<T>类型查询的所有部分,即不要使用ToListToArrayAsEnumerable等强制执行和评估它们的方法在记忆中。

一种方法是创建包含已过滤的ID的Union查询(根据定义将是唯一的),并使用join运算符将其应用于主查询:

var driverIdFilter1 = db.CarDriversManyToManyTable
    .Where(cd => filter.CarIds.Contains(cd.CarId))
    .Select(cd => cd.DriverId);
var driverIdFilter2 = db.DriverShopManyToManyTable
    .Where(ds => filter.ShopIds.Contains(ds.ShopId))
    .Select(ds => ds.DriverId);
var driverIdFilter = driverIdFilter1.Union(driverIdFilter2);
query = query.Join(driverIdFilter, d => d.Id, id => id, (d, id) => d);

另一种方法是使用两个基于OR {ed Any的条件,这些条件将转换为EXISTS(...) OR EXISTS(...) SQL查询过滤器:

query = query.Where(d =>
    db.CarDriversManyToManyTable.Any(cd => d.Id == cd.DriverId && filter.CarIds.Contains(cd.CarId))
    ||
    db.DriverShopManyToManyTable.Any(ds => d.Id == ds.DriverId && filter.ShopIds.Contains(ds.ShopId))
);

你可以尝试看看哪一个表现更好。

答案 1 :(得分:1)

这个问题的答案很复杂,并且有许多方面,在您的特定情况下,这些方面可能会或可能不会有所帮助。

首先,考虑使用分页。 .Skip(PageNum * PageSize).Take(PageSize)我怀疑您的用户需要在前端同时看到数百万行。只显示它们100,或者其他任何较小的数字对你来说都是合理的。

您已经提到需要使用联接来获取所需的数据。这些连接可以在形成IQueryable(实体框架)时完成,而不是在内存中(linq到对象)。阅读linq中的连接语法。

但是 - 在LINQ中执行显式连接并不是最佳实践,尤其是在您自己设计数据库的情况下。如果您正在执行数据库第一代实体,请考虑在表上放置外键约束。这将允许数据库优先实体生成选择它们并为您提供导航属性,这将极大地简化您的代码。

但是,如果您对数据库设计没有任何控制或影响,那么我建议您首先在SQL中构建查询以查看它的执行情况。在那里进行优化,直到获得所需的性能,然后将其转换为实体框架linq查询,该查询使用显式连接作为最后的手段。

要加快此类查询速度,您可能需要对所有&#34;键&#34;进行索引编制。您要加入的列。找出提高性能所需索引的最佳方法是,获取EF linq生成的SQL查询并将其转移到SQL Server Management Studio。从那里,更新生成的SQL,为您的@p参数提供一些预定义值,只是为了做一个例子。完成此操作后,右键单击查询并使用显示估计执行计划或包括实际执行计划。如果索引可以提高您的查询性能,那么此功能很可能会告诉您它,甚至为您提供脚本来创建您需要的索引。

答案 2 :(得分:1)

在我看来,使用LINQ扩展的实例版本会在完成之前创建多个集合。使用from语句版本应该减少相当多:

process.env.PWD

同样使用groupby扩展名会比查询每个驱动程序ID提供更好的性能。