【问题标题】:Simplifying complex linq query with multiple conditions简化具有多个条件的复杂 linq 查询
【发布时间】:2015-03-06 12:20:45
【问题描述】:

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

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

【问题讨论】:

    标签: c# .net linq conditional


    【解决方案1】:

    我在这种情况下使用 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 子句)以获取依赖于某些用户选择(例如排序方向)的特定数据。

    【讨论】:

    • LINQ 使用 IQueryable。
    • @PanagiotisKanavos:你到底是什么意思?我告诉我需要 IQueryable 来存储中间结果(一些表达式树),而不是在同一语句中执行查询。请在任何 cmets 之前仔细阅读源代码。
    • 没有中间结果。 IQueryable 是查询的抽象,而不是查询的结果。您正在做的是撰写一个查询。实际上,您可以将代码重写为query2=query1.Where(account=&gt;account.ID&gt;100); query3=query2.Select(...),结果将完全相同。最后只有一个查询会被发送到服务器,当你调用ToList();
    • @PanagiotisKanavos:关于 QUERY 的中间结果(任何行、获取的数据等),我在哪里说过?我讲述了中间结果,意思是“一些表达式树”。请阅读括号内的文字。所以 "query1.Where(account=>account.ID>100).Select(...)" 将产生完全 IQueryable,但我们讲述了将复杂语句拆分为多个语句的方法。而 IQueryable 正是存储中间表达式的类型,所有下一个表达式都将使用对应的前一个作为基础。
    【解决方案2】:

    首先,此代码看起来像是试图将错误的 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 查询永远不应该将参数与常量进行比较 - 为什么要强制服务器执行您可以在客户端轻松执行的常量检查?

    这将导致查询更简单,服务器更容易优化

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-14
      • 2012-12-03
      • 2016-05-28
      • 2011-09-21
      • 2012-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多