LINQ compiled query struggling with nullable int - c#

I have the following compiled Linq query:
public static readonly Func<DBContext, Models.User, Type, ObjectType, int?, UserNotification> GetUnreadNotificationID =
CompiledQuery.Compile((DBContext db, Models.User forUser, Type notificationType, ObjectType forObjectType, int? forObjectID) =>
db.UserNotifications.FirstOrDefault(c =>
c.ForUserID == forUser.ID
&& c.ForObjectTypeID == (short)forObjectType
&& c.ForObjectID == forObjectID
&& c.TypeID == (byte)notificationType
&& c.Date > forUser.NotificationsLastRead.Date));
Note the parameter int? forObjectID.
In query profiler, an example executed SQL statement would be:
exec sp_executesql N'SELECT TOP (1)
[t0].[ID], [t0].[TypeID], [t0].[ForUserID], [t0].[Date], [t0].[ForObjectTypeID], [t0].[ForObjectID], [t0].[Count]
FROM
[dbo].[UserNotifications] AS [t0]
WHERE
([t0].[ForUserID] = #p0)
AND ([t0].[ForObjectTypeID] = #p1)
AND ([t0].[ForObjectID] = #p2)
AND ([t0].[TypeID] = #p3)
AND ([t0].[Date] > #p4)',
N'#p0 int,#p1 int,#p2 int,#p3 int,#p4 datetime',#p0=77812,#p1=5,#p2=NULL,#p3=4,#p4='2018-01-24 13:18:44.107'
When forObjectID is null, the query does not return the expected records. If I change:
AND ([t0].[ForObjectID] = #p2)
To:
AND ([t0].[ForObjectID] IS NULL)
It does return the correct results.
Why is null not handled in the way I would expect it to?
Is there an easy fix? (I can convert the table to not accept nulls for that field and default to 0 but feels icky)

If this is Linq2Sql
change
c.ForObjectID == forObjectID to Object.Equals(c.ForObjectID, forObjectID)
for it to be able to translate to is null when forObjectID is null.

The easy fix would be to write a SQL stored procedure which does what you want -
I doubt that anyone will fix LinkToSql.
Or try this instead:
((c.ForObjectID == forObjectID) ||
(c.ForObjectId == null && forObjectId == null))

Related

Entity Framework 6 and is null

I would like to get all lines
from a table where column: CODEFIN (varchar) not like "MIR" or "CED".
My table looks like this:
CODEFIN
ID
NULL
2
NULL
3
MIR
2
My Entity Framwork linq request is:
db.MYTABLE.Where(m => m.CODEFIN !="MIR" && m.CODEFIN != "CED") [...]
But CODEFIN null lines not appear...
I get the generated sql, but this is the same things in my sql editor.
the sql is like this:
SELECT
[Extent1].[CODEFIN] AS [CODEFIN],
[Extent1].[ID] AS [ID],
FROM [dbo].[MYTABLE] AS [Extent1]
WHERE ('MIR' <> [Extent1].[CODEFIN] AND 'CED' <> [Extent1].[CODEFIN])
Thank you.
Sylvain
I would do it as
db.MYTABLE.Where(m => m.CODEFIN == NULL ||
(m.CODEFIN !="MIR" && m.CODEFIN != "CED"))
or use Contains:
db.MYTABLE.Where(m => m.CODEFIN == NULL ||
!(new [] {"MIR", "CED"}.Contains(m.CODEFIN)))
Which would yield the SQL:
WHERE [Extent1].[CODEFIN] IS NULL OR ([Extent1].[CODEFIN] NOT IN ('MIR', 'CED'))
Comparing something (even NULL) to NULL always yields NULL, not FALSE, and NOT(NULL) is still NULL, not TRUE, so you have to be more explicit about checking for nulls.
Add:
&& m.CODEFIN == null
A sometimes more efficient solution is to use Except
db.MYTABLE.Where(m => new [] {"MIR", "CED"}
.Except(new[] {m.CODEFIN})
.Any())
This should hopefully result in the following SQL
WHERE EXISTS (
SELECT 'MIR'
UNION
SELECT 'CED'
EXCEPT
SELECT [Extent1].[CODEFIN]
)
This is pretty efficient, as shown here.

Big execution time for a simple request with Entity Framework

I have a weird behaviour from Entity Framework 6. I have a simple (a simple where and a simple select) query which takes 30s.
I used Sql Profiler to watch what sql code is executed. I am using a Where then the FirstOrDefault method to get an item. Then I tried another query, I did a ToList (to fetch data) then FirstOrDefault and it takes less than 1 second.
Original code (takes 30s to be executed):
-----------------------------------------
id = Container.SocialNetworks.Where(a => a.SocialNetwork == EnumSocialNetwork.LinkedIn && a.Link == linkedinurl && a.User.TenantID == Container.TenantId).Select(i => i.UserID).FirstOrDefault();
From SQL Profiler :
-------------------
exec sp_executesql N'SELECT
[Limit1].[UserID] AS [UserID]
FROM ( SELECT TOP (1)
[Extent1].[UserID] AS [UserID]
FROM [dbo].[SocialNetworks] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[ID]
WHERE (0 = [Extent1].[SocialNetwork]) AND (([Extent1].[Link] = #p__linq__0) OR (([Extent1].[Link] IS NULL) AND (#p__linq__0 IS NULL))) AND ([Extent2].[TenantID] = #p__linq__1)
) AS [Limit1]',N'#p__linq__0 nvarchar(4000),#p__linq__1 int',#p__linq__0=N'linkedin.com/in/a-profile',#p__linq__1=5
After testing another solutions (takes less than 1s):
-----------------------------------------------------
id = Container.SocialNetworks.Where(a => a.SocialNetwork == EnumSocialNetwork.LinkedIn && a.Link == linkedinurl && a.User.TenantID == Container.TenantId).Select(i => i.UserID).ToList().FirstOrDefault();
From SQL Profiler:
------------------
exec sp_executesql N'SELECT
[Extent1].[UserID] AS [UserID]
FROM [dbo].[SocialNetworks] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[ID]
WHERE (0 = [Extent1].[SocialNetwork]) AND (([Extent1].[Link] = #p__linq__0) OR (([Extent1].[Link] IS NULL) AND (#p__linq__0 IS NULL))) AND ([Extent2].[TenantID] = #p__linq__1)',N'#p__linq__0 nvarchar(4000),#p__linq__1 int',#p__linq__0=N'linkedin.com/in/a-profile-as',#p__linq__1=5
As you can see, I use ToList to fetch data before filtering with FirstOrDefault. And, normally, it is not advisable to do a ToList, a eager load. Why Entity Framework put a select into a select when I use FirstOrDefault ?
I am sorry for my english and I hope I explained properly my issue.
EDIT :
I have something interesting to add, when the "linkedinurl" value does not exist, and only when it does not exist, in the database, both queries take less than 1 second.
EDIT 2:
After writing a comment, I would like to add that our database is on Azure. And the problem does not appear on a simple SQLEXPRESS database. Moreover, this issue appeared like 4 or 5 days ago.
That is because you use FirstOrDefault AFTER the where().Select() combination.
The first query would work better like this :
id = Container.SocialNetworks.FirstOrDefault(a => a.SocialNetwork == EnumSocialNetwork.LinkedIn && a.Link == linkedinurl && a.User.TenantID == Container.TenantId)?.UserID;
As you can see, I use FirstOrDefault just like you used your Where, but this will load the entire object, as discussed in the comments.
Why is your seconde query faster ? Because you ended the query with a ToList() so the FirstOrDefault part apply only in your c# code, AFTER the lines have been loaded, not on the DB with a double select.
Edit :
Trying these 2 lines might highlight the root cause better :
1. Try to order your set :
id = Container.SocialNetworks
.Where(a => a.SocialNetwork == EnumSocialNetwork.LinkedIn && a.Link == linkedinurl && a.User.TenantID == Container.TenantId)
.OrderBy(t => t.UserID).Select(i => i.UserID).FirstOrDefault();
2. Use an aggregate function :
id = Container.SocialNetworks
.Where(a => a.SocialNetwork == EnumSocialNetwork.LinkedIn && a.Link == linkedinurl && a.User.TenantID == Container.TenantId)
.Min(i => i.UserID);

Entity Framework 6 related entity where datetime clause doesn't translate to SQL

How do I get the date filter into the generated SQL query without access to database context?
EF makes some very expensive queries. I don't cast or use the results as IEnumerable, so I would expect EF to make an efficient query. I would expect it to use the start and stop times to filter the results in SQL.
EF translates the following
context.Channels
.First(ch => ch.Channel_ID == id)
.ChannelValues
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop);
into
SELECT TOP (1)
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ChannelType_ID] AS [ChannelType_ID],
[Extent1].[Name] AS [Name],
FROM [dbo].[Channel] AS [Extent1]
WHERE [Extent1].[Channel_ID] = #p__linq__0
p__linq__0: '1' (Type = Int32, IsNullable = false)
SELECT
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ValueTime] AS [ValueTime],
[Extent1].[Value] AS [Value]
FROM [dbo].[ChannelValue] AS [Extent1]
WHERE [Extent1].[Channel_ID] = #EntityKeyValue1
EntityKeyValue1: '1' (Type = Int32, IsNullable = false)
The first SQL query I'm happy with, but the second should create a query similar to what the following does
context.ChannelValues
.Where(cv => cv.Channel_ID == id && start < cv.ValueTime && cv.ValueTime <= stop);
it results in
SELECT
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ValueTime] AS [ValueTime],
[Extent1].[Value] AS [Value]
FROM [dbo].[ChannelValue] AS [Extent1]
WHERE ([Extent1].[Channel_ID] = #p__linq__0) AND (#p__linq__1 < [Extent1].[ValueTime]) AND ([Extent1].[ValueTime] <= #p__linq__2)
p__linq__0: '1' (Type = Int32, IsNullable = false)
p__linq__1: '7/1/2018 12:00:00 AM' (Type = DateTime2, IsNullable = false)
p__linq__2: '7/23/2018 11:45:00 AM' (Type = DateTime2, IsNullable = false
Where I actually need this I don't have access to the the DatabaseContext.
I don't think the second part of the query is getting passed to the SQL at all.
This tells SQL to return all the ChannelValues with the Channel_ID equal to id.
context.Channels
.First(ch => ch.Channel_ID == id)
.ChannelValues
The second where clause is then executed in memory.
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop);
To execute all of this in the database you will need to do something like this:
context.Channels
.Where(ch => ch.Channel_ID == id)
.SelectMany(x => x.ChannelValues)
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop)
Though I don't know if this answers you question how to do this without access to the database context
"EF makes some very expensive queries" mmm you have control of how you would like to use the framework with its limitation*, if you know something is bad but still do it that way its not EF fault.
You could do it exactly the way you have.
i.e. if you did this with raw sql how would you do it.
Then simply do the same in EF.
var channel = context.Channels.First(x=> x.Channel_ID == id)
channel.ChannelValues = context.ChannelValues.Where(x => x.Channel_ID == id
&& start < x.ValueTime
&& x.ValueTime <= stop
).ToList();

How do I translate this SQL query to Lambda or LINQ that uses (where is null)

I've been trying to modify some rows of data in SQL to test in my application and I've noticed my query in Lambda brings back 0 rows when I am expecting 2387 row. The source of the problem is I am using parenthesis in a WHERE clause in SQL to look at some null values. This is the SQL query:
SQL
-- THIS WORKS!
select * from vwAppsWithIssues
where fld1stCheckAllocatedTo = 'nicholasg' and fldStage = 1
and (fldStopStartDate is null or fldStopEndDate is not null)
-- The query was originally this (doesn't return rows)
select * from vwAppsWithIssues
where fld1stCheckAllocatedTo = 'nicholasg' and fldStage = 1
and (fldStopStartDate = null or fldStopEndDate <> null)
LAMBDA query that returns 0 rows
public static int GetApplicationsFirstCount(string UserId)
{
try
{
using (IME_CheckOffEntities IME_CheckOffEntities = new IME_CheckOffEntities())
{
return IME_CheckOffEntities.vwAppsWithIssues
.Where(a => a.fld1stCheckAllocatedTo == UserId && a.fldStage == 1 && (a.fldStopStartDate == null || a.fldStopEndDate != null))
.ToList().Count;
}
}
catch (Exception ex)
{
throw;
}
}
Update
Using LINQPad I have written this expression:
VwAppsWithIssues
.Where (v => v.Fld1stCheckAllocatedTo == "nicholasg"
&& v.FldStage == 1
&& (v.FldStopStartDate == null || v.FldStopEndDate != null)).Count()
that generates this sql
SELECT COUNT(*) AS [value]
FROM [vwAppsWithIssues] AS [t0]
WHERE ([t0].[fld1stCheckAllocatedTo] = #p0) AND ([t0].[fldStage] = #p1) AND (([t0].[fldStopStartDate] IS NULL) OR ([t0].[fldStopEndDate] IS NOT NULL))
So now that I have some lambda that I think will work, I simply copy it to visual studio.
var count = IME_CheckOffEntities.vwAppsWithIssues
.Where(v => v.fld1stCheckAllocatedTo == "nicholasg" && v.fldStage == 1 && (v.fldStopStartDate == null || v.fldStopEndDate != null)).Count();
It still returns only 0 rows?! I am passing in the right userId in C# as well.
My count in c# also returns 0 rows. Any idea how I can rewrite this C# query?
from linqpad, on one of my schema
from
f in Files
where
f.PubDate == null || f.FilingDate != null
select
f.IdFile
is translated as follow
SELECT
[Extent1].[idFichier] AS [idFichier]
FROM [dbo].[tableF] AS [Extent1]
WHERE ([Extent1].[datePubliF] IS NULL) OR ([Extent1].[dateDepotF] IS NOT NULL)
so, in your case, are you, for example, sure of the UserId value ?

Nullable Guid in Linq to Sql Yields Unexpected Result

I have a simple SQL table which defines a set of hierarchical categories and sub-categories - note the ParentCategoryId can be null for 'top-level' categories...
CREATE TABLE [dbo].[Category](
[CategoryId] [uniqueidentifier] NOT NULL,
[ParentCategoryId] [uniqueidentifier] NULL,
[Name] [nvarchar](50) NOT NULL
)
If I then construct a Linq expression to find a particular category by Name and ParentCategoryId, I find that I cannot get the correct result if I set a Guid? variable to null:
Guid? parentCategoryId = null;
var category = dc.Categories
.Where(c => (
(c.Name == "Fred") &&
(c.ParentCategoryId == parentCategoryId)
));
This does not yield the same result as:
var category = dc.Categories
.Where(c => (
(c.Name == "Fred") &&
(c.ParentCategoryId == null)
));
From what I can find on the web, others have had this problem, but I haven't been able to find a clean workaround to fix the problem.
Any ideas would be much appreciated. Thanks.
Additional Information
Here is the LINQ generated SQL statements for firstly a Guid? null parameter and then for a simple null parameter:
-- With Guid? null parameter : return an empty record set
DECLARE #p0 NVarChar(1000) SET #p0 = 'Fred'
DECLARE #p1 UniqueIdentifier SET #p1 = null
SELECT [t0].[CategoryId], [t0].[ParentCategoryId], [t0].[Name], [t0].[Timestamp]
FROM [dbo].[cad_ScoCategory] AS [t0]
WHERE ([t0].[Name] = #p0) AND ([t0].[ParentCategoryId] = #p1)
-- With null parameter - returns a single (correct) record
DECLARE #p0 NVarChar(1000) SET #p0 = 'Fred'
SELECT [t0].[CategoryId], [t0].[ParentCategoryId], [t0].[Name], [t0].[Timestamp]
FROM [dbo].[cad_ScoCategory] AS [t0]
WHERE ([t0].[Name] = #p0) AND ([t0].[ParentCategoryId] IS NULL)
As you can see, the first option compares the ParentCategoryId with a nulled paraemeter where as the second method checks ParentCategoryId IS NULL - which is correct
No, this is a reasonably common problem unfortunately. The workaround is to explicitly compare with null:
.Where(c => c.Name == Fred &&
((c.ParentCategoryId == parentCategoryId) ||
(c.ParentCategoryId == null && parentCategoryId == null)))
Alternatively, perform the check outside the query (to change which filter is used):
var category = dc.Categories.Where(c => c.Name == "Fred");
category = parentCategoryId == null
? category.Where(c => c.ParentCategoryId == null)
: category.Where(c => c.ParentCategoryId == categoryId);

Categories