LINQ to Entities chaining commands with differing results - c#

My question is more general, but I have an example to help illustrate:
db.aTable.Where(x => x.Date < someDateInThePast).OrderByDescending(x => x.Date).First()
That gives me one item, which differs from the item returned by this command:
db.aTable.Where(x => x.Date < someDateInThePast).ToList().OrderByDescending(x => x.Date).First()
(note the "ToList()" in the middle).
From what I can see, what is actually happening in the 1st example is the OrderBy is completely disregarding the filtering that is done by the .Where(). It is ordering the entire aTable.
And the 2nd query is giving the actually correct item.
The .Date parameter is a DateTime type (on SQL side it is a 'datetime').
Is this behaviour to be expected from LINQ to Entities?

By adding .ToList() you actually change the context in which the data is processed.
Your first query is handled by your database completely and you only return the value from .First() to your Entity-Instance.
In the second one, you basically give the command to load up the conditioned aTable by giving him the command .ToList() and THEN order it, add the second condition and pick the first Date value from that already instanced table.
Microsoft states link that a CLR change might lead to unexpected results, which is what you are doing.
One way to know exactly what is happening would be that you execute your statement on your SQL Server directly:
Select Top(1) Date
From aTable
Where Date < someDateInThePast
order by Date desc
And then create a dbset for your data up to the point where the context changes:
Select *
From aTable
Where Date < someDateInThePast
order by Date desc
And then call it separately in your c# environment. Then check whether the results still differ.
Hope this helps!

I can't fully explain why it works like this but I have found that the inclusion of the First() is what is causing the issue. When I view the raw SQL that is generated by the LINQ there is no reference to 'Order By' in it. I can only assume the ordering happens on the client side. But, there is reference to take 'TOP (1)' in the SQL. Meaning, because the SQL server is only returning 1 result, the order by is happening on just 1 result and doesn't do anything useful.
If I change First() to ToList() then the ordering works as expected. This doesn't solve my issue but it explains the behaviour.

Related

Why does this Linq query return 0 for Count()?

