Nullable Guid in Linq to Sql Yields Unexpected Result - c#

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);

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);

LINQ compiled query struggling with nullable int

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))

Convert SQL to EF Linq

I have the following query:
SELECT COUNT(1)
FROM Warehouse.WorkItems wi
WHERE wi.TaskId = (SELECT TaskId
FROM Warehouse.WorkItems
WHERE WorkItemId = #WorkItemId)
AND wi.IsComplete = 0;
And since we are using EF, I'd like to be able to use the Linq functionality to generate this query. (I know that I can give it a string query like this, but I would like to use EF+Linq to generate the query for me, for refactoring reasons.)
I really don't need to know the results of the query. I just need to know if there are any results. (The use of an Any() would be perfect, but I can't get the write code for it.)
So... Basically, how do I write that SQL query as a LINQ query?
Edit: Table Structure
WorkItemId - int - Primary Key
TaskId - int - Foreign Key on Warehouse.Tasks
IsComplete - bool
JobId - int
UserName - string
ReportName - string
ReportCriteria - string
ReportId - int - Foreign Key on Warehouse.Reports
CreatedTime - DateTime
The direct translation could be something like this
var result = db.WorkItems.Any(wi =>
!wi.IsComplete && wi.TaskId == db.WorkItems
.Where(x => x.WorkItemId == workItemId)
.Select(x => x.TaskId)
.FirstOrDefault()));
Taking into account the fact that SQL =(subquery), IN (subquery) and EXISTS(subquery) in nowadays modern databases are handled identically, you can try this instead
var result = db.WorkItems.Any(wi =>
!wi.IsComplete && db.WorkItems.Any(x => x.WorkItemId == workItemId
&& x.TaskId == wi.TaskId));
Turns out that I just needed to approach the problem from a different angle.
I came up with about three solutions with varying Linq syntaxes:
Full method chain:
var q1 = Warehouse.WorkItems
.Where(workItem => workItem.TaskId == (from wis in Warehouse.WorkItems
where wis.WorkItemId == workItemId
select wis.TaskId).First())
.Any(workItem => !workItem.IsComplete);
Mixed query + method chain:
var q2 = Warehouse.WorkItems
.Where(workItem => workItem.TaskId == Warehouse.WorkItems
.Where(wis => wis.WorkItemId == workItemId)
.Select(wis => wis.TaskId)
.First())
.Any(workItem => !workItem.IsComplete);
Full query:
var q3 = (from wi in Warehouse.WorkItems
where wi.TaskId == (from swi in Warehouse.WorkItems
where swi.WorkItemId == workItemId
select swi.TaskId).First()
where !wi.IsComplete
select 1).Any();
The only problems with this is that it comes up with some really jacked up SQL:
SELECT
(CASE
WHEN EXISTS(
SELECT NULL AS [EMPTY]
FROM [Warehouse].[WorkItems] AS [t0]
WHERE (NOT ([t0].[IsComplete] = 1)) AND ([t0].[TaskId] = ((
SELECT TOP (1) [t1].[TaskId]
FROM [Warehouse].[WorkItems] AS [t1]
WHERE [t1].[WorkItemId] = #p0
)))
) THEN 1
ELSE 0
END) AS [value]
You can use the Any() function like so:
var result = Warehouse.WorkItems.Any(x => x.WorkItemId != null);
In short, you pass in your condition, which in this case is checking whether or not any of the items in your collection have an ID
The variable result will tell you whether or not all items in your collection have ID's.
Here's a helpful webpage to help you get started with LINQ: http://www.dotnetperls.com/linq
Subquery in the original SQL was a useless one, thus not a good sample for Any() usage. It is simply:
SELECT COUNT(*)
FROM Warehouse.WorkItems wi
WHERE WorkItemId = #WorkItemId
AND wi.IsComplete = 0;
It looks like, since the result would be 0 or 1 only, guessing the purpose and based on seeking how to write Any(), it may be written as:
SELECT CASE WHEN EXISTS ( SELECT *
FROM Warehouse.WorkItems wi
WHERE WorkItemId = #WorkItemId AND
wi.IsComplete = 0 ) THEN 1
ELSE 0
END;
Then it makes sense to use Any():
bool exists = db.WorkItems.Any( wi => wi.WorkItemId == workItemId & !wi.IsComplete );
EDIT: I misread the original query in a hurry, sorry. Here is an update on the Linq usage:
bool exists = db.WorkItems.Any( wi =>
db.WorkItems
.SingleOrDefault(wi.WorkItemId == workItemId).TaskId == wi.TaskId
&& !wi.IsComplete );
If the count was needed as in the original SQL:
var count = db.WorkItems.Count( wi =>
db.WorkItems
.SingleOrDefault(wi.WorkItemId == workItemId).TaskId == wi.TaskId
&& !wi.IsComplete );
Sorry again for the confusion.

Conditional linq query

What would be the best approach for converting this SQL to linq?
I've earlier made views in the database based on such an sql, and then query the view with linq. But I've would like to know if there are other approaches.
The SQL finds the assigned object for an task. The task table contains three foreign key columns, as the assigned to may be from Department, Position or a Person. Only one is allowed.
SQL:
SELECT id,
title,
assigned_to = (case
when idAssignedDepartment is not null then (select description from department where id = idAssignedDepartment)
when idAssignedPosition is not null then (select description from position where id = idAssignedPosition )
when idAssignedPerson is not null then (select fullname from person where id = idAssignedPerson )
end)
FROM task
Using LinqToEF
You can write it like this:
var q = from t in task
from dep in department.Where(x => x.id == t.idAssignedDepartment).DefaultIfEmpty()
from pos in position.Where(x => x.id == t.idAssignedPosition).DefaultIfEmpty()
from per in person .Where(x => x.id == t.idAssignedPerson).DefaultIfEmpty()
select new
{
t.id,
t.title,
assigned_to = t.idAssignedDepartment != null ? dep.description :
t.idAssignedPosition != null ? pos.description :
t.idAssignedPerson != null ? per.fullname :
null
};

Categories