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?
Entity framework is a great ORM shipped by Microsoft which allow you to query and modify RDBMS like SQL Server, Oracle, DB2 and MySQL etc. by using LINQ syntax. Today, EF is widely used by each and every . NET application to query to database.
First off, if you're starting a new project, use Entity Framework ("EF") instead of Linq to SQL because it now generates far better SQL (more like Linq to SQL does) and is easier to maintain ("L2S").
A compiled query is an object that keeps a prepared SQL statement and a delegate to a materializing function. The first one is to be executed at the server to get rows related to the query, the second one transforms a result set into a sequence of entity objects.
Entity Framework has the following forms of caching built-in: Object caching – the ObjectStateManager built into an ObjectContext instance keeps track in memory of the objects that have been retrieved using that instance. This is also known as first-level cache.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With