I have the following asp.net core LINQ code:
List<UserSearchResult> results = await db.ApplicationUsers.Where(u => u.Name.StartsWith(name) && !u.Deleted && u.AppearInSearch)
.OrderByDescending(u => u.Verified)
.ThenBy(u => u.DateAdded) // Added to prevent duplication of results in different pages
.Skip(page * recordsInPage)
.Take(recordsInPage)
.Select(u => new UserSearchResult()
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
}).ToListAsync();
Unfortunately this translates to the following:
SELECT [t].[Name], [t].[Verified], [t].[PhotoURL], [t].[Id], [t].[Subdomain] FROM ( SELECT [u0].* FROM [AspNetUsers] AS [u0] WHERE ((([u0].[Name] LIKE @__name_0 + N'%' AND (CHARINDEX(@__name_0, [u0].[Name]) = 1)) OR (@__name_0 = N'')) AND ([u0].[Deleted] = 0)) AND ([u0].[AppearInSearch] = 1) ORDER BY [u0].[Verified] DESC, [u0].[DateAdded] OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY ) AS [t]
I wonder why it has this part:
(CHARINDEX(@__name_0, [u0].[Name]) = 1)) OR (@__name_0 = N''))
and not only LIKE
Thanks a lot
Entity Framework provides special function EF.Functions.Like to use it for LINQ expressions with standard SQL LIKE syntax. For StartWith pattern an expression will be like this:
var likeExpression = name+"%";
... await db.ApplicationUsers.Where(u => EF.Functions.Like(u.Name,likeExpression)...
The rules for SQL translation in EF Core are still unclear, far from perfect, under discussion and are changing with every even minor release.
The translation of StartsWith
, EndsWith
and Contains
has been discussed and changed several times - for instance, issue #474: Query: Improve translation of String's StartsWith, EndsWith and Contains). The translation of StartsWith
has been even changed in the latest official v1.1.2 release, so the v1.1.1 translation
(CHARINDEX(@__name_0, [u0].[Name]) = 1)) OR (@__name_0 = N''))
now will be something like
[u0].[Name] LIKE @__name_0 + '%' AND (CHARINDEX(@__name_0, [u0].[Name]) = 1)) OR (@__name_0 = N''))
The idea is with LIKE
condition to allow query optimizer to use the index, and then do the slow filtering with the second condition like before (it's all about handling correctly (similar to C#) the wildcard characters inside the search string as well as empty search string).
So you may try upgrading and see if it helps. The upcoming v2 when released will provide more natural support for db specific operators like LIKE
etc.
Another workaround currently (if the above is really the performance bottleneck) is to build the query filtering part directly with SQL and the rest with LINQ (in contrast with EF6, EF Core allows that):
var results = await db.ApplicationUsers
//.Where(u => u.Name.StartsWith(name) && !u.Deleted && u.AppearInSearch)
.FromSql("select * from ApplicationUsers where Name like {0}", name + "%")
.Where(!u.Deleted && u.AppearInSearch)
.OrderByDescending(u => u.Verified)
.ThenBy(u => u.DateAdded) // Added to prevent duplication of results in different pages
.Skip(page * recordsInPage)
.Take(recordsInPage)
.Select(u => new UserSearchResult()
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
}).ToListAsync();
Note that FromSql
method supports parameters, so SQL injection should not be a concern. Still you need to know the table name, column names and the concrete database SQL syntax - something that is supposed to be abstracted by the ORM for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With