我们的前端用户界面有一个过滤系统,在后端运行数百万行。它使用一个在逻辑过程中构建的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?
答案 0 :(得分:3)
有几种方法可以生成单个SQL查询。他们需要保留IQueryable<T>
类型查询的所有部分,即不要使用ToList
,ToArray
,AsEnumerable
等强制执行和评估它们的方法在记忆中。
一种方法是创建包含已过滤的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提供更好的性能。