【问题标题】:Create a one to many collection from linq result setC#从 linq 结果集创建一对多集合C#
【发布时间】:2022-01-12 18:58:49
【问题描述】:

我有一个包含一对多数据集合的数据库表。从该表中,我需要制作一个代表数据的字典。键将是主 ID,值将是与该主 ID 关联的 ID。我的数据集如下

预期的字典应该如下所示

我尝试了以下代码,但没有按预期工作。而不是 Dictionary 它返回 Dictionary

 Table.GroupBy(item => item.MasterId).ToDictionary(item => item.Key);

有什么想法吗?

【问题讨论】:

  • 这个问题有几个矛盾之处。没有一对多的字典。一个字典键只能对应一个值。 SQL 中的分组也消除 值,它不会对它们进行批处理。您想要的输出是一个 flat 结果集,在客户端转换为嵌套对象。
  • 你可以使用Dictionary<int, List<int>>
  • 它没有按预期工作 - 所以你应该告诉我们它是如何工作的以及为什么这不符合你的期望,这样我们就可以推断出你的情况想要想要。
  • @Chetan 这个问题是关于列表的,而不是关于 EF 的。它行不通。 SQL 中的GROUP BY 将消除值并基于聚合函数为每个键返回一个值。问题恰恰相反 - 如何嵌套平面结果

标签: c# linq entity-framework-6


【解决方案1】:

我尝试了以下代码,但没有按预期工作。而不是 Dictionary 它返回 Dictionary

这里对 LINQ GroupBy 有一个基本的误解

GroupBy 的输出是 IGrouping 的枚举,它类似于列表列表。然而,它并不完全是 List<List<something>>

IGrouping 是具有属性 Key 和相关值集合的东西。无论源集合推动了分组的创建,其中的所有值都是通过源对象上的某些操作以及创建键的某些操作创建的

在最简单的情况下,您使用过的那个,您告诉 GroupBy 如何生成一个键作为纯单个简单值,MasterId,它是 Table 中所有对象的属性。您没有指定自定义操作来从项目生成值,因此将整个项目用作值

对于发布的示例,您的项目是一对整数,也许我们可以将它们建模为:

record Thing(int MasterId, int AssociatedId);

仅按 MasterId 分组:

GroupBy(t => t.MasterId)

