问题
I have some code that was recently upgraded from EF 4.2 to EF 5.0 (actually EF 4.4 since I am running on .Net 4.0). I have discovered that I had to change the syntax of my query, and I'm curious as to why. Let me start off with the problem.
I have an EventLog table that is populated by the client periodically. For each event log an entry is created in a Report table. This is the query that is run periodically to discover any event logs that do not have an entry in the Report table yet. The query I used in EF 4.2 was:
from el in _repository.EventLogs
where !_repository.Reports.Any(p => p.EventLogID == el.EventlogID)
Since upgrading to EF 5.0 I get the following error at runtime:
System.NotSupportedException: Unable to create a constant value of type 'Namespace.Report'. Only primitive types or enumeration types are supported in this context.
I discovered that rewriting it with the join syntax fixed the issue. The following works in EF 5.0 and is roughly the equivalent:
from eventLog in _repository.EventLogs
join report in _repository.Reports on eventLog.EventlogID equals report.EventLogID into alreadyReported
where !alreadyReported.Any()
Some people may have mixed opinions about the mixed syntax/style of the first query, but I'm really more interested in the why of this. It seems odd that the EF 4.2 compiler could generate the SQL for the original query but that the EF 5.0 refuses. Is this a setting I am missing or just a tightening of constraints between the two? Why is this happening?
回答1:
The problem is caused by the type returned by your repository; the problem can be reproduced when _repository.Reports
is not IQueryable<T>
. In that case the Reports
is considered as a non-scalar variable; which by the way is not allowed in LINQ. See Referencing Non-Scalar Variables Not Supported
On your question regarding why the second query works, it is basically the following extension method of IQueryable<T>
which group joins it with IEnumerable<TInner>
.
public static IQueryable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,IEnumerable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, IEnumerable<TInner>, TResult>> resultSelector)
Which just accepts the expression for the key selectors for both outer and inner(instead of referencing non scalar variables); in which the above constraint doesn't apply.
Note: If _repository.Reports
is of IQueryable<T>
the first query will work; because EF will build the expression tree correctly and execute the appropriate SQL.
回答2:
Just for curiosities sake, have you tried converting
from el in _repository.EventLogs
where !_repository.Reports.Any(p => p.EventLogID == el.EventlogID)
to
from el in _repository.EventLogs
where !_repository.Reports.Where(p => p.EventLogID == el.EventlogID).Any();
or
from el in _repository.EventLogs
where !_repository.Reports.Where(p => p.EventLogID == el.EventlogID).Count() > 0;
来源:https://stackoverflow.com/questions/18454046/why-does-ef-5-0-not-support-this-ef-4-x-linq-syntax-when-compiling-to-sql