StringComparison.InvariantCultureIgnoreCase cannot be translated while used in a LINQ query - c#

I am facing a problem executing the below query in .NET 6.
query = context.Where(user =>
user.Email.Contains(model.Email,
StringComparison.InvariantCultureIgnoreCase));
After searching the web I understood that EF Core does translate Contains for server-side evaluation - but not the overload that accepts StringComparison.InvariantCultureIgnoreCase or any other StringComparison. But never found the correct way to solve this issue
So I changed the query to something as follows to make it work:
query = context.Where(user =>
user.Email.ToLower().Contains(model.Email.ToLower());
Even though it is working I am not entirely happy with this solution and still wondering which solution solves my problem best. Would using ToLowerInvariant() be a better solution? Any better approach to solve this?
UPDATE
ToLowerInvariant() does not work and causes the same error caused by StringComparison.InvariantCultureIgnoreCase

It seems like your are writing your LINQ query on a DbSet. This is not possible as it cannot be translated to SQL statements.
You could however use the EF.Functions.Like function. This gets translated to the SQL provider and is by default case insensitive.
query = context.Where(user =>
EF.Functions.Like(user.Email, model.Email));

How your query reacts depends on the collation you set on the server side. After all your linq expressions will be translated into an SQL query and how that is interpreted will depend on your database and column settings.
What you could try is stating a collation in your query e.g.
var customers = context.Customers
.Where(c => EF.Functions.Collate(c.Name, "latin1_general_ci collation") == "John")
.ToList();
//SQL_Latin1_General_CP1_CI_AS for SQL Server
//latin1_general_ci collation for MySQL
as found in the Microsoft documentation. Where CI stands for case-insensitive (opposed to CS). Be aware that this query won't be able to leverage the index on the Name due to the custom collation. So it would be better to define it on the column (or table/database).

Try this:
query = context.Where(user => EF.Functions.Collate(user.email,
"SQL_Latin1_General_CP1_CI_AI").Contains(model.Email));

Related

SQLite bitwise query to EF Core Linq version?

So, I know my SQL much better than my EF Core linq, so my starting query was just a FromSQL like this:
var links = context.PortalLinks.FromSql("SELECT * FROM portal_link WHERE grant_bits & {0} AND linkSet_id={1} ORDER BY sortKey",claim,0).ToList();
Now I am trying to translate it into the proper linq style query
var links = context.PortalLinks.Where(x => ((x.GrantBits & claim) != 0) && x.LinkSetId == 0).OrderBy(s => s.SortKey);
I've come up with this, but I am not happy that I am being forced to explicitly test for !=0 when in SQL that is implicit, and while this instance is harmless I can imagine other cases where there can be a more ... amusing ... mismatch between what I can express in C# and what I want to express in SQL.
I guess, is there some other way to express more idiomatic SQL queries for Linq to parse thats less bound by c#'s own assumptions as to what constitutes valid logic?
I am trying to figure what you mean by idiomatic... you can use string interpolation of C# 6.0 inside the query and pass your var:
var links = context.PortalLinks.Where(x => ((x.GrantBits & claim) != Int32.Parse($"{myVar}")) && x.LinkSetId == Int32.Parse($"{myVar2}").OrderBy(s => s.SortKey);
Notice that I use string interpolation as there will be no easy way to achieve this logic with an integer.
you can relate to this post for another source of information.
Edit:
Now that I understand your question I can address your inquiry:
What you describe isn't going to work with native LINQ functionality however!
there is this library which will help you extend the dynamic linq and achieve your goal:
Dynamic LINQ

LINQ Correlated SubQuery throws Oracle Exception: Oracle 11.2.0.4.0 does not support APPLY

Here is the c# query:
qry = qry.Where(comment => idsArr.Any(
selectedId => dbv.VW_STAKEHOLDER_TYPE_XREF.Where(xref => xref.STAKE_ID == comment.STAKE_ID && xref.STAKEHOLDER_TYPE_ID == selectedId).FirstOrDefault() != null
));
From the language of the exception I am guessing that something in the c# is being translated to an apply which Oracle 11 doesn't like for some reason. I am not sure which part of the query is getting translated into APPLY and how I can circumvent this issue.
Thanks in advance.
LINQ to SQL only really works with SQL Server, although some simple queries may work on other databases. In this case the query failed as Oracle does not have a CROSS APPLY clause, but uses lateral joins instead. See this post for options when you want to use LINQ with Oracle: Is there a Way to use Linq to Oracle

LINQ to entities : cannot call a method

I'm aware there are alreay a lot of posts concerning this issue, but i can't seem to find a solution for this.
Here's my Ling to Entities query :
IEnumerable<Tblstamp> changes = (from c in userGSN.edb.Tblstamp
where (c.Ts_Date >= userGSN.DateLastCheck &&
TimeSpan.Parse(c.Ts_Time) >= userGSN.TimeLastCheck)
orderby c.Ts_Id ascending
select c);
I want to compare c.Ts_time to userGSN.TimeLastCheck, but for that I have to convert the c.Ts_Time to a timespan (it is a string, and comes from a database I can't modify, tried everything). I also can't do the converting before the query in an other variable since I can't access it outside of the query.
Obviously, I get an error for trying to use the TimeSpan.Parse method in my query, but I can't find any workaround to this. I have tried using LINQ to Object but since I am really not used to it I couldn't make the equivalent query that i have here.
I am aware of the problem, I'm just trying to find a workaround and need some help please !
EDIT :
So I tried the DateDiff function as suggested :
IEnumerable<Tblstamp> changes = (from c in userGSN.edb.Tblstamp
where (c.Ts_Date >= userGSN.DateLastCheck && SqlFunctions.DateDiff("second",userGSN.TimeLastCheck,c.Ts_Time).Value > 0 )
orderby c.Ts_Id ascending
select c
);
but it gives me the same error : "LINQ to Entities does not recognize the method 'System.Nullable1[System.Int32] DateDiff(System.String, System.Nullable1[System.TimeSpan], System.String)' method, and this method cannot be translated into a store expression."
Even though it clearly says here "You cannot call this function directly. This function can only appear within a LINQ to Entities query.", which is exactly what I'm doing ?!
There's no clean way to do this, so you'll have to think outside the box. Try using SqlFunctions.DateDiff to perform the check. You'll need to adapt it for your usage, but:
SqlFunctions.DateDiff("second", c.Ts_Time, userGSN.TimeLastCheck.ToString()) > 0
There are other methods that you can use, if DateDiff is not suitable. See EntityFunctions as well.
Depending on what you're using to query the data, you might want System.Data.Entity.SqlServer.SqlFunctions instead.
Every code you write in LinQ to get data from database must convert to a valid T-SQL query with valid statements. Not all C# methods have a correspondance in T-SQL so whenever you use them in your code while fatching data you will get errors.
You should either do modifications on the SQL side or use C# spesific functions after fething data from database, while working with entities in memory, or use fıunstions which comply with SQL statements.
If you can't get EF to translate your query to SQL, maybe you could do it yourself? See Writing SQL queries for entities
Linq to Entities does not support all functions since the query has to be translated to SQL:
from c in userGSN.edb.Tblstamp.AsEnumerable()
//now you are allready getting the data from the database
//so be carefull with that because you will have bad performance

Entity Framework LINQ contains not case insensitive

I'm having some 'tear my hair out'-problem with Entity Framework and I just can't find a solution.
What I want to do is compare strings for a search function I'm running on the server. It's basically: collection.Where(c => c.Name.Contains(searchTerm)); where searchTerm is a string passed by the user.
Everywhere I look it's either:
Change both string with toUpper(), or
Set collation to a Case Insensitive one.
However neither of these apply to my case. Here's a similar question which doesn't have an answer: Entity Framework - case insensitive Contains?
Using the first alternative would result in getting every row in the database, and then perform toUpper(), to see if it's a match. This is unacceptable performance-wise.
The second approach seems more likely to be a valid solution, but does for some reason not work. I have two databases. One local and one remote. The remote MSSQL database is set to collation: Finnish_Swedish_CI_AS, which means it's case insensitive? The local database is an auto-generated localDB with the property "Case Sensitive" set to False.
No matter which of these two databases I use it's still always Case Sensitive for the users.
Can someone please explain why this is happening so I can go on with my miserable life?
Kind regards,
Robin Dorbell
It's never been case sensitive for me, but I guess that is just how I set my database up. You can definitely use your first option of converting them both to upper case, EF doesn't pull them into memory to do that, just informs SQL server to do it. For example:
string searchTerm = "Some Text";
dbcontext.Table.Where (t => t.Column.ToLower().Contains(searchTerm.ToLower()));
Produces the following SQL (ish, i did this with linqtosql but EF should be pretty similar):
-- Region Parameters
DECLARE #p0 NVarChar(1000) = '%some text%'
-- EndRegion
SELECT *
FROM [Table] AS [t0]
WHERE LOWER([t0].[Column]) LIKE #p0
From the comments, it sounds like the OP is casting the IQueryable list to an ICollection first, meaning that any subsequent LINQ is running "locally" rather than having the chance to be converted to SQL.
For example,
// Should be IQueryable<T>
ICollection<User> users = context.Users;
// This is executed in code rather than SQL, and so is case SENSITIVE
users = users.Where(c => c.Name.Contains(searchTerm));
This may have helped debug the issue: How do I view the SQL generated by the entity framework?
Use string.Equals
collection.Where(c => string.Equals(c.Name, searchTerm, StringComparison.CurrentCultureIgnoreCase));
Also, you don't have to worry about null and get back only the information you want.
Use StringComparision.CurrentCulture for Case Sensitive.
collection.Where(c => string.Equals(c.Name, searchTerm, StringComparison.CurrentCulture));

Compare Greek (or other non-latin) strings in linq

I am trying to write a query in linq that returns results like this:
PeopleEntities pe = new PeopleEntities();
String fName = "Τάκης";
var people = pe.Person.Where(per => per.FirstName.Equals(fname)).ToList();
When I dump the query using
String query = ((ObjectQuery)pe.Person.Where(per => per.FirstName.Equals(fname))).ToTraceString();
Console.WriteLine(query);
and then using the query in a MySQL WorkBench providing a value, everything works fine but in code the query returns nothing.
Edit: I have connected entity manager by using a MySQL connector. I hadn't thought about it earlier but this is the problem as with an MsSQL connection everything works fine
Any ideas on how I can execute the above?
In a regionally-sensitive query, you should not use string.Equals() without providing a StringComparison value, e.g.:
var people = pe.Person.Where(
per => per.FirstName.Equals(fname, StringComparison.CurrentCultureIgnoreCase));
Yes, this overload is supported in L2E.
If that still does not work, it is possible that the AppDomain's current culture or the DB's collation is wrong. Look at the query/params in SQL Profiler.

Categories