使用多个条件简化复杂的linq查询

时间:2015-01-08 11:37:17

标签: c# .net linq conditional

最近我遇到了修复bug的代码,如下所示。 它很难理解,并使变化看起来也很危险。 是否有任何简单的方法可以解决这样的问题,即De Morgan的法律或以不同的方式重写。

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

2 个答案:

答案 0 :(得分:3)

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

这两件事都是糟糕的想法。

  • 首先,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查询永远不应该将参数与常量进行比较 - 为什么强制服务器执行常规检查,您可以轻松地在客户端上执行此操作?

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

答案 1 :(得分:1)

我在这种情况下使用IQueryable。我使用了一些虚拟名称而不是你的例子来展示自己的想法 - 你需要应用"其中"条款一步一步。

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子句),以根据某些用户选择(例如排序方向)获取特定数据。