简化具有多个条件的复杂 linq 查询

本文关键字:复杂 linq 查询 条件 | 更新日期: 2023-09-27 18:36:47

最近我遇到了修复错误的代码,如下所示。只是很难理解它,并使更改看起来也有风险。有没有简单的方法来分解这样的查询,即德摩根定律或以不同的方式重写。

SomeReturnType xyz() 
{
    return (from m in this.context.tblMessages
            where m.SystemActionID.HasValue &&
            (userID == null || m.RecipientID == null || m.RecipientID == userID) &&
            m.TargetUserType == userType &&
            (
                 (territoryID.HasValue && !m.TerritoryID.HasValue) ||
                 (!territoryID.HasValue && !m.TerritoryID.HasValue) ||
                 (territoryID.HasValue && m.TerritoryID.HasValue && territoryID.Value == m.TerritoryID.Value)
            ) &&
            (
                  (regionID.HasValue && !m.RegionID.HasValue) ||
                  (!regionID.HasValue && !m.RegionID.HasValue) ||
                  (regionID.HasValue && m.RegionID.HasValue && regionID.Value == m.RegionID.Value)
            ) &&
            (
                  (teamID.HasValue && !m.TeamID.HasValue) ||
                  (!teamID.HasValue && !m.TeamID.HasValue) ||
                  (teamID.HasValue && m.TeamID.HasValue && teamID.Value == m.TeamID.Value)
            ) &&
            m.SystemActionData == additionalData &&
            (!completed.HasValue || m.IsSystemActionCompleted == completed.Value)
            select m).FirstOrDefault();
}  //function xyz ends

简化具有多个条件的复杂 linq 查询

首先,此代码看起来像是试图将错误的 SQL 代码移动到 LINQ:这些语句包含参数检查,以尝试创建可以具有"可选"参数的查询。看起来作者试图将存储过程移动到代码中。

这两件事都是非常糟糕的主意。

  • 首先,SQL Server 将根据查询的参数缓存查询的执行计划。这意味着该过程的第一次执行决定了所有后续调用的计划。"可选"参数通常会导致非最佳计划(例如,通过省略索引)
  • 存储过程是一个抽象层,它隐藏了复杂的查询,允许您使用产品的完整 SQL 语法。将它们移动到代码只会让事情变得更难,而不是更容易。
  • 最后,LINQ 不需要"可选"参数。您可以编写查询,当您将 IQueryable 转换为 IEnumerable 时,将生成最终的 SQL 语句。在这种情况下,当您调用 FirstOrDefault 时。

例如,您可以更改以下内容:

(from m in this.context.tblMessages
        where m.SystemActionID.HasValue &&
        (userID == null || m.RecipientID == null || m.RecipientID == userID) &&
        m.TargetUserType == userType &&
        (
             (territoryID.HasValue && !m.TerritoryID.HasValue) ||
             (!territoryID.HasValue && !m.TerritoryID.HasValue) ||
             (territoryID.HasValue && m.TerritoryID.HasValue && territoryID.Value == m.TerritoryID.Value)
        ) &&

对此

if (userId==null)
    return null;
var query=from m in this.context.tblMessages
        where m.SystemActionID != null 
              && (m.RecipientID == null || m.RecipientID == userID)
              && m.TargetUserType == userType ;
if (territoryID.HasValue)
    query=query.Where(m=>m.TerritoryId==null || m.TerritoryId==teritoryID);

您可以以相同的方式简化语句的其余部分,例如:

if (regionID.HasValue)
    query=query.Where(m=>m.RegionId==null || m.RegionId==regionID);
if (teamID.HasValue)
    query=query.Where(m=>m.TeamID==null || m.TeamID==teamID);

LINQ 查询不应将参数与常量进行比较 - 为什么要强制服务器执行可以在客户端上轻松执行的常量检查?

这将导致查询更简单,

服务器更容易优化

在这种情况下,我正在使用IQueryable。我使用了一些虚拟名称而不是您的示例来演示想法本身 - 您需要逐步应用"where"子句。

IQueryable<Account> query1 = from account in storage.Accounts
                                where account.Username == username
                                select account;
IQueryable<SomeNewTypeIfNecessary> query2 = from account in query1
                                            where account.ID > 100
                                            select new SomeNewTypeIfNecessary { ID = account.ID };
// Final call doing real query to database.
List<SomeNewTypeIfNecessary> accounts = query2.ToList();

此外,这种技术允许动态添加所需的语句(主要是where和orderby子句)以获得依赖于某些用户选择的特定数据(例如排序方向)。