Reputation: 360
I'm wetting my feet with EF Core on .NET 3.1. I've the following code excerpt:
public static async Task<Task> getWithName(HttpContext c) {
var name = c.Request.RouteValues["name"].ToString();
// with API - WORKS!
var authors = await DB.Authors.Where(a => a.first_name.Contains(name)).ToListAsync();
// with raw SQL interpolated - BROKEN
var authors2 = await DB.Authors.FromSqlInterpolated($"SELECT * FROM author WHERE first_name like '%{name}%'").ToListAsync();
// with raw SQL parametrized - BROKEN
var authors3 = await DB.Authors.FromSqlRaw("SELECT * FROM author WHERE first_name like '%{0}%'", name).ToListAsync();
// with LINQ expressions - WORKS!
var authors4 = await (from a in DB.Authors where a.first_name.Contains(name) select a).ToListAsync();
c.Response.ContentType = "application/json";
return c.Response.WriteAsync(Serialize(authors));
}
beside the missing context around this method, what confuses me is that both the fluent API version and the LINQ query expression version work as expected, returning the 12 items in the DB. On the opposite both the interpolated and raw SQL fail returning 0 items.
Plese, mind: it is not that I get an exception or any erorr. They simply return 0 results as if the query was wrong.
To make a raw test, I've just put a breakpoint, copied the name value and performed the query straight into pgAdmin. The query works as expected, returning the same 12 items.
This is the (rather complex) debug output of the 4 queries you see in the code:
#this is the DB init debug output
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 3.1.4 initialized 'AuthorContext' using provider
'Npgsql.EntityFrameworkCore.PostgreSQL' with options: None
#this is the fluent API query, which works
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (14ms) [Parameters=[@__name_0='?'], CommandType='Text', CommandTimeout='30']
SELECT a.id, a.first_name, a.last_name, a.nationality
FROM public.author AS a
WHERE (@__name_0 = '') OR (STRPOS(a.first_name, @__name_0) > 0)
#this is the raw SQL, which fails
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (7ms) [Parameters=[p0='?'], CommandType='Text', CommandTimeout='30']
SELECT * FROM author WHERE first_name like '%@p0%'
#this is the interpolated SQL, which fails too
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[p0='?'], CommandType='Text', CommandTimeout='30']
SELECT * FROM author WHERE first_name like '%@p0%'
#this is the LINQ expression query, which works
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__name_0='?'], CommandType='Text', CommandTimeout='30']
SELECT a.id, a.first_name, a.last_name, a.nationality
FROM public.author AS a
WHERE (@__name_0 = '') OR (STRPOS(a.first_name, @__name_0) > 0)
I see that the LINQ/Fluent version transpiles in a rather strange SQL I'm unable to understand, but unfortunately I do not also understand why the code doesn't expand correctly my param in the raw/interpolated SQL.
Thank you for any hint!
Upvotes: 5
Views: 2803
Reputation: 1624
Change this part:
var authors2 = await DB.Authors.FromSqlInterpolated($"SELECT * FROM author WHERE first_name like '%{name}%'").ToListAsync();
to:
var authors2 = await DB.Authors.FromSqlInterpolated($"SELECT * FROM author WHERE first_name like '%' || {name} || '%' ").ToListAsync();
Reasons:
Upvotes: 0
Reputation: 360
OK,
I've got it! And in the while ErikEJ answered in the comments.
TL; DR: basically you need to include the wildcards into the original C# variable
In my original code there were a number of issues:
SELECT * FROM author WHERE first_name like '%matteo%'
Anyway, any DB connection layer is possibly oriented towards prepared statements rather than raw queries, for that reason I've tried this new code in SQL:
DEALLOCATE foo;
PREPARE foo (text) AS
SELECT * FROM author WHERE first_name like '%$1%';
execute foo('matteo');
this fails also in SQL!
DEALLOCATE foo;
PREPARE foo (text) AS
SELECT * FROM author WHERE first_name like $1;
execute foo('%matteo%');
So I've tried this code:
public static async Task<Task> getWithName(HttpContext c) {
var name = c.Request.RouteValues["name"].ToString();
var name2 = "'%"+name+"%'"; //<- please notice the single quote for SQL strings!
// with API - WORKS!
var authors = await DB.Authors.Where(a => a.first_name.Contains(name)).ToListAsync();
// with raw SQL interpolated - BROKEN
var authors2 = await DB.Authors.FromSqlInterpolated($"SELECT * FROM author WHERE first_name like {name2}").ToListAsync();
// with raw SQL parametrized - BROKEN
var authors3 = await DB.Authors.FromSqlRaw("SELECT * FROM author WHERE first_name like {0}", name2).ToListAsync();
// with LINQ expressions - WORKS!
var authors4 = await (from a in DB.Authors where a.first_name.Contains(name) select a).ToListAsync();
c.Response.ContentType = "application/json";
return c.Response.WriteAsync(Serialize(authors));
}
unfortunately this failed again.
DEALLOCATE foo;
PREPARE foo (text) AS
SELECT * FROM author WHERE first_name like $1;
execute foo(''%matteo%''); //<- double single quote caused by the EF Core automatic cast.
So the solution to the problem has been:
public static async Task<Task> getWithName(HttpContext c) {
var name = c.Request.RouteValues["name"].ToString();
var name2 = "%"+name+"%"; //<- please notice: NO MORE single quote!
// with API - WORKS!
var authors = await DB.Authors.Where(a => a.first_name.Contains(name)).ToListAsync();
// with raw SQL interpolated - BROKEN
var authors2 = await DB.Authors.FromSqlInterpolated($"SELECT * FROM author WHERE first_name like {name2}").ToListAsync();
// with raw SQL parametrized - BROKEN
var authors3 = await DB.Authors.FromSqlRaw("SELECT * FROM author WHERE first_name like {0}", name2).ToListAsync();
// with LINQ expressions - WORKS!
var authors4 = await (from a in DB.Authors where a.first_name.Contains(name) select a).ToListAsync();
c.Response.ContentType = "application/json";
return c.Response.WriteAsync(Serialize(authors));
}
basically you need to include the wildcards into the original C# variable
NEW Q.: is there no other solution then this ugly wildcard inclusion?!
Upvotes: 6