StartsWith() doesn't translate to Like('abc%') in LINQ

asp.net-core entity-framework-core linq sql

Question

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

1
5
6/4/2017 4:25:32 PM

Accepted Answer

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.

7
6/5/2017 3:41:08 PM

Popular Answer

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)...


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow