【问题标题】:How would I write this as a single LINQ query?我将如何将其编写为单个 LINQ 查询?
【发布时间】:2010-10-20 23:31:29
【问题描述】:

使用以下逻辑,单个 LINQ 查询的正确语法是什么?

If Branch is Service, I want its parent, otherwise I want Branch.

您能否批评我的尝试并告诉我如何改进它?

int branchId = 21;
var t = ctx.BranchInfos.Single(p => p.BranchID == branchId );
if (t.Type == BranchType.Service.ToString())
{
    t = ctx.BranchInfos.Single(p => p.BranchID == t.Parent);
}

【问题讨论】:

    标签: c# .net linq .net-3.5


    【解决方案1】:

    我建议,如果仅在一个地方需要,那么您现在拥有的内容相当清楚,应该保留。

    如果您经常这样做,请执行以下操作:

    public static BranchInfo BranchOrServiceParent(
        this IEnumerable<BranchInfo> input)
    { 
        var t = BranchInfos.Single(p => p.BranchID == branchId);
        if (t.Type == BranchType.Service.ToString())    
            t = input.BranchInfos.Single(p => p.BranchID == t.Parent);
        return t;
    }
    

    那么使用就这么简单:

    int branchId = 21;
    var t = ctx.BranchInfos.BranchOrServiceParent();
    

    如果您随后需要以明确的方式参数化/更改事物。

    如果您随后发现两次可能 访问数据库是性能问题,那么您可以尝试复杂的 Linq 查询,或者接受这可能需要通过存储过程实际完成。

    【讨论】:

      【解决方案2】:
      var t = ctx.BranchInfos.Single(
        p => (p.BranchID == branchId && p.Type != BranchType.Service.ToString) ||
             (p.BranchID == GetBranchParentId(branchId) && p.Type == BranchType.Service.ToString));
      

      GetBranchParentId 是一个函数,它返回将 ID 作为参数传递的分支的 BranchId。

      但是,我喜欢你的原始代码,所以我不会使用单个查询来获取我的数据。

      【讨论】:

      • 似乎单次往返数据库优于两次。
      • 这最终将不起作用。您假设父项的类型是 Service 并且如果类型不是 Service 并且存在具有类型 Service 的父项,则表达式将引发异常,因为您获得了两个与 predecate 匹配的元素,而 SIngle() 期望一个。
      【解决方案3】:

      我相信以下内容等同于您的代码示例。我添加了一些模拟代码来将其变成一个独立的示例。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      
      namespace ConsoleApplication1
      {
          class Program
          {
              static void Main(string[] args)
              {
                  Context ctx = new Context();
                  ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 20, Parent = 0 });
                  ctx.BranchInfos.Add(new BranchInfo() { Type = "Service", BranchID = 21, Parent = 20 });
                  ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 30, Parent = 20 });
      
                  int branchId = 21;
      
                  var t = (from a in ctx.BranchInfos
                           where a.BranchID == branchId
                           select a.Type != BranchType.Service.ToString() ? a :
                           (from b in ctx.BranchInfos
                            where b.BranchID == a.Parent
                            select b).Single()).Single();
      
                  Console.WriteLine(t.BranchID); // Prints 20
              }
      
              class Context
              {
                  public List<BranchInfo> BranchInfos = new List<BranchInfo>();
              }
      
              class BranchInfo
              {
                  public string Type;
                  public int BranchID;
                  public int Parent;
              }
      
              enum BranchType
              {
                  Service = 0
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        除非我搞砸了逻辑,否则这可能会为您提供所需的东西。

        编辑:不同的方法

        var t = ctx.BranchInfos.Where(p.BranchID == branchId).First(p => p.Type == BranchType.Service.ToString() ? p.Parent : p);
        

        【讨论】:

        • 我认为这行不通。你有 p.ParentID == branchId && p.BranchID = branchID 在那里。这看起来像一个循环。
        • 第二次尝试,告诉我进展如何
        • 看起来不错,虽然我不知道为什么没有人使用 firstordefault / singleordefault。
        【解决方案5】:
        var t = ctx.BranchInfos.Where(p =>
         (
           p.BranchID == branchID &&
           p.Type != BranchType.Service.ToSting()
         )
         ||
         (
           p.Type == BranchType.Service.ToSting() &&
           ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault() != null &&
           p.BranchID == ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault().ParentID
         )).FirstOrDefault();
        

        这里的逻辑是:(如果类型是服务,则按 ID 获取分支)或(如果分支类型是服务,则获取我知道子 ID 的分支的父级)

        还有:

        即使其中有一个子查询,它也会评估为对数据库的一次命中,因为您在子查询中使用相同的 Datacontext。

        【讨论】:

        • 您假设父级的类型为 Service,但我不确定这是否合理。
        • 没有。我是说(在第二个 OR 条件下):只要给我所有类型为 Service 的分支。其中,给我一个 ID 与 ID = 21 的分支的 ParentID 匹配的那个
        • 是的……给我所有类型为 Service 的项目,然后你从 ID 为 21 的分支的 ParentID 中选择 ID 为 ID 的项目。所以你暗示这一步的类型为 Service。但是问题中没有声明说非服务分支的父级是服务类型。进一步检查 Single() != null - Single() 永远不会返回 null 但会引发异常。最后,您将在子查询中重用参数名称 p。
        • 如果Branch 不是Service 类型,而parent 是Service 类型,将会崩溃。在这种情况下,两个实体的条件都为真,并且 Single() 将抛出异常,因为该序列包含多个元素。
        • 酷,感谢编辑丹尼尔,绝对更具可读性。仍然不确定您的逻辑,尽管我确定那里有错误。我只是在记事本中写了那个查询,所以在真实情况下我会测试它。但回到逻辑上,我不确定我是否同意你的观点,因为它只检查父级的东西,如果有一个匹配 type = Service 的分支,因此使用运算符“&&”(AndAlso)。我还将 Singles() 更改为 Where() // FirstOeDefault() 欢呼
        【解决方案6】:

        我很确定您可以使用单个 LINQ 语句来完成,但我同样确定您不应该这样做。它不会提高可读性,也不会提高性能。

        var t = ctx.BranchInfos.Single(x =>
          (
            x.BranchID == branchID &&
            x.Type != BranchType.Service.ToSting()
          )
          ||
          (
            ctx.BranchInfos.Any(
              y.BranchID == branchID) &&
              y.Type == BranchType.Service.ToSting()) &&
            x.BranchID == ctx.BranchInfos.Single(
              y.BranchID == branchID) &&
              y.Type == BranchType.Service.ToSting()).ParentID
          )
        );
        

        很好,不是吗? :D 我仍然建议不要使用它。第一种情况很简单 - 如果项目具有正确的 ID 并且不是服务类型,我们就有匹配项。

        第二种情况更棘手。我们必须检查该项目是否具有来自具有提供的 ID 的项目的 ParentID 属性的 ID,但前提是具有提供的 ID 的项目是服务类型。因为我们在检查这个的时候不知道有没有提供的ID和类型Service的item,所以要先用Any()检查有没有这个item,依赖and的条件评估。

        【讨论】:

        • 第一点值得商榷,而后者只是愚蠢的。两次往返数据库和一次一样吗?
        • 如果它在某个地方循环,那么你应该争取一次往返。如果它偶尔执行一次,可读性应该是你的首要任务。
        • 可读性:试着找一个表达方式,可能就没有讨论的余地了。性能:我在 Lars 的观​​点 - 你真的应该有一个很好的理由来介绍这样一个不可读的结构。如果您决定使用 OR 映射器并且可能会延迟加载,那么您已经决定数据库访问对性能没有那么重要。
        • 说 ORM 的使用自动表明数据库性能无关紧要,这并不是一个公平的说法,也不意味着您应该做出有利于“短视”观点的任意决定可读性”。可读性意味着代码的作用应该是相当明显的,而且这看起来并不是很模棱两可。
        • 特别是在延迟加载 ORM 的情况下 - 对我来说,这确实是一个明确的声明,您不在乎是否需要一、五或十次往返来获取某些东西 - 可能是一个集合的几个实体。当然,您将尝试从 ORM 中获得最大的收益,并尝试一些热点。但如果数据库性能真的很关键,你就不会使用 ORM。至少我不会。
        【解决方案7】:

        提供的 BranchInfo.Parent 与 BranchInfo 的类型相同:

        int branchID;
        var branchOrParent = db.BranchInfos
            .Where(b => b.BranchID == branchID)
            .Select(b => b.Type == BranchType.Service.ToString() ? b.Parent : b)
            .FirstOrDefault();
        

        【讨论】:

          【解决方案8】:

          这可能会奏效,但有点尴尬。

          var t = ctx.BranchInfos.Where(p => p.BranchID == branchId)
              .Select(p => 
                p.Type != BranchType.Service.ToString() 
                ? p 
                : ctx.BranchInfos.Single(t => p.Parent == t.BranchId)).FirstOrDefault();
          

          【讨论】:

          • 目前最好的解决方案,我也想到了。但我怀疑 LINQ to SQL 或 Entity 可以处理条件运算符。无论如何+1
          • 单曲不支持选择
          • 只需将第一个 Single() 替换为 Where() 并在末尾添加一个 Single() 即可。
          • 你可能想要 first 或 default 在最后
          • 很好的输入,实际上。编辑为包括 Where 和 FirstOrDefault。
          【解决方案9】:

          我认为你可以这样做:

          var t = ctx.BranchInfos.FirstOrDefault(p => p.BranchID == branchId || p.BranchID == t.Parent);

          迈克

          【讨论】:

          • 这行不通——你在给 t 赋值之前使用了 t.Parent。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-01-15
          • 2011-09-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多