问题 在LINQ中,强制转换与内部选择之间的区别


此代码抛出异常:

var query = services
             .SomeQuery(bar).select(x => (Foo)x)
             .Where(x.PropertyOfFoo == FooState.SomeState);
var result = query.ToList(); 

例外:

Unable to cast the type...
LINQ to Entities only supports casting EDM primitive or enumeration types.

此代码有效:

var query = services
             .SomeQuery(bar).select(x => x as Foo)
             .Where(x.PropertyOfFoo == FooState.SomeState);
var result = query.ToList(); 

为什么 as 允许转换和 cast 才不是?

我明白那个 as 将返回null并且如果任一调用失败,则cast将抛出异常。好的。但是当我运行这段代码时:

var query = services
             .SomeQuery(bar);
var result = query.ToList(); 

我得到了更大的查询结果。为什么?


2093
2018-06-16 19:19


起源

它返回一个更大的查询结果,因为没有 Where 条款? - Dennis Rongo
什么是 Foo?结构? - Quality Catalyst
不,这是一堂课。 - David Vogel
什么类型的 SomeQuery 返回?它与...有什么关系? Foo? - D Stanley
Foo 继承自 Bar 然后它回来了 IQueryable<Bar> - David Vogel


答案:


LINQ to Entities与LINQ to Objects不同。而 LINQ to Objects函数可以使用任何匹配的委托 并盲目地将其作为普通的C#代码调用, LINQ to Entities将您的lambda视为表达式树 因为它需要理解这些lambda的语义(不仅仅是它们的签名),并将它们转换为EF后端的等效查询。当然,这意味着LINQ to Entities无法处理LINQ to Objects可以执行的所有操作。

现在,正如你所说,铸造和使用之间的一个区别 as 是失败和铸造抛出异常 as 回报 null。但两者之间存在更为重要的差异: 演员将应用任何潜在的自定义显式转换,而 as 将简单地尝试将引用重新解释为其他内容,忽略任何潜在的转换。

如你所知,演员比复杂得多 as,因为强制转换可能会调用自定义方法( explicit operator)LINQ提供商不容易解决这个问题。提供者可能根本无法检查潜在自定义转换的代码(我对表达式树的限制知之甚少),更不用说为底层源转换它了。因此,LINQ to Entities选择仅允许 as 和最简单的转换案例(基本上,它允许提前知道转换逻辑并且不可能是自定义用户代码的情况)。


6
2018-06-16 21:01





这两个语句之间有一个重要的区别,它表明实体框架在这里涉及的内容比任何事情(如CLR规则)都多。

  1. x as Foo 被翻译成SQL。 EF可以这样做,因为它知道它总会返回一个结果,或者a Foo 或者为null。顺便说一句,生成的SQL是可怕的,但它完成了这项工作。

  2. (Foo)x 是  翻译成SQL。 EF知道没有办法创造 Foo 对象 所有  x的,作为强制转换语义的要求,它抛出异常。

请注意EF将接受 foos.Select(f => (Bar)f),因为始终可以从子类型创建基本类型。实际强制转换是在收到SQL结果后完成的,它不会影响SQL本身。

它也会接受 bars.OfType<Foo>().Select(x => (Foo)x) (虽然这是相当无用的)。

所以错误信息......

LINQ to Entities仅支持转换EDM原语或枚举类型。

......并非完全正确。 EF确实接受演员阵容。因此,在EF的查询转换器中,只有一堆逻辑来决定是否值得为生成SQL语句而努力。


3
2018-06-16 21:39



+1,很高兴知道它支持简单的upcast。我猜错误信息恰恰是因为(正如Eric Lippert所说),演员操作员试图表达两个本质上矛盾的事情。看起来这个错误消息的作者当时只有两个中的一个。 - Theodoros Chatzigiannakis


这与直接投射的工作原理有何不同 as 操作员工作。由于您没有使用值类型,因此无法使用直接转换 - 它仅适用于基元。

现在,你的lambda正试图选择和投射 - 你 可以 把它分成两个操作,所以:

var result = services.SomeQuery(bar).select(x => new Foo() {
    SomeProperty = x.SomeProperty,
    SomeOtherProperty = x.SomeOtherProperty,
    ... }).ToList()

至于更多的结果:你错过了一个 where 那里的条款。


1
2018-06-16 20:26