This code causes a NotSupportedException.
var detailList = context.Details.Where(x => x.GetType().GetProperty("Code").GetValue(x,null).ToString() == "00101").ToList();
But this code works.
var detailList = context.Details.AsEnumerable().Where(x => x.GetType().GetProperty("Code").GetValue(x,null).ToString() == "00101").ToList();
MSDN says:
- AsEnumerable() Returns the input typed as IEnumerable
- DbSet Is an IEnumerable
So why we need to use AsEnumerable() method?
DbSet is also IQueryable.
IQueryable has its own set of LINQ extension methods that translate expression trees into SQL, and do not support reflection.
By calling AsEnumerable(), you change the compile-time type of the expression to IEnumerable<T>, forcing the extension methods to bind to the standard LINQ ones.
If you prefer to run your query on the server, you should build an expression tree instead of using reflection.
The first query attempts to have the query provider translate the query into SQL and execute it against the database. It fails to create a valid database query, so it fails with the error mentioned.
Using AsEnumerable types the query as an IEnumerable<T>, rather than an IQueryable<T>, statically, and as such ends up calling the LINQ to objects version of the query methods, pulling the entire table into memory and then performing all of the operations within the application.
When you're querying an IQueryable<T>, your method gets translated via an Expression Tree into SQL. AsEnumerable to change the compile time type to IEnumerable<T> and to bring all the entities from your database into memory, where you can query them via reflection via LINQ to Objects.
Related
I've got an error:
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
The piece of code:
plist= plist.Where(p => Convert.ToInt(p.Period) >= syslockPeriod);
p.Period example: 201206
pList is IQueryable. p.Period is string typed. sysLockPeriod is int.
How to fix it?
LINQ to entities will try to translate Convert.ToInt32() into SQL instructions, because it's not one of the known methods it can translate then it'll generate that error.
You have some workaround but for all of them you have to change your code. Let's see what you can do (in order of preference, for me).
Convert Period property/field to the proper type. If it's an integer type then why you store it in your DB as a string?
Create a function (in your database) to perform the conversion and use a calculated property (or a view). LINQ to Entities will now have a true column of the proper type (no need to convert) or a function to call (on SQL side) to do the conversion. See this example on MSDN about how to call custom database functions.
Convert IQueryable to IList with ToList() before Where(), now the where clause will be executed locally and not on the database (this may be something terrible to do if the list is pretty big).
Do not convert that syslockPeriod to a string outside the LINQ query. It would help to get rid of the error (if you also remove the "Convert.ToInt"), but would return wrong results. This would give a "true" result: "12" < "2".
The correct solution would be to convert that string-column "Period" to an integer column.
This is a common problem in LINQ because you cannot use normal functions in the LINQ expression. (This allows LINQ to do lazy evaluation and reduces state related problems that can occur from invoking functions). Fortunately, we can work around this problem by creating a business object to do the conversions for you in the accessors, or use Linq to SQL which will convert it.
I prefer the former, as it is good practice to create the business objects as it encouraged reuse.
See the excellent post at http://mosesofegypt.net/post/LINQ-to-Entities-Workarounds-on-what-is-not-supported.aspx for samples.
I'm working with entity framework with POCO objects and I've got LazyLoading turned on.
If i'm working with a collection associated with an object, when is the collection fully loaded and in what circumstances?
If I call each of these
Order.OrderItems.Count()
Order.OrderItems.Any(x => x.StatusId = aValue)
Order.OrderItems.All(x => x.StatusId = aValue)
Do any of these guarantee the complete loading of the OrderItems collection?
At points in the code we are calling
Order.Include(“OrderItems”)
when querying or
context.LoadProperty(order, “OrderItems”)
after querying
But I’ve realised sometimes this isn’t always happening – and I want to know the consequences of this not occurring. I think I’ve got a bit of a knowledge gap with it
Linq methods don't generally load your data until you either foreach over their result, or append a ToList, ToArray, etc. This is how the Linq to Entities provider can allow you to chain methods and only build a query over the final structure.
If a method returns an IQueryable or IEnumerable, you can be pretty sure that Linq won't load the data at that point.
But:
Order.OrderItems.Count()
Order.OrderItems.Any(x => x.StatusId = aValue)
Order.OrderItems.All(x => x.StatusId = aValue)
For an outer-most query, if Linq must return a boolean value or an integer then it must run your query immediately. But things get a little more complicated for nested queries, since they won't be evaluated until the outer query gets evaluated.
As for:
Order.Include("OrderItems")
I believe an Include won't resolve a query on its own. You use it to piggyback additional data when you're making another query.
Linq in general
If you want to know (generally) how Linq works, you can check out this set of articles:
http://msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
That will give you an idea of when an IEnumerable can be deferred, and when it must be evaluated. It won't tell you everything about the Linq to Entities provider, but a good portion of the knowledge will transfer.
Order.OrderItems.Any(x => x.StatusId = aValue)
Order.OrderItems.All(x => x.StatusId = aValue)
are queries.
These queries are executed when you call .ToList() (or some other iterator to materialize it into objects). If you use .Include then included property will be populated (query will contain join for that table) when you execute query. Using Any or All (or any filter extension like Where) only adds where condition (which can be from another table), but it does not put OrderItems into sql select, so they will not be loaded unless you use .Include.
--
Not related to your question, but if you are fresh with this, and you have paging, remember to add Skip and Take to your query before calling ToList (executing query), I have seen people calling ToList very early and reading whole table to memory before doing paging.
My question is how / when it makes sense to overload (if thats possible?) the Where() extension method of IQueryable when you are making your own IQueryable implementation?
For example in Entity Framework its my understanding that a Where() call made against an ObjectSet will change the actual SQL thats being passed to the database. Alternatively if you cast to IEnumerable() first the filtering is done with LINQ-To-Objects rather than LINQ-To-Entities.
For instance:
new MyDBEntities().MyDbTable.Where(x => x.SomeProperty == "SomeValue");
// this is linq-to-entities and changes the database-level SQL
Versus:
new MyDBEntities().MyDbTable.AsEnumerable().Where(x => x.SomeProperty == "SomeValue");
// this is linq-to-objects and filters the IEnumerable
How do you deal with this when implementing your own IQueryable and IQueryable already has pre-defined extension methods such as Where()? Specifically I want make my own very simple ORM that uses native SQL and SqlDataReader under the hood, and want to have a .Where() method that changes the native SQL before passing it to the database.
Should I even use IQueryable or just create my own class entirely? I want to be able to use lambda syntax and have my SQL command altered based on the lambda function(s) used for filtration.
You could create your own type analogous to IQueryable<T>, but that's probably not a good idea. You should probably write your own implementation of the interface. There is a nice series of articles on how to do it, but be prepared, doing so is not one of the easier tasks.
Is it IEnumerable<T>. As far as I know, the reference always points to a class instance. What instance type does the LINQ query really point to?
You can find it out by calling .GetType() on your IEnumerable<T> variable and inspecting the type in the debugger.
For different LINQ providers and even different LINQ methods, such types may or may not be different.
What matters to your code is that they all implement IEnumerable<T> which you should work with, or IQueryable<T> which also accepts expressions, meaning your predicates and projections will become syntax trees and may be manipulated by a LINQ provider at runtime, e.g. to be translated into SQL.
Actual classes, if this is what you're asking about, may even be compiler-generated, e.g. yield return expression is translated to such a class.
Either way, they are usually internal and you should never, ever depend on them.
Depending on your original data source, it is either IEnumerable or IQueryable:
The result of a Linq database query is typically IQueryable<T> which is derived from IEnumerable<T>, IQueryable, and IEnumerable.
If your database query includes an OrderBy clause, the type is IOrderedQueryable<T>, being derived from IQueryable<T>
If your data source is an IEnumerable, the result type is IEnumerable<T>
I don't know but I guess it's an internal type. You don't have to think about the class.
In fact it could be different classes depending on the concrete query. The compiler could convert the LINQ expression to one or another implementation depending on the conditions/processing.
if I am not mistaken, IEnumerable< T >, where T depends on your query
You can check here for Three parts of Query operation.You can see that the return type of an LINQ query is IEnumerable< int >.
I am writing some code that takes a LINQ to SQL IQueryable<T> and adds further dynamically generated Where clauses. For example here is the skeleton of one of the methods:
IQueryable<T> ApplyContains(IQueryable<T> source, string field, string value)
{
Expression<Func<T, bool>> lambda;
... dynamically generate lambda for p => p.<field>.Contains(value) ...
return source.Where(lambda);
}
I might chain several of these methods together and finish off with a Skip/Take page.
Am I correct in thinking that when the IQueryable is finally evaluated if there is anything in the lambda expressions that can't be translated to SQL an exception will be thrown? In particular I'm concerned I might accidentally do something that would cause the IQueryable to evaluate early and then continue the evaluation in memory (thereby pulling in thousands of records).
From some things I've read I suspect IQueryable will not evaluate early like this. Can anyone confirm this please?
Yes you are correct in thinking that your IQueryable can throw an error at runtime if part of the expression can't be translated into SQL. Because of this I think it's a good idea to have your queries in a Business Layer class (like a data service or repository) and then make sure that query is covered by an automated test.
Regarding your Linq expression evaluating at an unexpected time, the basic rule to keep in mind is that your expression will evaluate whenever you call a foreach on it. This also includes methods that call a foreach behind the scenes like ToList() and FirstOrDefault().
BTW an easy way to tell if a method is going to call a foreach and force your lambda to evaluate is to check whether the return value on that method is an IQueryable. If the return value is another IQueryable then the method is probably just adding to the expression but not forcing it to evaluate. If the return value is a List<T>, an anonymous type, or anything that looks like data instead of an IQueryable then the method had to force your expression to evaluate to get that data.
Your thinking is correct.
As long as you pass the IQueryable an Expression in your Where clauses it will not evaluate unexpectedly.
Also, the extension methods beginning with "To" will cause evaluation (i.e. ToList(), ToArray()).