This seems like an inconsistency, but I'm probably just missing something obvious. The base query is:
var events = db.tbl_special_events.Where(x => x.TimeStamp >= startDate);
The apparent inconsistency comes when I run the following code block:
int c1 = 0;
foreach (var e in events)
{
if (e.TimeStamp.DayOfWeek.ToString() == "Tuesday") c1++;
}
int c2 = events.Where(e => e.TimeStamp.DayOfWeek.ToString() == "Tuesday").Count();
After that runs, c1 is 1832, but c2 is 0. What am I missing?
I recreated this test and found that it may be directly related to the DateTime function.
The query that is generated:
exec sp_executesql N'SELECT [t0].[User_ID], [t0].[Email], [t0].[Password], [t0].[BrandID], [t0].[CustomerID], [t0].[Created], [t0].[EndDate], [t0].[AccountStatusID], [t0].[Verified], [t0].[ResetPasswordFailCount], [t0].[ResetPasswordLocked]
FROM [dbo].[User] AS [t0]
WHERE ((CONVERT(NVarChar,CONVERT(Int,(DATEPART(dw, [t0].[Created]) + (##DATEFIRST) + 6) % 7))) = #p0) AND ([t0].[BrandID] = #p1)',N'#p0 nvarchar(4000),#p1 int',#p0=N'Tuesday',#p1=3
Notice where #p0=N'Tuesday'
Keeping in mind that IQueryable and IEnumerable differ in that where IEnumerable represents an actual .net object, IQueryable converts your expression into an actual SQL statement used to query the database. So any values that you provide in that expression are actually sent to the database.
It is returning 0 results because there is no match. Reason being, the date conversion in SQL returns a 2 instead of 'Tuesday'. You can test this if you replace Tuesday with 2 in your LINQ WHERE clause, it'll actually work. This will work after enumerating it since the results will have been successfully mapped into a usable .net object where the DateTime.DayOfWeek conversion to "Tuesday" will work properly.
Your count is acting on an IQueryable<Event> while e is an enumerated instance. Therefore, some operations might not work as they cannot be translated into SQL [Edit: or will be translated in nonsensical SQL].
To ensure your Where clause works add an AsEnumerable() before it. This converts the IQueryable<Event> into an IEnumerable<Event> and tells the linq provider that it should stop generating SQL at this point.
So, this statement should provide the correct result:
int c2 = events.AsEnumerable()
.Where(e => e.TimeStamp.DayOfWeek.ToString() == "Tuesday")
.Count();
The actual code that cannot be converted causes the problem in SQL is e.TimeStamp.DayOfWeek.ToString().
Alternatively you can use the System.Data.Objects.SqlClient.SqlFunctions (doc here) class to hint the linq provider what it should be doing in SQL.
Edit
As #Servy pointed out this is a Linq to SQL question. However, the problem is quite common so I leave the answer and do not delete it.
On looking at the OP again, there could be another variable to the whole game ... lazy loading.
In the foreach loop the TimeStamp is lazy loaded. In the count query the provider tries to construct an SQL query, during this construction it might not be able to work with ToString (which is known to be problematic) and evaluate e.TimeStamp.DayOfWeek.ToString() to something different than "Tuesday".
The AsEnumerable() forces the provider to stop generating SQL, so that e.TimeStamp is lazy loaded again.
The only way kowing exactly what is going on, is to use tracing tool on the DB (e.g. SQL Server Profiler) to actually see the query (or queries) executed on the server.
Edit 2
Building on #Sinaesthetic's answer the reason why 0 is returned is that the Query tries to compare "4" to "Tuesday" which returns false and therefore the correct result is false.
Can be tested by executing
select ((CONVERT(NVarChar,CONVERT(Int,(DATEPART(dw, getdate()) + (##DATEFIRST) + 6) % 7))))
against the DB.
However, this also shows that it is up to the provider to decide what SQL it generates. Also it is up to the provider to decide what statements it supports and which statements it doesn't.
It also shows that using AsEnumerable to stop the SQL generation process can have an infulence on the semantic evaluation of a query.

LINQ join behaving oddly

I am attempting to perform a join between two tables and limit results by 3 conditions. 2 of the conditions belong to the primary table, the third condition belongs to the secondary table. Here is the query I'm attempting:
var articles = (from article in this.Context.contents
join meta in this.Context.content_meta on article.ID equals meta.contentID
where meta.metaID == 1 && article.content_statusID == 1 && article.date_created > created
orderby article.date_created ascending
select article.content_text_key);
It is meant to join the two tables by the contentID, then filter based on the metaID (type of article), statusID, and then get all articles that are greater than the datetime created. The problem is that it returns 2 records (out of 4 currently). One has a date_created less than created and the other is the record that produced created in the first place (thus equal).
By removing the join and the where clause for the meta, the result produces no records (expected). What I can't understand is that when I translate this join into regular SQL it works just fine. Obviously I'm misunderstanding what the functionality of join is in this context. What would cause this behavior?
Edit:
Having tried this in LinqPad, I've noticed that LinqPad provides the expected results. I have tried these queries separately in code and it isn't until the join is added that odd results begin populating it appears to be happening on any date comparison where the record occurs on the same day as the limiter.
I can't seem to be able to add a comment but in debug mode you should be able to put a break point on this line of code. When you do you should be able to hover over it and have it tell you the sql that LINQ generates. Please post that sql.
At your suggestion, I'm posting my comment as the answer:
"It might also help to see your schema. The data types for metaID,
content_statusID, and date_created might come into play as well -- and
it's easy for me (somebody who's unfamiliar with your code) to make
assumptions about those data types."

Dynamically Generating a Linq/Lambda Where Clause

I've been searching here and Google, but I'm at a loss. I need to let users search a database for reports using a form. If a field on the form has a value, the app will get any reports with that field set to that value. If a field on a form is left blank, the app will ignore it. How can I do this? Ideally, I'd like to just write Where clauses as Strings and add together those that are not empty.
.Where("Id=1")
I've heard this is supposed to work, but I keep getting an error: "could not be resolved in the current scope of context Make sure all referenced variables are in scope...".
Another approach is to pull all the reports then filter it one where clause at a time. I'm hesitant to do this because 1. that's a huge chunk of data over the network and 2. that's a lot of processing on the user side. I'd like to take advantage of the server's processing capabilities. I've heard that it won't query until it's actually requested. So doing something like this
var qry = ctx.Reports
.Select(r => r);
does not actually run the query until I do:
qry.First()
But if I start doing:
qry = qry.Where(r => r.Id = 1).Select(r => r);
qry = qry.Where(r => r.reportDate = '2010/02/02').Select(r => r);
Would that run the query? Since I'm adding a where clause to it. I'd like a simple solution...in the worst case I'd use the Query Builder things...but I'd rather avoid that (seems complex).
Any advice? :)
Linq delays record fetching until a record must be fetched.
That means stacking Where clauses is only adding AND/OR clauses to the query, but still not executing.
Execution of the generated query will be done in the precise moment you try to get a record (First, Any etc), a list of records(ToList()), or enumerate them (foreach).
.Take(N) is not considered fetching records - but adding a (SELECT TOP N / LIMIT N) to the query
No, this will not run the query, you can structure your query this way, and it is actually preferable if it helps readability. You are taking advantage of lazy evaluation in this case.
The query will only run if you enumerate results from it by using i.e. foreach or you force eager evaluation of the query results, i.e. using .ToList() or otherwise force evaluation, i.e evaluate to a single result using i.e First() or Single().
Try checking out this dynamic Linq dll that was released a few years back - it still works just fine and looks to be exactly what you are looking for.

SingleResult<myObject> .ToList or .Count throws an InvalidCastException

I have a stored procedure GetTopRecords(). The sp returns the top 500 records.
I dragged the sp from the server explorer to the LinqtoSql designer surface.
I changed the return type to Record
In code I have:
var x = myDataContext.GetTopRecords();
var y = x.Count();
var z = x.ToList();
either one of the last two lines above throws an InvalidCastException
the type of x is
System.Data.Linq.SqlClient.SqlProvider.SingleResult<Record>
Why am I getting an exception? What am I doing wrong? Please help.
x is not a list, it is single element. As a single element you can't convert it to a list of more than one element or count how many elements it has.
As you say "I changed the return type to Record". Why did you do this?
Since this is L2S (Linq To SQL), I'm assuming it's using Lazy loading, which means that only when calling Count or ToList, which are of immediate execution, the query is performed.
That said, the problem should be related to what you are returning from the GetTopRecords method, which isn't compatible with the Record type.
You should use the L2S designer to check if the result of that method is correctly mapped to a Record data type. Meaning that Record should be a L2S mapped table and GetTopRecords should return data from that table.
Well,can you give details what GetTopRecords() .What it returns?do you need count of top records?
var x = i.ToList();
var y = x.Count();
then it will allowed,but when ever you use tolist() ToList iterates on the query and creates an instance of List<> initialized with all the results of the query.
which is same for count(),becuse count also need all records to perform counting.
the purpose of tolist is to prefilled data with result.
I think there there have been changes to the database schema since the linq to sql classes were generated. therefore there is a mismatch between the database table and the linq to Sql object representing it. therefore, the calls fail because the .NET cannot successfully cast from the db created object to the linq to sql object
I am not sure how I resolved this. But it is fixed now. If someone runs into this problem, please add a note here and I will look up my code and post the proper answer. While I thank the responders above, unfortunately, none of the answers above helped.

LINQ to Entities: Query not working with certain parameter value

I have a very strange problem with a LINQ to Entities query with EF1.
I have a method with a simple query:
public DateTime GetLastSuccessfulRun(string job)
{
var entities = GetEntities();
var query = from jr in entities.JOBRUNS
where jr.JOB_NAME == job && jr.JOB_INFO == "SUCCESS"
orderby jr.JOB_END descending
select jr.JOB_END;
var result = query.ToList().FirstOrDefault();
return result.HasValue ? result.Value : default(DateTime);
}
The method GetEntities returns an instance of a class that is derived from System.Data.Objects.ObjectContext and has automatically been created by the EF designer when I imported the schema of the database.
The query worked just fine for the last 15 or 16 months. And it still runs fine on our test system. In the live system however, there is a strange problem: Depending on the value of the parameter job, it returns the correct results or an empty result set, although there is data it should return.
Anyone ever had a strange case like that? Any ideas what could be the problem?
Some more info:
The database we query against is a Oracle 10g, we are using an enhanced version of the OracleEFProvider v0.2a.
The SQl statement that is returned by ToTraceString works just fine when executed directly via SQL Developer, even with the same parameter that is causing the problem in the LINQ query.
The following also returns the correct result:
entities.JOBRUNS.ToList().Where(x => x.JOB_NAME == job && x.JOB_INFO == "SUCCESS").Count();
The difference here is the call to ToList on the table before applying the where clause. This means two things:
The data is in the database and it is correct.
The problem seems to be the query including the where clause when executed by the EF Provider.
What really stuns me is, that this is a live system and the problem occurred without any changes to the database or the program. One call to that method returned the correct result and the next call five minutes later returned the wrong result. And since then, it only returns the wrong results.
Any hints, suggestions, ideas etc. are welcome, never mind, how far-fetched they seem! Please post them as answers, so I can vote on them, just for the fact for reading my lengthy question and bothering thinking about that strange problem... ;-)
First of all remove ObjectContext caching. Object context internally uses UnitOfWork and IdentityMap patterns. This can have big impact on queries.

Categories

Resources