通过EF/Linq将投影到KeyValuePair中

42

我正在尝试从EF/Linq查询中加载一个KeyValuePair列表,如下所示:

return (from o in context.myTable 
select new KeyValuePair<int, string>(o.columnA, o.columnB)).ToList();

我的问题是这导致出现错误

“在 LINQ to Entities 中,仅支持无参数构造函数和初始化器。”

有没有简单的解决方法?我知道我可以创建一个自定义类来代替使用 KeyValuePair,但那似乎是在重新发明轮子。


关于聚合作为替代选项的讨论:http://social.msdn.microsoft.com/Forums/en-US/513350db-6f1e-4930-87e9-81a04f574b54/linq-select-projection-vs-classic-for-loop-which-one-is-better - George Johnston
4个回答

90

从您的表中仅选择列A和列B,并将进一步处理移到内存中:

return context.myTable
              .Select(o => new { o.columnA, o.columnB }) // only two fields
              .AsEnumerable() // to clients memory
              .Select(o => new KeyValuePair<int, string>(o.columnA, o.columnB))
              .ToList();

也可以考虑创建一个包含键值对的字典:

return context.myTable.ToDictionary(o => o.columnA, o => o.columnB).ToList();

10
这段内容的意思是:这种实现方式可行的原因在于 LINQ-to-Entities 只支持无参数构造函数,所以你不能使用 new KeyValuePair...。而 LINQ-to-Collections 支持带有参数的构造函数。当你调用 AsEnumerable() 时,它会评估 EF 查询,然后由 LINQ-to-Collections 处理以下的 .Select() - Rory
1
如何实现此方法的异步版本?没有 AsEnumerableAsync() 方法。 - Zapnologica
1
@Zapnologica - 将 AsEnumerable() 替换为 ToListAsync(),然后将 await 包裹到 ToListAsync 的末尾,并附加 .Select(o => new KeyValuePair<int, string>(o.columnA, o.columnB)).ToList();。根据需要进行重构。 - ttugates

9

由于LINQ to Entities不支持 KeyValuePair,因此您应该先使用AsEnumerable切换到LINQ to Object:

return context.myTable
              .AsEnumerable()
              .Select(new KeyValuePair<int, string>(o.columnA, o.columnB))
              .ToList();

10
虽然这个方法可以实现,但它效率低下,因为它会加载myTable中的所有列。 - Damian Green
.Select(o=> new KeyValuePair<int, string>(o.columnA, o.columnB)) - vicky

3

最近版本的EF Core可以很好地选择KeyValuePair。我还验证了(在MS SQL Server上),SQL只选择了两个相关字段,因此“Select”实际上已经被翻译了:

return context.myTable 
    .Select(x => new KeyValuePair<int, string>(o.columnA, o.columnB))
    .ToList();

SELECT [m].[columnA], [m].[columnB]
FROM [myTable] AS [m]

生成的SQL与匿名类相同,只是对于后者(例如“AS A”)进行了重命名:
return context.myTable 
    .Select(x => new {A = o.columnA, B = o.columnB})
    .ToList();

SELECT [m].[columnA] AS [A], [m].[columnB] AS [B]
FROM [myTable] AS [m]

使用 .NET 6 进行测试。

请注意,使用字典 (如其他答案所建议的) 会引入与键值对列表不同的额外约束:

  • 字典条目的顺序是未定义的。因此,虽然当前填充和枚举字典元素通常具有相同的元素顺序,但这并不得到保证(我已经看到了重新排序的情况),将来可能会发生改变而没有通知。
  • 在字典中,键值必须是唯一的(这可能适用于数据库中的 columnA,也可能不适用,并且如果 columnA 不唯一,则会导致异常)。
  • 字典不允许键为“null”(这仅是为了完整性,因为 OP 无论如何都使用“int”类型的键)。
  • 字典保留其键的索引,因此设置它可能比仅有一个键值对列表更耗时(虽然这只是微优化...)

根据 OP 的用例(我无法从问题中推断出),其中一些约束可能是可取的,字典实际上可能是解决方案。


需要注意的是,这仅在最后的Select中起作用,因为EF不会尝试将其翻译成SQL。在它之后添加一个Where,EF就必须将其翻译,但却失败了。尽管如此,在最后的选择中利用这种所谓的客户端评估来自动切换可能是有用的。 - undefined
明白了。我实在想不出在其他过滤/处理步骤之前投影到键值对的使用场景。或许直接在后续命令中使用字段/列会更容易,或者在中间步骤中投影到一个匿名类中。 - undefined

0

还有一种替代方案,当您想要为一个键存储多个值时,存在一种称为查找的东西。

表示映射到一个或多个值的每个键的集合。

在这里,您可以找到一些官方文档

此外,查找似乎比字典<TKey,List<TValue>>快得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接