【问题标题】:LINQ Exception: "Queries with local collections are not supported." but not using a local collectionLINQ 异常:“不支持使用本地集合的查询。”但不使用本地集合
【发布时间】:2011-05-23 19:21:15
【问题描述】:

以 AdventureWorks 数据库的 Products 表为例,我创建了一个 DBML 并扩展了 DataContext 的属性以包含一个新属性:

部分类 AdventureWorksDataContext { public IQueryable FinishedProducts { 得到 { 返回 Products.Where(p => p.FinishedGoodsFlag); } } }

Products 属性是生成的 DataContext 的一部分,我所做的只是从 Table 中添加一个 Where,因此它返回一个 IQueryable。

现在,当尝试像这样查询它时,问题就出现了(愚蠢的例子,但应该显示问题):

var queryFinishedProducts = datacontext.FinishedProducts.Where(fp => fp.ProductID == datacontext.FinishedProducts.Max(p => p.ProductID));

迭代此查询会导致“不支持具有本地集合的查询”异常。我不明白为什么在没有使用本地集合时会抛出该错误。如果我对正常的 Products 表(这是一个 Table<Product>)运行它:

var queryProducts = datacontext.FinishedProducts.Where(fp => fp.ProductID == datacontext.Products.Max(p => p.ProductID));

...它工作正常。唯一的区别是我在 Table<Product> 中添加了 Where 并将其返回为 IQueryable<Product>

有人有什么想法吗?

【问题讨论】:

  • FinishedProducts 是一个属性吗? “get”在哪里?
  • 是的,它是一个属性。抱歉,为了清楚起见,我将添加 get。
  • 看起来 SQL 生成器可能无法判断 FinishedProducts` 查询中使用的 Products 与查询的其余部分来自同一个 DataContext

标签: linq exception datacontext


【解决方案1】:

这是一种应该可行的方法:

var max = datacontext.FinishedProducts.Max(p => p.ProductID);
var queryFinishedProducts = datacontext.FinishedProducts
                                       .Where(fp => fp.ProductID == max);

或者假设 ProductID 是唯一的,尝试如下重写您的查询:

var queryProducts = datacontext.FinishedProducts
                               .OrderByDescending(p => p.ProductID)
                               .First();

【讨论】:

  • 感谢您的回复,但是我正在寻找更多原因,为什么 LINQ 会以不同方式处理本质上相同的查询。该查询实际上只是一个更大问题的示例,因为 lambda 能够使用本质上是子 SELECT 的内容。
  • @Mike:我认为区别在于Products不仅仅是IQueryable<T>,而是Table<T>
  • 我用 Reflector 挖掘了 Table 和 DataQuery 类的内部结构,而 Table 只是实现了 CreateQuery 方法,并在使用扩展方法时返回一个 DataQuery。话虽如此,这: Products.Where(p => p) 应该等于返回相同语句的属性,这就是示例所示。我不明白的是,使用 Products 属性传回 IQueryable 有何不同。
【解决方案2】:

我能够重现这种行为。以下是反射器中要查看的更多类型:

System.Data.Linq.SqlClient.SqlBinder.Visitor
System.Data.Linq.SqlClient.SqlVisitor

基于此堆栈跟踪。

at System.Data.Linq.SqlClient.SqlBinder.Visitor.ConvertToFetchedSequence(SqlNode node)
at ..SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at ..SqlVisitor.VisitSource(SqlSource source)
at ..SqlBinder.Visitor.VisitSelect(SqlSelect select)
at ..SqlVisitor.Visit(SqlNode node)
at ..SqlBinder.Visitor.VisitAlias(SqlAlias a)
at ..SqlVisitor.Visit(SqlNode node)
at ..SqlVisitor.VisitSource(SqlSource source)
at ..SqlBinder.Visitor.VisitSelect(SqlSelect select)
at ..SqlVisitor.Visit(SqlNode node)
at ..SqlVisitor.VisitSequence(SqlSelect sel)
at ..SqlVisitor.VisitScalarSubSelect(SqlSubSelect ss)
at ..SqlVisitor.VisitSubSelect(SqlSubSelect ss)
at ..SqlBinder.Visitor.VisitSubSelect(SqlSubSelect ss)

我想知道为什么Table<T> 类型的属性与IQueryable<T> 甚至ITable<T> 类型的属性的处理方式不同。属性的实现无关紧要,返回类型很重要。


IQueryable<T> 类型的属性的处理方式肯定不同于 IQueryable<T> 类型的局部范围变量

IQueryable<Customer> query1 =
  myDC.Customers.Where(c => c.ID == myDC.CoolCustomers.Max(c2 => c2.ID));

IQueryable<Customer> query2 =
  myDC.Customers.Where(c => c.ID == myDC.Customers.Where(c2 => c2.Flag).Max(c2 => c2.ID));

IQueryable<Customer> subQuery = myDC.CoolCustomers;
IQueryable<Customer> query3 =
  myDC.Customers.Where(c => c.ID == subQuery.Max(c2 => c2.ID));

query1 表现出原始行为(异常,本地序列)。

query2 生成此 sql - 不太理想。

SELECT [t0].[ID], [t0].[Flag]
FROM [Customer] AS [t0]
OUTER APPLY (
    SELECT MAX([t1].[ID]) AS [value]
    FROM [Customer] AS [t1]
    WHERE [t1].[Flag] = 1
    ) AS [t2]
WHERE [t0].[ID] = [t2].[value]

query3 在查询翻译过程中过于急切地发出子查询,然后对主查询进行第二次往返。

【讨论】:

  • 感谢您深入研究 LINQ 框架!我只是希望我明白为什么它会受到不同的对待。我将开始挖掘这些课程,看看我还能找到什么。真的希望这不是第一次出现,这似乎是任何大型项目都可能对表属性进行预过滤的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 1970-01-01
  • 2011-10-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多