【问题标题】:Linq reducing one-to-many relationship to one-to-oneLinq 将一对多关系减少为一对一
【发布时间】:2015-11-19 23:21:54
【问题描述】:
List<dynamic> a = new List<dynamic>();
a.Add(new { Foo = 1, Baz = "Inga", Name = "Alice"});
a.Add(new { Foo = 2, Baz = "Baz", Name = "Bob"});
a.Add(new { Foo = 3, Baz = "Hi", Name = "Charlie"});

List<dynamic> b = new List<dynamic>();
b.Add(new { Foo = 1, Value = "Bar", Code = "A"});
b.Add(new { Foo = 1, Value = "Quux", Code = "B"});
b.Add(new { Foo = 2, Value = "Bar", Code = "C"});
b.Add(new { Foo = 3, Value = "Mint", Code = "A"});
b.Add(new { Foo = 3, Value = "Seven", Code = "Q"});
b.Add(new { Foo = 3, Value = "Threeve", Code = "T"});

好的....所以我有一个问题(自然) 这是人为设计和简化的,以专注于手头的问题。 我需要修改一个 Linq 查询以将两个列表投影到以下响应:

[
   {  Foo = 1
    , Baz = "Inga"
    , Code = "A"
    , Bars = [{ Value = "Bar", Code = "A"}
             ,{ Value = "Quux", Code = "B"}
             ]
   }
   ,{  Foo = 2
     , Baz = "Baz"
     , Code = "C"
     , Bars = [{ Value = "Fizz", Code = "C"}]
    }
   ,{  Foo = 3
     , Baz = "Hi"
     , Code = "A"
     , Bars = [{ Value = "Mint", Code = "A"}
              ,{ Value = "Seven", Code = "Q"}
              ,{ Value = "Threeve", Code = "T"}
              ]
    }
]

首先,TL;DR

有没有办法查询集合b来选择

(b.First Where Distinct By b.Foo) AsEnumerable()?

...长版

我需要选择a 的投影,但是当它被具体化时,在b 列表中识别第一个Code,其中b.Foo == a.Foo 并将b.Code 直接放在a 上。然后 b 中 b.Foo == a.Foo 的项目需要放入 a.Bars 中。

我手头的问题是我没有识别单个 a,所以我无法预先选择 ab 值来简化这个混乱,也没有机会改造。

所以,如果我想搜索Where Value = Bar; Alice 和 Bob 需要返回正确的映射和投影。

天真的尝试是......

var results = a.Join( b
              , master => master.Foo
              , detail => detail.Foo
              , (master, detail) => new { master, detail})
         .Select(item => new
         {
             item.master.Foo
           , item.master.Baz
           , item.master.Name
           , item.detail.Code
           , Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
         };

但这会导致我的结果包含重复的“Alice”记录和重复的“Charlie”记录,因为它内部连接了 a 和 b。我真正想做的(伪)是

a.Join(
        b.Where(b.Foo.Equals(a.Foo)).First()
        , master => master.Foo
        , detail => detail.Foo
        , (master, detail) => new { master, detail}
      )
 .Select(item => new
         {
             item.master.Foo
           , item.master.Baz
           , item.master.Name
           , item.detail.Code
           , Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
         };

但无论我怎么尝试,结果都是一团糟。

...注意,我不能采取幼稚的方法然后运行DistinctBy,因为投影是匿名的。

任何人都可以通过 Linq to Object 查询来解决这个问题吗? (注意:我不需要单通道分辨率)

【问题讨论】:

    标签: c# linq linq-to-objects


    【解决方案1】:

    我会坚持对第二组进行分组(在此处表示为 b),然后将其与 find 配对来组成投影。

    var results = b.GroupBy( d => d.Foo ).Select( g => new {
        Foo = g.Key,
        Baz = a.First( i => i.Foo == g.Key ).Baz,
        Code = g.First().Code,
        Bars = g.Select( e => new { Value = e.Value, Code = e.Code }).ToArray()
    });
    

    【讨论】:

    • 如果你事先从a 生成Dictionary&lt;int, string&gt; (Foo -> Baz),并用它代替a.First( i =&gt; i.Foo == g.Key).Baz,我认为它的执行速度会比GroupJoin 还要快。好答案+1
    【解决方案2】:

    如果我理解正确,单个结果条目中的Code 只是第一个加入的b 元素的Code

    所以试试这个:

    var result = a.GroupJoin(b,
                             a0 => a0.Foo,
                             b0 => b0.Foo,
                             (a0, bs) =>
                                 new
                                 {
                                     Foo = a0.Foo,
                                     Baz = a0.Baz,
                                     Code = bs.Select(b1 => b1.Code).FirstOrDefault(),
                                     Bars = bs.Select(b1 => new {b1.Value, b1.Code}).ToArray()
                                 }).ToArray();
    

    【讨论】:

    • 我认为您的方法似乎很有希望,但是... A lambda expression with a statement body cannot be converted to an expression tree
    • 我没有收到那个错误。它运行顺利。使用类和dynamic 进行了测试。您是否将其与EntityFramework 或其他东西一起使用?我假设 ab 已经被填充了。
    • 我已经更新了答案,所以它现在可能适合你的情况。
    • 实际上,您很清楚(关于 EF - 目前正在调整您对我的 RWS 的更改以查看它是否合适)我很抱歉将其排除在外。我正在使用 Entity Framework 为其 ADO.NET 生成器从数据库视图中补充水分。我将场景设置为 Linq to Objects 以简化问题,但我忘记了那个“小细节”。
    • 好的。所以在我发帖之前的一次尝试中,我曾尝试做一个 GroupJoin。我放弃了它,因为我为了简化而考虑了以下复杂性:调用者需要能够基于Bar.Value 查询整个事物,但返回的Bars 列表必须是“all b where a.Foo == b.Foo”,当我谈到这一点时提到了与我的查询相关的问题,希望返回一个集合。我需要能够搜索a,如果适用,b如果适用,抓取所有b对于每个已识别a然后投影整个事情.问题来了。
    【解决方案3】:

    GroupJoin 是您需要的。您可以将Join 视为SelectMany,而将GroupJoin 视为Select。区别在于投影的第二个参数的类型 - TInner 用于 JoinIEnumerable&lt;TInner&gt; 用于 GroupJoin。在 LINQ 语法中,GroupJoin 是通过 into 子句实现的。

    话虽如此,以下是它在两种语法中查找示例的方式:

    var resultsA = a.GroupJoin(b, master => master.Foo, detail => detail.Foo, (master, details) => new
    {
        master.Foo,
        master.Baz,
        master.Name,
        Code = details.Select(detail => detail.Code).First(),
        Bars = details.Select(detail => new { detail.Value, detail.Code })
    });
    
    var resultsB =
        from master in a
        join detail in b on master.Foo equals detail.Foo into details
        select new
        {
            master.Foo,
            master.Baz,
            master.Name,
            Code = details.Select(detail => detail.Code).First(),
            Bars = details.Select(detail => new { detail.Value, detail.Code })
        };
    

    【讨论】:

      【解决方案4】:
      var query = from ai in a
                  let bs = b.Where(bi => bi.Foo == ai.Foo)
                  select new
                  {
                      ai.Foo,
                      ai.Baz,
                      Code = bs.Select(bi => bi.Code).FirstOrDefault(),
                      Bars = bs.Select(bi => new { bi.Value, bi.Code }),
                  };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-07
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 2020-10-14
        • 1970-01-01
        • 2018-09-27
        相关资源
        最近更新 更多