When I use two conditions joined by an OR, the result is not correct for SQL Server.
How can I fix it?
This is my LINQ code and result in SQL (that reflection created for me):
query.Where(p => ((p.Code == "100000") Or p.Code.EndsWith("200")))
query.Where(p => (p.year == "2015"))}
I added this where clause at runtime, now I add another extension method and it's not working:
query.sum(p => p.value)
Exception:
An exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code
Additional information: An expression of non-boolean type specified in
a context where a condition is expected, near 'AND'.
SQL translated:
SELECT SUM([e].[Value])
FROM [acc].[Data161] AS [e]
WHERE (CASE
WHEN RIGHT([e].[Code], LEN(N'201')) = N'201'
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END |
CASE
WHEN RIGHT([e].[Code], LEN(N'199')) = N'199'
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END)
AND ([e].[SetadCode] = N'161')
The correct SQL should have = 1 before the AND.
But without sum its works fine and add a = 1 to SQL command
First off, the Or is not a valid C# operator
.Where(p => ((p.Code == "100000") Or p.Code.EndsWith("200")))
If you change it to ||, the query translates correctly and executes w/o issue.
Looking at the generated SQL, I'm pretty sure you have used the bitwise or operator (|) instead, in which case I get the same error. While this could be a EF Core translator bug, you shouldn't be using it anyway - use the logical or || operator and the generated SQL will not have all that CASE WHEN expressions, but a typical simple WHERE conditions.
You can use || instead of or
query.Where(p => ((p.Code == "100000") || p.Code.EndsWith("200")))
This is too long for a comment, but does not address the linq part of the question.
I would expect the where clause to look like this:
where (code = '100000' or p.code like '%200') and (p.year = '2015')
All that bit manipulation that is generated is jarring. Note that the above code is ANSI-standard SQL as well.
Related
I am writing a query for a repository service for an inventory table. FYI: We are using C# 7.0, EF 6, and we are using Moq for testing our queries.
I learned that when string.Contains(...), which is by default case sensitive, is put into a LINQ query and then converted to SQL, the result is case insensitive (found other SO posts to help with that and we'll deal with it), and I also found that the string.Contains(...) functions seems to have a quirk when the argument is string.Empty and is converted to lower case (found no SO posts about this).
Attempts to use the case-insensitive string.Contains(...) overloads are beaten back with an exception when LINQ to Entities attempts to convert to SQL, so I have to manually specify column.Contains(argument.ToLower()) in order for both the the LINQ to Entities' SQL query to operate as intended and for the mocked-up unit test for case-insensitivity to pass.
Problem: If the argument is string.Empty, nothing is matched. The culprit is when then argument is converted to lower case.
This is not a roadblock (simply moving the argument.ToLower() check outside the query solved the issue, and it'd be a tad more efficient anyway), but I still want to know what's up.
public List<InventoryModel> FindByTrackingNumberSubstring( string substring )
{
// (bad) matches nothing when argument is string.Empty
//var query = _modelTable.Where( entity => entity.Tracking_Number.ToLower().Contains( substring.ToLower() ) );
// (good) matches everything when argument is string.Empty
string lower = substring.ToLower();
var query = _modelTable.Where( entity => entity.Tracking_Number.ToLower().Contains( lower ) );
return query.ToList<InventoryModel>();
}
// SQL for queries 1 and 2, respectively (stripped out SELECT and FROM stuff for brevity)
WHERE ((CASE WHEN (( CAST(CHARINDEX(LOWER(#p__linq__0), LOWER([Extent1].[Tracking Number])) AS int)) > 0) THEN cast(1 as bit) WHEN ( NOT (( CAST(CHARINDEX(LOWER(#p__linq__0), LOWER([Extent1].[Tracking Number])) AS int)) > 0)) THEN cast(0 as bit) END) = 1)
WHERE ((CASE WHEN (LOWER([Extent1].[Tracking Number]) LIKE #p__linq__0 ESCAPE N'~') THEN cast(1 as bit) WHEN ( NOT (LOWER([Extent1].[Tracking Number]) LIKE #p__linq__0 ESCAPE N'~')) THEN cast(0 as bit) END) = 1)
I did some checking and found that, in the LINQ to Entities' SQL query, that string.Contains(string.Empty) matches anything, and I found that string.Empty.ToLower() == string.Empty match anything, but put these two together and C# and LINQ to Entities diverge. In the former, string.Contains(string.Empty.ToLower()) matches anything (as expected), but in the latter matches nothing..
Why?
I believe this would be a quirk of the SQL Server provider for EF in that when you perform the .ToLower() on the criteria and the field being compared it is recognizing the request as explicitly case-insensitive and replaces the LIKE query with the CHARINDEX comparison which does not handle empty strings in SQL Server the same way. The behaviour for case sensitivity will depend on the database engine, and in the case of SQL Server, the collation selected for strings in the database. Not sure why a LOWER(Tracking_Number) LIKE LOWER('%%') couldn't have been used.
Personally, when composing EF Linq expressions, my querying code will always inspect for IsNullOrEmpty on strings and not append .Where() conditions where an actual criteria has not been supplied. This way WHERE clauses are only applied for provided criteria.
I.e. If I trust the DB won't be collated case-sensitive:
if(!string.IsNullOrEmpty(substring))
query = query.Where(entity => entity.Tracking_Number.Contains(substring));
if I am concerned that the database could be collated case-sensitive:
if(!string.IsNullOrEmpty(substring))
query = query.Where( entity => entity.Tracking_Number.ToLower().Contains(substring.ToLower()));
Even there I would prefer to set a standard that Tracking_Number is always stored as a lower-case value if the database serves solely this application. The entity properties would enforce that any set value is lower-cased. (removing the need for .Tracking_Number.ToLower() in the queries.)
I have the following query:
var query = from item in Session.Query<FactuurItem>()
where item.EnergieType == etype
&& (item.DienstType == null || item.DienstType == DienstType.Onbekend || item.DienstType == dtype)
&& item.IsActive == true
orderby item.Naam
select item;
Which is converted to the following SQL:
select * from [FactuurItem] factuurite0_
where
factuurite0_.EnergieType=?
and (factuurite0_.DienstType is null or factuurite0_.DienstType=? or factuurite0_.DienstType=?)
and case when factuurite0_.IsActive=1 then 'true' else 'false' end=case when ?='true' then 'true' else 'false' end
order by factuurite0_.Naam asc
Which results in the Exception:
{"Unable to cast object of type 'System.Boolean' to type 'System.String'."}
Now for my question: why??
The original query looks ok to me. The SQL, however, does not. Where do the two case-statements originate from? Apparently it tries to convert the property IsActive to a string in SQL, which it fails to do.
EDIT
Ok, found the solution. Nothing wrong with mapping etc., just with how the LINQ query is translated to SQL. In particular, how this line is translated:
&& item.IsActive == true
Somehow, this gets translated into the complex CASE-statement which ultimately results in the exception message. However, the == true-part isn't really necessary. By removing it, the translator no longer gets confused and provides the proper SQL:
factuurite0_.IsActive=1
No more CASE-statement and no more exception.
Ok, found the solution. Nothing wrong with mapping etc., just with how the LINQ query is translated to SQL. In particular, how this line is translated:
&& item.IsActive == true
Somehow, this gets translated into the complex CASE-statement which ultimately results in the exception message. However, the == true-part isn't really necessary. By removing it, the translator no longer gets confused and provides the proper SQL:
factuurite0_.IsActive=1
No more CASE-statement and no more exception.
Using Log4Net at the debug level? In some version of Hibernate and Log4Net there is an incompatibility when turn on logging at the DEBUG level. All you get is this error about 'unable to execute sql cannot cast boolean to string'. Try turning up your logging level to INFO and the problem should go away.
In my code I have the following fragment of a L2E query:
where ol.ordhead.ohcustno == login && (ol.ollastdoctype == "IN") && ol.olstatus == "9"
This translates to following SQL fragment:
WHERE ([Extent8].[ohcustno] = #p__linq__1) AND (''IN'' = [Extent7].[ollastdoctype]) AND (''9'' = [Extent7].[olstatus]) ...
On a certain input the query executes 3 seconds. I change the query this way:
where ol.ordhead.ohcustno == login && (ol.ollastdoctype == "IN" || ol.ollastdoctype == "CR") && ol.olstatus == "9"
and the resulting SQL changes are as follows:
WHERE ([Extent6].[ohcustno] = #p__linq__1) AND ([Extent5].[ollastdoctype] IN (N''IN'',N''CR'')) AND (''9'' = [Extent5].[olstatus]) ...
Note, that for some bizarre reason Entity Framework decided to convert my IN and CR to unicode. The result is that the query now executes 6 seconds on the same input. If I manually remove the N prefix from the IN clause and re-run query in SSMS the execution time goes back to 3 seconds. This is of course because SQL Server Query Optimizer can't get advantage of an index because compared types are now different (varchar vs nvarchar)
Can anyone explain me why Entity Framework all of a sudden decides to convert my constants to unicode and how can I avoid it?
you can try this method EntityFunction.AsNonUnicode, as follow
where ol.ordhead.ohcustno == login &&
(ol.ollastdoctype == EntityFunctions.AsNonUnicode("IN") ||
ol.ollastdoctype == EntityFunctions.AsNonUnicode("CR")) &&
ol.olstatus == "9"
This is only last hope, next is report bug to microsoft.
The EntityFunction.AsNonUnicode workaround is actually quite limited, it only works when the value supplied is either a literal or a string:
System.NotSupportedException: The method 'System.String
AsNonUnicode(System.String)' is only supported in LINQ to Entities
when the argument is a string variable or literal.
This is a serious problem in EF4.1 and has been documented here as well:
http://connect.microsoft.com/VisualStudio/feedback/details/650410/entity-framework-contains-still-defaulting-to-unicode-for-varchar-fields
Until this is fixed in EF itself, there is no workaround short of intercepting the query and manually replacing the syntax using something like EFTraceProvider.
Brutal.
This issue has been officially resolved in the latest EF versions. You can define the column type using DataAnnotations. Hope this helps someone!
See this answer: EF Data Annotations Column Type
This was a problem till ODP.net Beta 2 release but with Beta3 release of ODP.net 4.112.2.50 this problem is solved.
in the following query
var restrictions = from p in dcTrad.quop_restricted_items
where p.entry_status == 'P' && p.batch == "PRODUCTION" && p.problem != null
from q in dcTrad.model_companies
where q.co_name != null && p.brimsec == q.primary_bsec
select new { Company = q.co_name, Restriction = p.comment ?? "Restricted without comments", Portfolio = p.problem };
I need to replace
p.brimsec == q.primary_bsec
with
p.brimsec.StartsWith ( q.primary_bsec )
but I get the following error:
Only arguments that can be evaluated on the client are supported for the String.StartsWith method
How can I make this work?
Unfortunately the LINQ to SQL translator is not smart enough to translate this code, but there's a trick that achieves the same:
p.brimsec.StartsWith ( q.primary_bsec )
Translates to:
p.brimsec.SubString(0, q.primary_bsec.Length) == q.primary_bsec
The LINQ to SQL translator handles this just fine, and the semantics are equivalent to StartsWith.
Frankly, I don't see why translating StartsWith properly for server-side arguments was so hard that the LINQ developers just decided to throw an error instead.
Basically the linq to sql does not know how to convert startswith to Sql. This is because internally at run time your linq is code generated to sql.
You may go about achieving this by creating a UDF (user defined function in sql) and using it from your linq statement.
The article is as below:
http://msdn.microsoft.com/en-us/library/bb399416.aspx
Andrew
I think the problem you're running into is that linq-to-sql has no translation of String.StartsWith into SQL. String.Contains does work, though - you'd need to go through your resultant collection and filter out the items that don't start with q.primary_bsec.
I need to filter my queries by dates but I don't care in this case about time portion of it that is stored in SQL Database.
I first tried to something like
var now = DateTime.Now.Date;
Where(x => x.CreatedDate.Date.Compare(now) == 0)
but this seems to all get locally checked making the query slow. How can I do this without making it do the check locally?
I am pretty much trying to just find all results that would say have happened today(2020-01-06).
There are a limited number of methods you can use on translatable types when constructing your Lambda / Linq expressions. This is because each method would need additional code so that it could be translated into a sql store expression. It means that you must check that any methods you want to use and expect to be translated into a sql store expression are supported.
In this case the DateTime.Compare is not supported.
The easiest thing to do here is a simple range comparison because the time is included in your persisted value.
var start = DateTime.Now.Date;
var end = start.AddDays(1);
Where(x => x.CreatedDate >= start && x.CreatedDate < end)
This will result in a sargable query.
Use
var now = DateTime.Now.Date
...WHERE(CreatedDate.Date == now)
I just checked that above translates to the following SQL query:
WHERE ((CONVERT(date, [x].[CreatedDate]) = '2019-01-07T00:00:00.000')
I used this (link) method to see what LINQ translates to