Skip to main content

Fixed: EF Core StartsWith Finds Nothing While Contains Works (Case-Sensitive with Oracle's MySql.EntityFrameworkCore)

· 7 min read
Jagdish Kumawat
Founder @ Dewiride

A search for usb with Contains found five products named USB-C …. The same search with StartsWith found none, and neither threw an error. With Oracle's MySQL provider for EF Core, StartsWith and EndsWith compare with a case-sensitive collation, while Contains uses the column's own, which ignores case. EF.Functions.Like gives you a prefix search that follows the column.

What I Was Doing​

In a .NET 10 Web API with EF Core 10, Oracle's MySql.EntityFrameworkCore 10.0.12 and MySQL 9.7, I had two search endpoints on the product name, one for "contains" and one for "starts with":

src/Dewiride.Inventory.Api/Program.cs (temporary endpoints)
app.MapGet("/demo/contains", async (string q, InventoryDbContext db) =>
Results.Ok(await db.Products.Where(p => p.Name.Contains(q)).Select(p => p.Name).ToListAsync()));

app.MapGet("/demo/startswith", async (string q, InventoryDbContext db) =>
Results.Ok(await db.Products.Where(p => p.Name.StartsWith(q)).Select(p => p.Name).ToListAsync()));

I searched for usb, in lowercase. Several product names start with USB-C.

What I Expected​

The same behaviour from both for case: the Name column's collation, utf8mb4_0900_ai_ci, ignores upper and lower case.

What I Got​

Contains found them:

Response (/demo/contains?q=usb)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[
"USB-C cable, 2 m",
"USB-C cable, 1 m",
"USB-C charger, 20 W",
"USB-C charger, 65 W",
"USB-C to USB-A adapter"
]

StartsWith didn't:

Response (/demo/startswith?q=usb)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[]

A terminal where a search for usb with Contains returns five product names starting with USB-C, and the same search with StartsWith returns an empty list.

What I Checked​

Whether USB in capitals works. It does, so case is the difference:

Response (/demo/startswith?q=USB)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[
"USB-C cable, 2 m",
"USB-C cable, 1 m",
"USB-C charger, 20 W",
"USB-C charger, 65 W",
"USB-C to USB-A adapter"
]

The SQL for each. Contains uses LOCATE, which follows the column's collation:

API log (Contains)
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@q='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
SELECT `p`.`Name`
FROM `products` AS `p`
WHERE (@q LIKE '') OR (LOCATE(@q, `p`.`Name`) > 0)

StartsWith has a second condition, with an explicit collation:

API log (StartsWith)
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@q='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
SELECT `p`.`Name`
FROM `products` AS `p`
WHERE (`p`.`Name` LIKE CONCAT(@q, '%')) AND (LEFT(`p`.`Name`, CHAR_LENGTH(CONVERT(@q USING utf8mb4) COLLATE utf8mb4_bin)) = CONVERT(@q USING utf8mb4) COLLATE utf8mb4_bin)

COLLATE utf8mb4_bin compares raw character values, so u and U are different. The first half, LIKE CONCAT(@q, '%'), does ignore case, and would find the rows; the second half then removes them.

EndsWith. It's the same, with RIGHT(…) = … COLLATE utf8mb4_bin, so a search for ADAPTER found nothing:

Response (/demo/endswith?q=ADAPTER)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[]

What EF Core promises. EF Core's collations page says C# equality "is translated directly to SQL equality, which may or may not be case-sensitive, depending on the specific database in use and its collation configuration." For StartsWith and EndsWith, the provider decides, and Oracle's chose this.

Why It Happens​

C#'s string.StartsWith(string) is case-sensitive, so Oracle's provider adds COLLATE utf8mb4_bin to match what the C# would do. Contains is translated to LOCATE with no collation, so it follows the column, which here ignores case. Both are defensible on their own, but side by side the two methods act differently on the same data, with no error to warn you.

The Fix​

Use EF.Functions.Like, which translates directly to SQL's LIKE and follows the column's collation:

An endpoint with a case-insensitive prefix search
app.MapGet("/demo/like", async (string q, InventoryDbContext db) =>
Results.Ok(await db.Products.Where(p => EF.Functions.Like(p.Name, q)).Select(p => p.Name).ToListAsync()));

The caller passes the pattern, with % for "anything". For a search box, build it in code: EF.Functions.Like(p.Name, search + "%").

If your users type % or _, those act as wildcards in LIKE, so escape them, or keep using Contains, which treats them as plain characters.

After the Fix​

Response (/demo/like?q=usb%25)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[
"USB-C cable, 2 m",
"USB-C cable, 1 m",
"USB-C charger, 20 W",
"USB-C charger, 65 W",
"USB-C to USB-A adapter"
]
API log
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@q='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
SELECT `p`.`Name`
FROM `products` AS `p`
WHERE `p`.`Name` LIKE @q

A terminal where a search for usb% with EF.Functions.Like returns the five product names starting with USB-C, with a plain LIKE statement in the log.

LIKE 'usb%' can also use an index on the column, unlike Contains, which has to check every row.

Common Mistakes​

MistakeWhat happensDo this instead
Assuming StartsWith acts like ContainsLowercase searches find nothingUse EF.Functions.Like, or test both
p.Name.ToLower().StartsWith(q.ToLower())It works, but no index can helpUse EF.Functions.Like
Passing StringComparison.OrdinalIgnoreCaseAn exception from this providerSee that post
Letting users type raw LIKE patterns% matches everythingEscape them, or use Contains

Troubleshooting​

SymptomLikely causeFix
StartsWith returns nothing, and Contains finds rowsCase differs, and the provider compares exactlyEF.Functions.Like
EndsWith returns nothingThe sameEF.Functions.Like(p.Name, "%" + search)
Contains matches accented letters tooThe ai_ci collation ignores accentsUse a different collation if that matters
A search with % finds everythingLIKE treats it as a wildcardEscape it

FAQ​

Is this a bug?​

Oracle's Connector/NET pages on EF Core don't mention it, so I can't say whether it's intended. It matches what C# does, and it's different from what Contains does. Check it again when you update the provider: these results are from 10.0.12.

Does Pomelo do the same?​

I only tested Oracle's provider, because Pomelo has no version for EF Core 10. Oracle MySql.EntityFrameworkCore vs Pomelo covers the choice.

Conclusion​

With Oracle's MySQL provider, StartsWith and EndsWith add COLLATE utf8mb4_bin, so they're case-sensitive, while Contains follows the column's collation and usually isn't. Lowercase searches then quietly return nothing. Use EF.Functions.Like for a prefix search that follows the column, and can use its index.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.