意味着您得到的结果类似于(JSON-esque 表示)

  [
    {
      Key: 584753,
      this: [ { MasterId: 584753, AssociatedId: 5 },{ MasterId: 584753, AssociatedId: 4 },{ MasterId: 584753, AssociatedId: 3 } ]
    },

   {
      Key: 584754,
      this: [ { MasterId: 584754, AssociatedId: 4 },{ MasterId: 584754, AssociatedId: 3 } ]
    },
    ...

我说 json-esque 是因为 json 不能真正代表一个对象数组,该数组也有一个不是对象之一的属性。我能得到的最接近的方法是让您想象一个具有默认属性 this 的对象,即数组


GroupBy 生成的不是Dictionary<int, List<int>>,它是“具有 Key 属性的 IGrouping 对象列表,也是表中的整个项目的 Thing 对象列表,包括主 ID 和关联 ID " - IGrouping 有一个 Key 并且里面有项目,就像一个数组有一个 Length 并且里面有项目一样。我们可以把它变成字典,但首先需要做更多的工作。

GroupBy 将整个 Thing 项作为值输出这一事实是一个问题,因为您不想要 Dictionary<int, List<Thing>>

GroupBy 有另一种形式,您可以在其中提供第二个参数以从事物中获取值,而不是使用整个事物

Table.GroupBy(t => t.MasterId, t => t.AssociatedId);

这一次,您的 IGrouping 中的项目值仅采用 AssociatedId。在 json-esque 中它看起来像:

  [
    {
      Key: 584753,
      this: [ 5, 4, 3 ] 
    },

   {
      Key: 584754,
      this: [ 4, 3 ]
    },
    ...

这更接近你想要的,它不是字典,它是 IGrouping 的列表,而 IGrouping 不是列表

进入ToDictionary的使用

如果使用 ToDictionary 的单参数形式:

GroupBy(...).ToDictionary(g => g.Key)

你将得到一个Dictionary<int, IGrouping> - int 来自作为 int 的 Key,这是在分组操作期间做出的决定。该值是一个 IGrouping,因为这种形式的 ToDictionary 仅使用输入的整个项目作为值。整个项目是一个 IGrouping。

ToDictionary 有另一种形式,它采用一些代码来生成值和键。您可以使用此表单将 IGrouping 转换为列表。请记住,您已经有一个充满整数的 IGrouping,所以这是一个简单的例子

GroupBy(...).ToDictionary(g => g.Key, g => g.ToList());

给你一个完整的表达方式:

Table
  .GroupBy(t => t.MasterId, t => t.AssociatedId)
  .ToDictionary(g => g.Key, g => g.ToList());

当然,还有其他写法。你可以通过生成IGrouping<Thing> 离开 Group,而是在 ToDictionary 期间选择关联 ID

.GroupBy(t => t.MasterId)
.ToDictionary(g => g.Key, g => g.Select(t => t.AssociatedId).ToList())

您可以使用 groupby 的形式在分组结果上执行其他操作:

.GroupBy(t => t.MasterId, t => t.AssociatedId, (k, g) => new { K= k, L = g.ToList() } )
.ToDictionary(g => g.K, g => g.L)

总有几十种方法可以给这只猫剥皮。对您来说最重要的是要欣赏 GroupBy 将 1D 列表转换为 2D 列表,这将我引向脚注...

关于数据库的脚注

这就是 Panagiotis 的目的。 LINQ GroupBy 与 SQL GROUP BY 非常不同

在 SQL GROUP BY 中,您选择哪些内容将成为您的关键,而从中获取任何非关键数据的唯一其他选择是立即执行聚合,然后将数据丢弃

SELECT MasterId, MIN(AssociatedId), MAX(AssociatedId)
FROM Table
GROUP BY MasterId

您根本无法拥有 Key,然后是 SQL GROUP BY 的所有关联数据。具有相同 MasterId 的所有行都被放入外部带有该 masterid 标签的存储桶中,混合在一起,您只能使用聚合操作从存储桶中提取数据,例如“最大 AssociatedId,平均值(虽然很荒谬) AssociatedId”等。这会混淆您的数据,因为 MAX(AssociatedID) 来自一行,MAX(OrderItemCount) 来自另一行..

LINQ GroupBy 将公共键下的行聚集在一起,但随后将所有数据仍在其中的存储桶集作为整行仍然一体的数据交还给您。您可以在 LINQ 中进行 GroupBy,然后在每个组中请求 First(),然后您会得到例如 584753,5 -> SQL 根本没有这个概念。扔进分组桶后就没有“第一”的东西了

..这意味着您在此处表达的 LINQ 根本无法转换为 SQL 并在服务器上执行。如果您尝试(在 EFCore 上),您将收到错误消息“此查询必须在客户端完成” - 在某些旧版本的 EF(核心和非核心)中,“我只需将所有行拉到客户端并在那里做”是自动的,我们已经摆脱了这一点,因为自动下载一百万行只是为了找到数据库不能做的事情是开发人员应该具体做出的决定

【讨论】:

    【解决方案2】:

    您需要将分组内的值转换为字典

    var test = Table.GroupBy(item => item.MasterId).ToDictionary(g => g.Key, v=> v.Select(x => x.InternalValue));
    

    所以你有一个字典

    【讨论】:

      【解决方案3】:

      问题不明确,包含矛盾。 EF 没有数据集或数据表——它甚至没有表。 DbSet 不是表的表示形式,它是将表数据映射到客户端对象的一种方式。

      EF 查询被转换为 SQL。 GROUP BY 不嵌套值,它聚合它们,每组只返回一个值。这和你问的相反。 SQL 结果总是平坦的。

      另一方面,可以将 LINQ 与 in-memory ADO.NET DataTable 对象一起使用,就像与任何其他容器一起使用一样。但是,DataRow 没有命名列,因此 LINQ to Dataset 函数用于检索特定字段。

      除非您使用 Visual Studio 的设计器创建了 DataTable,该设计器会生成自定义 DataRow 派生类,将列值作为属性公开。

      实体框架

      如果您使用 EF,则必须将平面数据加载到内存中,然后再将其嵌套。您应该避免使用GroupBy,因为您并不想生成GROUP BY

      字典的每个键不能有很多值。您可以改用ToLookup 来生成一个ILookup 对象。

      以下查询将嵌套整行:

      var lookup= dbContext.OrderItems
                           .Where(...)
                           .ToLookup(o=>o.OrderID);
      

      虽然这只会加载所需的列

      var lookup= dbContext.OrderItems
                           .Where(...)
                           .Select(o=>new {o.OrderItemId,o.OrderId})
                           .ToLookup(o=>o.OrderID);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-10
        • 1970-01-01
        • 2014-01-05
        • 1970-01-01
        • 2020-07-03
        • 1970-01-01
        相关资源
        最近更新 更多