Why does LINQ-to-SQL generate this SQL statement for a comparison - linq

if I have a LINQ to SQL query like this:
var query = from x in db.XTable
select x.y == 123; // the y column is nullable!
List<bool> list = query.ToList();
it will generate a SQL statement that will contain this:
(CASE
WHEN [t0].[y] = 123 THEN 1
WHEN NOT ([t0].[y] = 123) THEN 0
ELSE NULL
END)
which will throw an error cause null could not get assigned to bool. I know why this happens (because a comparison in SQL with null is always false) but I don't know why LINQ to SQL does not use a stetement like this:
(CASE
WHEN [t0].[y] = 123 THEN 1
ELSE 0
END)
which would work.
Can I push LINQ to SQL to do this?

Probably, as you state, because you must think the SQL way with linq to sql, not the object way...
It might be considered as a bug or as a feature, by the way...
Especially with null values.
For example, concatenation of nullable string is different in linq to sql and linq to objects.
Assuming a and b are strings :
from n in db
select n.a + n.b
in linq to sql, if a is null and b is not, a + b = null
in linq to object if a is null anb b is not a + b = b
to get the same result in linq to sql, you'll have to use the coalesce operator select (a ?? string.Empty) + b
Anyway, you can either return a list of Nullable<bool> and a list of bool which would be :
from x in db.XTable
select x.y != null && x.y == 123
or
from x in db.XTable
select (x.y ?? 0) == 123
But to get what you want in linq to objects, you would have to do
from x in db.XTable.ToList()
select (x.y== null ? (bool?)null : x.y== 123))
EDIT
This might change in future versions (not sure if the given case will be included)

Related

Transform Sql to EF Core Linq query

I am trying to translate the following query from SQL to EF Core. I can easily just use a stored procedure (I already have the SQL), but am trying to learn how some of the linq queries work. Unfortunately this is not by any means an ideal database schema that I inherited and I don't have the time to convert it to something better.
DECLARE #userId INT = 3
SELECT *
FROM dbo.CardGamePairs
WHERE EXISTS (SELECT 1
FROM dbo.Users
WHERE Users.Id = CardGamePairs.player1Id
AND Users.userId = #userId)
UNION
SELECT *
FROM dbo.CardGamePairs
WHERE EXISTS (SELECT 1
FROM dbo.Users
WHERE Users.Id = TableB.player2Id
AND Users.userId = #userId)
So basically I have an id that can exist in one of two separate columns in table b and I don't know in which column it may be in, but I need all rows that have that ID in either column. The following is what I tried to make this work:
//Find data from table A where id matches (part of the subquery from above)
var userResults = _userRepository.GetAllAsQueryable(x => x.userId == userId).ToList();
//Get data from table b
var cardGamePairsResults = _cardGamePairsRepository.GetAllAsQueryable(x => userResults .Any(y => y.userId == x.player1Id || y.userId == x.player2Id));
When I run the code above I get this error message:
predicate: (y) => y.userId == x.player1Id || y.userId == x.player2Id))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
Any ideas on how I can make this work? (I tried changing the column and table names to something that would actually make sense, hopefully I didn't miss any spots and make it more confusing.)
Because you are already have user id use it to query both columns.
var userResults = _userRepository
.GetAllAsQueryable(x => x.userId == userId)
.ToList();
var cardGamePairsResults = _cardGamePairsRepository
.GetAllAsQueryable(x => x.player1Id == userId || x.player2Id == userId));

BETWEEN operator for string in LINQ to SQL query

