繁体   English   中英

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

[英]Linq query timing out, how to streamline query

我们的前端UI具有一个过滤系统,该系统在后端可处理数百万行。 它使用一个在逻辑过程中建立的IQueryable,然后立即执行所有操作。 每个单独的UI组件都进行“与”运算(例如,Dropdown1和Dropdown2将仅返回具有相同选择的行)。 这不是问题。 但是,Dropdown3中包含两种类型的数据,并且需要对选中的项进行“或”运算,然后与其余查询进行“与”运算。

由于要进行大量操作,因此会一直超时。 由于还需要进行其他一些联接,因此有些棘手。 这是我的代码,替换了表名:

//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?

产生单个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))
);

您可以尝试看看哪个效果更好。

这个问题的答案很复杂,有很多方面,个别情况可能会也可能不会有帮助。

首先,考虑使用分页。 .Skip(PageNum * PageSize).Take(PageSize)我怀疑您的用户需要一次在前端看到数百万行。 只给他们看100个,或者对您来说合理的其他较小数字。

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

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

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

为了加快此类查询的速度,您可能需要对要加入的所有“关键”列进行索引。 找出需要哪些索引以提高性能的最佳方法,使用EF linq生成的SQL查询,并将其带入SQL Server Management Studio。 从那里开始,更新生成的SQL,以为您的@p参数提供一些预定义的值,仅作为示例。 完成此操作后,右键单击查询,然后使用显示估算的执行计划或包括实际的执行计划。 如果建立索引可以改善查询性能,则很有可能该功能将告诉您有关该索引的信息,甚至可以为您提供创建所需索引的脚本。

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

driveIds = (from var record in db.CarDriversManyToManyTable
            where filter.CarIds.Contains(record.CarId)
            select record.DriverId).Concat
            (from var record in db.DriverShopManyToManyTable
             where filter.ShopIds.Contains(record.ShopId)
             select record.DriverId).Distinct()

与查询每个驱动程序ID相比,使用groupby扩展还将提供更好的性能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM