64

"Contains" in Entity Framework core should equivalent to the SQL %like% operator. Therefore "Contains" should be case insensitive however it is case sensitive! (at least in postgres????)

The following only outputs a result when the correct casing for keyword is used.

context.Counties.Where(x => x.Name.Contains(keyword)).ToList();

What am I doing wrong?

001
  • 62,807
  • 94
  • 230
  • 350
  • 17
    LIKE **is** case sensitive in postgresql. – Evk Apr 07 '17 at 12:17
  • Note that there is extension called "citext" which will allow you to use columns of type "citext" comparisions on which are not case-sensitive. Otherwise you should use `lower` explicitly to perform case-insensitive comparision. There is also `ILIKE` in postgresql which is case-insensitive version of `LIKE`. – Evk Apr 07 '17 at 12:23
  • @Evk no wonder! – 001 Apr 07 '17 at 12:26

6 Answers6

106

It used to be the case for older versions of EF core. Now string.Contains is case sensitive, and for exemple for sqlite it maps to sqlite function `instr()' ( I don't know for postgresql).

If you want to compare strings in a case-insensitive way, you have DbFunctions to do the jobs.

context.Counties.Where(x => EF.Functions.Like(x.Name, $"%{keyword}%")).ToList();

UPDATE to @Gert:

A part of the assumption in the question is incorrect. string.Contains does NOT convert into a LIKE expression even though it USED to be the case in ef core versions <= 1.0 (I think).

  • In SQLServer string.contains converts into CHARINDEX(), in oracle and sqlite into instr() which are case sensitive by default UNLESS db or column collation is defined otherwise ( Again, I don't know for postgresql ).
  • In all cases EF.Functions.Like() converts into a SQL LIKE expression which is case-insensitive by default unless db or column collation is defined otherwise.

So yes it all goes down to collation but - correct me if I'm wrong - in a way the code can have an influence on the case-sensitive/insensitive search depending on which one of the above method you use.

Now, I might not be completely up to date but I don't think EF core migrations deal with DB collation naturally and unless you've already created the table manually you will end up with the default collation (case-sensitive for sqlite and I honestly don't know for the others).

Getting back to the original question you have at least 2 options to perform this case-insensitive search if not 3 in a future release :

  1. Specify the column collation on creation using DbContext.OnModelCreating() using this trick
  2. Replace your string.Contains by EF.Functions.Like()
  3. Or wait for a promising feature still in discussion : EF.Functions.Collate() function
DarkUrse
  • 2,084
  • 3
  • 25
  • 33
  • 2
    Not true. It depends solely on the database collation. EF doesn't have *any* influence here, let alone EF *version*. – Gert Arnold Jun 20 '18 at 14:17
  • Collation has influence on this and I am not saying otherwise. However if you don't want to use collation, you have an option which is the one I'm pointing out here. However, let me edit my reply and hopefully I won't be too wrong. – DarkUrse Jun 20 '18 at 20:59
  • OK, go ahead, but there's really no way whatsoever to direct this from the client, else than applying `ToLower`, but that's "cheating". – Gert Arnold Jun 20 '18 at 21:02
  • 2
    `EF.Functions.ILike`... what a PITA it was to find out about that. Thanks. – Jeremy Holovacs Oct 25 '18 at 01:13
  • 2
    What about security; it seems quite possible this is not secure: EF.Functions.Like(x.Name, $"%{keyword}%") – aleksander_si Oct 21 '20 at 11:22
  • It is possible to set both sides to upper or lowercase: context.Counties.Where(x => x.Name.ToUpper().Contains(keyword.ToUpper())).ToList(); – Observer Nov 30 '20 at 17:35
58

My answer will concern NpgSQL.

  1. EF.Functions.Like() in PostgreSQL is case-sensitive, but you can use EF.Functions.ILike() extension method located in Npgsql.EntityFrameworkCore.PostgreSQL assembly.

  2. If you don't have reference to Entity Framework assembly in place where you build query, you can use combination ToLower() and Contains() methods, because Npgsql is able translate ToLower() method to correct SQL

Example:

context.Counties.Where(x => x.Name.ToLower().Contains(keyword.ToLower())).ToList();

About second method keep in mind: you may have performance problems and may encounter problems associated with encoding.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Stas Boyarincev
  • 3,690
  • 23
  • 23
  • 9
    The performance hell exactly what brought me to here, I was using the second method, and I was not able to understand why my query is taking 5 minutes to execute, please be very careful with the second method, the change in the performance is scary. – Hakan Fıstık Aug 18 '20 at 13:39
  • 1
    I am a bit late to the party here, but I was wondering about the performance impact, since the second method seems to work perfectly for me. I inspected the query and I saw it is using `strpos(lower(...))` for the string comparison, which in this post (https://dba.stackexchange.com/questions/89901/lower-vs-ilike-on-postgresql) is mentioned as the faster alternative. Am I missing something here? – aochagavia Sep 29 '21 at 08:47
  • 1
    @HakanFıstık ToLower in postgres will use scan for column that's why its very poor performance. – Dharmeshsharma Oct 08 '21 at 05:55
  • && (A.Adi.ToLower().Contains(request.Name.ToLower())) && (A.Soyadi.ToLower().Contains(request.Surname.ToLower())) – ertugrulakdag Oct 18 '22 at 07:29
10

Use Explicit collation in a query

For example

var customers = context.Customers
.Where(c => EF.Functions.Collate(c.Name, "SQL_Latin1_General_CP1_CS_AS") == "John")
.ToList();

For more details see the msdn link

https://learn.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity

Bibin Gangadharan
  • 1,393
  • 12
  • 13
10

With Entity Framework Core 3.1 and MySQL / MariaDB providers you can manually set the case (in)sensitiveness with StringComparison.InvariantCultureIgnoreCase in the following way:

items = items.Where(i => 
    i.Name.Contains(value, StringComparison.InvariantCultureIgnoreCase));

The default behaviour seems to be case sensitive, however you can explicitly set it using StringComparison.InvariantCulture.

For additional info, check out this post on my blog.

I don't know if it works for previous versions as well (will check and update this answer accordingly).

Darkseal
  • 9,205
  • 8
  • 78
  • 111
  • How's the performance on this one? Is it translated to an efficient query with MySQL? Or does it have to load entries one-by-one to do a comparison on the server? – John Weisz Aug 11 '21 at 12:15
  • 2
    It gets translated like this: `WHERE (LOCATE(CONVERT(LCASE(@__value_0) USING utf8mb4) COLLATE utf8mb4_bin, LCASE(b.Name)) > 0)` – Darkseal Aug 12 '21 at 21:25
  • 1
    Can anyone confirm the oldest version of entity framework that this works on? – rollsch Oct 19 '22 at 04:20
7

Just try it :

You can Lower case field and search value

  context.Counties.Where(x => x.Name.ToLower().Contains(keyword.ToLower())).ToList();

Or you can Upper Case filed and search value

context.Counties.Where(x => x.Name.ToUpper().Contains(keyword.ToUpper())).ToList();
topcool
  • 2,498
  • 7
  • 28
  • 52
  • 7
    This could cause a very big performance issue, especially with PostgreSQL, see the @Stas answer above to avoid the performance disaster that you may face – Hakan Fıstık Aug 18 '20 at 13:41
  • And also there are invariant culture problem you can't pass invariant culture – Mahmood Garibov Jan 27 '22 at 19:56
  • 1
    Just to add to the possible performance issues statement, [from the docs](https://learn.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity): *"doing so [using `string.ToLower` to force a case-insensitive comparison] may prevent your application from using indexes"*. – devklick Nov 19 '22 at 10:42
6

IQueryable.Where is executed in the database, so it is most likely to be case insensitive.

IEnumerable.Where uses C# String.Contains, so it is case sensitive.

Read this answer: Returning IEnumerable vs. IQueryable

Community
  • 1
  • 1
Kfir Guy
  • 2,545
  • 2
  • 14
  • 22
  • 6
    Behaviour of IQueriable would depend on the database. For example, in SQL server it would be case insensitive but for postgres it's case sensitive – Rohin Tak Oct 13 '20 at 10:55