【问题标题】:Join data from IQueryable and IList<KeyValuePair>连接来自 IQueryable 和 IList<KeyValuePair> 的数据
【发布时间】:2015-02-25 04:21:37
【问题描述】:

我正在尝试将一行父数据加入到已压缩为单个数据的相关集合中。

我有一个IQueryable 的订单:

IQueryable<Order> orderList = context.Set<Order>
                              .Where("OrderDate >= @0", startDate)
                              .Where("OrderDate <= @0", endDate)

和一个相关的IList&lt;KeyValuePair&lt;int, string&gt;&gt;,其中每个 KVP 包含 OrderID 和来自每个订单行的产品名称的串联字符串。我想根据 OrderID 键将正确 KeyValuePair 中的值加入到订单信息中。

为了说明,所需的输出如下所示:

OrderNum  OrderDate   Customer    State       OrderTotal   Products_Ordered
12345     12/12/2012  J.Bloggs    WA          $25.50       Bolts, Hammer, Suregrip Clamp

我正在尝试如下所示的 linq 连接:

IQueryable result = from o in orders
                    join line in orderLines on o.OrderID equals line.Key
                    select new
                    {
                      o.OrderNum
                      o.OrderDate,
                      o.Customer.CustomerFullName,
                      o.DeliverAddress.State,
                      o.TotalPrice,
                      line.Value
                    }

执行连接的方法似乎有效,但是当我访问返回的IQueryable 时,我得到一个NotSupportedException无法创建类型为'System.Collections.Generic.KeyValuePair`2 的常量值'。此上下文仅支持原始类型或枚举类型。

我做错了什么?

【问题讨论】:

  • @MatthewHaugen 是的,标签已更新
  • 是的,我一问就注意到你有context.Set&lt;Order&gt;。谢谢。
  • 您的变量result 的类型为IQueryable。是出于某种原因需要这样做,还是您只是想要实际结果,而这恰好是它们的样子?
  • 你为什么有IList&lt;KeyValuePair&lt;int, string&gt;&gt;?有字典不是更好吗?
  • 没有理由,除了父级中的原始数据是一个 IQueryable,并且由于这是对附加信息的请求,我认为最好(也是最简单)使用相同的构造。我认为唯一的要求是它是 IEnumerable。

标签: c# linq entity-framework join


【解决方案1】:

您的IQueryable 实际上是一个专门的实体框架实现,它在迭代时尝试通过检查表达式树、执行此查询并返回结果的可枚举来构造 SQL 查询。这是脆弱的,你的预测和查询不能任意复杂,否则表达式 -> SQL 转换器不知道如何处理它。

通过首先实现您的IQueryable 来解决此问题很好,但您甚至不需要这样做。为什么要有一个本质上是元组的列表,而你想要的是一个将订单 ID 映射到一堆数据的字典?

IDictionary<int, string> orderLines = new Dictionary<int, string>();

// Add a dummy item
orderLines[1234] = "Hello, this, is, a, test";

// Get your combined view
// Assuming you have an order with 1234 as the ID, this should work
var result = from o in orders
             select new
             {
                 o.OrderNum
                 o.OrderDate,
                 o.Customer.CustomerFullName,
                 o.DeliverAddress.State,
                 o.TotalPrice,
                 Products = orderLines[o.OrderID]
             }

【讨论】:

  • 你是绝对正确的。就 ef 的查询构造而言,将 linq to object 与 linq to sql 混合在任何情况下都不起作用。
  • “orderLines[o.OrderID]”行被标记为“无效的匿名类型成员声明符。必须使用成员分配、简单名称或成员访问来声明匿名类型成员。我将行更改为:Products = orderLines[o.OrderID]。这有意义吗?
  • @mcalex 是的,对不起。没错,您需要在 anon 类型中显式地为属性命名。有用吗?
  • @Asad 是的,它正在工作,除了返回类型。我将新数据的创建移到它自己的方法中,但我不知道如何返回 IEnumerable
  • 如果你真的需要返回并传递它,你需要创建一个适当的类来保存这些数据。它应该具有来自order 的所有这些属性以及Products 属性。在 LINQ 查询中创建一个新的类型而不是匿名类型,并使返回类型成为该类型的可枚举类型。
【解决方案2】:

Entity Framework 对某些类型有问题,因为它试图将它们推送到后备数据库。快速简单的解决方案是在执行联接之前拉取数据。

var orderList = context.Set<Order>
                       .Where("OrderDate >= @0", startDate)
                       .Where("OrderDate <= @0", endDate)
                       .ToList(); // ------> Relevant line <------

var result = from o in orderList
             join line in orderLines on o.OrderID equals line.Key
             select new
             {
                 o.OrderNum
                 o.OrderDate,
                 o.Customer.CustomerFullName,
                 o.DeliverAddress.State,
                 o.TotalPrice,
                 line.Value
             };

这只是连接发生位置的变化,RDBMS 或客户端。

假设您没有大量数据并且所有行都将匹配,性能不会成为问题。当然,您将不得不拉下未加入的数据,因此如果orderList 中只有少数会在result 中,那么从设计角度来说,这可能值得重新考虑。唯一的选择是首先将 KeyValuePair 项目推送到服务器,这可能不是您想要的。

【讨论】:

  • 我不会真的称它为 EF 有问题,这是一个非常有意识的设计决定。只要您使用IQueriable,您就是将文本注入查询字符串本身——这就是IQueriable 的含义!
  • 试过了,但我已经将代码移到它自己的方法中,我不完全确定如何识别返回类型。 IEnumerable 需要 Type 参数,结果不是 System.Linq.Enumerable。我如何表示一个 IEnumerable
  • @mcalex 作为一般规则,一旦您必须从方法中取出数据,您应该创建一个具体类型。除此之外,您还需要使用dynamic 或其他形式的反射,但至少您将失去静态类型检查。
【解决方案3】:

将查询结果 (IQueryable) 与存储在内存中的普通枚举 (IEnumerable&lt;...&gt;) 连接起来没有多大意义。想一想,查询在具体化之前不会得到处理,在查询中注入您自己的数据是在查询文本本身中完成的——这不是您真正想要的,不是吗?

我认为您对此的期望最好通过首先将IQueryable 实现为IEnumerable&lt;&gt;,然后在两个IEnumerable&lt;&gt;s 上执行普通的 LINQ 连接,这很简单。

这不像你会使用IEnumerable&lt;&gt; 来过滤服务器端的结果集,你并没有真正失去任何性能。

编辑:请注意,如果您正在使用IEnumerable&lt;&gt; 在服务器端过滤结果,您可以这样做! EF(我假设这就是您正在使用的)对于 IQueriable&lt;&gt;.Any&lt;&gt;() 之类的东西具有非常强大的特殊情况,其中包含 IEnumerable&lt;&gt;.Contains&lt;&gt;() ——它在查询文本中插入文字值。这只是在这种情况下没有多大意义的实际连接。

【讨论】:

  • 我明白了。我不知道查询在实现之前不会得到处理。这对我来说很有意义。非常感谢
  • 我通常发现查看生成的查询文本非常有教育意义。试一试,看看它在内部是如何工作的,它会让 EF 使用起来更加直观!
猜你喜欢
  • 2016-09-01
  • 2019-05-12
  • 2012-10-20
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多