from p in table
where ID == 201
&& date => 20160601
&& date <= 20160901
select {ID, name};
q.Dump();
The date in the database is in string simple format.
I am trying to convert a SQL query to LINQ. In SQL, BETWEEN operator is being used to select values within a given range. But, BETWEEN can't be used with LINQ statement, so I am getting an error which says => cannot be applied to operands of type string and int for the date field. Any help would be appropriated. I tried the DateTime, but it didn't work for me.
Since LINQ to Entities doesn't support Convert.ToDateTime (why not?) and your date formats are in a reasonable string format, you can compare as strings:
from p in table
where ID == 201
&& date.CompareTo("20160601") >= 0
&& date.CompareTo("20160901") <= 0
select { ID, name };
Try this working code:
var q = from p in table
where p.ID == 201
&& p.date >= DateTime.ParseExact("20160601", "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)
&& p.date <= DateTime.ParseExact("20160901", "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)
select p;
q.Dump();

How can I get distinct results from this LINQ query?

Below is my ERD and sample data. Note, I'm using Entity Framework and Code first to control my database.
For the project named Vacation, return all the DISTINCT users who have a "true" value in UserBooleanAttributes table for either the Parents or Teens rows defined in the UserAttributes table.
Here is my current attempt:
var myQuery =
from P in context.Projects
join UA in context.UserAttributes on P.ProjectID equals UA.ProjectID
join UBA in context.UserBooleanAttributes on UA.UserAttributeID equals UBA.UserAttributeID
join U in context.Users on UBA.UserID equals U.UserID
where P.ProjectID == 1
where UBA.Value == true
where (UA.UserAttributeID == 1 || UA.UserAttributeID == 2)
select new { uba = U };
This returns 6 users, with e#acme.org being listed twice. Is there a LINQ way of returning distinct values? I suppose I could convert this to a list then filter, but I'd rather have the Database do the work.
I'd rather avoid using lambda expressions if possible. Once again, I want the database to do the work, and not have to write code to union/intersect result groups.

LINQ query (or lambda expression) to return records that match a list

I have a list of strings (converted from Guid) that contains the ID's of items I want to pull from my table.
Then, in my LINQ query, I am trying to figure out how to do an in clause to pull records that are in that list.
Here is the LINQ
var RegionRequests = (from r in db.course_requests
where PendingIdList.Contains(r.request_state.ToString())
select r).ToList();
It builds, but I get a run error: "System.NotSupportedException: LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression".
I would prefer to compare guid to guid, but that gets me nowhere.
Can this be converted to a lambda expression? If that is best, how?
LINQ to Entites tries to convert your expression to an SQL Statement. Your server didn't know the stored procedure ToString().
Fix:
var regionRequests =
from r in db.course_requests.ToList()
where PendingIdList.Contains(r.request_state.ToString())
select r;
With db.course_requests.ToList() you force LINQ to materialize your database data (if big table, you gonna have a bad time) and the ToString() is executed in the object context.
You stated: I have a list of strings (converted from Guid) ...
Can you NOT convert them into strings and keep it as a List< System.Guid>?? Then you can do this (assuming PendingIdGuidList is List< System.Guid>:
var regionRequets = (from r in db.course_requests
join p in PendingIdGuidList on u.request_state equals p
select r).ToList();
Edited to add:
I ran a test on this using the following code:
var db = new EntityModels.MapleCreekEntities();
List<System.Guid> PendingIdGuidList =
new List<System.Guid>() {
System.Guid.Parse("77dfd79e-2d61-40b9-ac23-36eb53dc55bc"),
System.Guid.Parse("cd409b96-de92-4fd7-8870-aa42eb5b8751")
};
var regionRequets = (from r in db.Users
join p in PendingIdGuidList on r.Test equals p
select r).ToList();
Users is a table in my database. I added a column called Test as a Uniqueidentifier data type, then modified 2 records with the following Guids.
I know it's not exactly a 1:1 of what the OP is doing, but pretty close. Here is the profiled SQL statement:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[UserLogin] AS [UserLogin],
[Extent1].[Password] AS [Password],
[Extent1].[Test] AS [Test]
FROM [dbo].[Users] AS [Extent1]
INNER JOIN (SELECT
cast('77dfd79e-2d61-40b9-ac23-36eb53dc55bc' as uniqueidentifier) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
cast('cd409b96-de92-4fd7-8870-aa42eb5b8751' as uniqueidentifier) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1] ON [Extent1].[Test] = [UnionAll1].[C1]

Convert this SQL query to Linq (Not Exists + sub query)

I would like this SQL to be converted to LINQ. (it shouldl select rows from input which do not exist in table production based on 3 columns. If a column in both tables contains NULL, it should be considered as having the same value)
SELECT i.* FROM INPUT AS i
WHERE NOT EXISTS
(SELECT p.Agent FROM Production AS p
WHERE ISNULL(i.CustID,'') <> ISNULL(p.CustID,'')
AND ISNULL(i.CustName,'') <> ISNULL(p.CustName,'')
AND ISNULL(i.household,'') <> ISNULL(p.Household,''))
First of all - this is not a good SQL query. Every column is wrapped in a non-sargable function which means that the engine won't be able to take advantage of any indexes on any of those columns (assuming you have any).
Let's start by rewriting this as a semi-decent SQL query:
SELECT i.*
FROM Input i
LEFT JOIN Production p
ON (p.CustID = i.CustID OR (p.CustID IS NULL AND i.CustID IS NULL))
AND (p.CustName = i.CustName OR (p.CustName IS NULL AND i.CustName IS NULL))
AND (p.Household = i.Household OR
(p.Household IS NULL AND i.Household IS NULL))
WHERE p.CustID IS NULL
Now having said this, LEFT JOIN / IS NULL is not great for efficiency either, but we don't have much choice here because we're comparing on multiple columns. Based on your column names, I'm starting to wonder if the schema is properly normalized. A CustID should most likely be associated with one and only one CustName - the fact that you have to compare both of these seems a bit odd. And Household - I'm not sure what that is, but if it's a varchar(x)/nvarchar(x) column then I wonder if it might also have a 1:1 relationship with the customer.
If I'm speculating too much here then feel free to dismiss this paragraph; but just in case, I want to say that if this data isn't properly normalized, normalizing it would make it much easier and faster to query on:
SELECT *
FROM Input
WHERE CustID NOT IN (SELECT CustID FROM Production)
Anyway, going back to the first query, since that's what we have to work with for now. Unfortunately it's impossible to create a join on those specific conditions in Linq, so we need to rewrite the SQL query as something slightly worse (because we now have to read from Input twice):
SELECT *
FROM Input
WHERE <Primary Key> NOT IN
(
SELECT i.<Primary Key>
FROM Input i
INNER JOIN Production p
ON (p.CustID = i.CustID OR (p.CustID IS NULL AND i.CustID IS NULL))
AND (p.CustName = i.CustName OR (p.CustName IS NULL AND i.CustName IS NULL))
AND (p.Household = i.Household OR
(p.Household IS NULL AND i.Household IS NULL))
)
Now we have something we can finally translate to Linq syntax. We still can't do the join explicitly, which would be best, but we go old-school, start from the cartesian join and toss the join conditions into the WHERE segment, and the server will still be able to sort it out:
var excluded =
from i in input
from p in production
where
((p.CustID == i.CustID) || ((p.CustID == null) && (i.CustID == null))) &&
((p.CustName == i.CustName) ||
((p.CustName == null) && (i.CustName == null))) &&
((p.Household == i.Household) ||
((p.Household == null) && (i.Household == null)));
select i.PrimaryKey;
var results =
from i in input
where !excluded.Contains(i.PrimaryKey)
select i;
I'm assuming here that you have some sort of primary key on the table. If you don't, you've got other problems, but you can get around this particular problem using EXCEPT:
var excluded =
from i in input
from p in production
where
((p.CustID == i.CustID) || ((p.CustID == null) && (i.CustID == null))) &&
((p.CustName == i.CustName) ||
((p.CustName == null) && (i.CustName == null))) &&
((p.Household == i.Household) ||
((p.Household == null) && (i.Household == null)));
select i;
var results = input.Except(excluded);

Resources