在 EF Core 中使用表达式树会产生奇怪的 SQL 语句
Using expression trees with EF Core produces strange SQL statements
我正在使用表达式树在运行时动态构建我的表达式,然后在 IQueryable<T>
的 .Where()
子句中使用它,因为我调用了我的数据库。结果 SQL 语句对我来说看起来非常奇怪,我不明白那里发生了什么。
一般信息:项目使用 Framework 4.7.2、EF Core 3.1.3 NuGet
示例(以下是简化的):
考虑这样的客户 Class:
public class Customer{
public string Name {get; set;}
public int Age {get; set;}
public string Address {get; set;}
}
DBContext 已正确设置 DBSet<Customer>
等等。
所以现在我想使用类似 ... db.Customer.Where(expression).ToList() ...
的东西,我必须在运行时构建相应的表达式。该程序将获得用于 List<(string, string)>
之类的搜索条件列表,其中第一个字符串将是 属性 客户的名称,第二个是用于过滤的值。这种传递搜索条件的方式无法更改。
我这样构建我的表达式树,将搜索条件列表转换为字典 (Dictionary<string, List<string>>
),其中 属性 名称作为键和要搜索的实际值的值列表。 typesDictionary 包含有关 属性 类型(名称 -> 字符串等)的信息:
var param = Expression.Parameter(typeof(Customer), "c");
var andList = new List<Expression>();
foreach (var sc in searchCriteria){
var orList = new List<Expression>();
foreach (var value in sc.Value{
var expr = Expression.Equal(
Expression.Property(param, sc.Key),
Expression.Constant(Convert.ChangeType(value, typesDictionary[sc.Key]), typesDictionary[sc.Key]));
orList.Add(expr);
}
andList.Add(orList.Aggregate(Expression.Or));
}
var expression = Expression.Lambda<Func<Customer, bool>>(andList.Aggregate(Expression.And), param);
例如,结果表达式看起来像 c => ((c.Name == "Bob") OR (c.Name == "John")) AND (c.Age == 12)
。有点过度使用括号...
关于 SQL 我希望它是这样的:
SELECT c.Name, c.Age, c.Address
FROM someDB.someSchema.Customers as c
WHERE c.Name = "John" OR c.Name = "Bob" AND c.Age = 13
但是创建的内容类似于:
SELECT [c].[Name], [c].[Aage], [c].[Address]
FROM [Customer] AS [c]
WHERE ((CASE
WHEN [c].[Age] = CAST(12 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END | CASE
WHEN [c].[Age] = CAST(15 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END) | CASE
WHEN [c].[Age] = CAST(22 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END) = CAST(1 AS bit)
这是怎么回事? CASE WHEN
和 CASTS
从哪里来?
你的问题是 Expression.Or
是 bitwise or, so basically |
operator. And Expression.And
is bitwise and (&
)。所以你的表达是:
c => (c.Name == "John" | c.Name == "Bob" ) & c.Age = 13;
你想要的(你会如何手写)是这样的:
c => (c.Name == "John" || c.Name == "Bob" ) && c.Age = 13;
为此你需要使用 Expression.OrElse
和 Expression.AndAlso
:
foreach (var sc in searchCriteria){
var orList = new List<Expression>();
foreach (var value in sc.Value) {
var expr = Expression.Equal(
Expression.Property(param, sc.Key),
Expression.Constant(Convert.ChangeType(value, typesDictionary[sc.Key]), typesDictionary[sc.Key]));
orList.Add(expr);
}
andList.Add(orList.Aggregate(Expression.OrElse));
}
var expression = Expression.Lambda<Func<Customer, bool>>(andList.Aggregate(Expression.AndAlso), param);
之后您应该会生成更多看起来“正常”的 sql 查询。
我正在使用表达式树在运行时动态构建我的表达式,然后在 IQueryable<T>
的 .Where()
子句中使用它,因为我调用了我的数据库。结果 SQL 语句对我来说看起来非常奇怪,我不明白那里发生了什么。
一般信息:项目使用 Framework 4.7.2、EF Core 3.1.3 NuGet
示例(以下是简化的):
考虑这样的客户 Class:
public class Customer{
public string Name {get; set;}
public int Age {get; set;}
public string Address {get; set;}
}
DBContext 已正确设置 DBSet<Customer>
等等。
所以现在我想使用类似 ... db.Customer.Where(expression).ToList() ...
的东西,我必须在运行时构建相应的表达式。该程序将获得用于 List<(string, string)>
之类的搜索条件列表,其中第一个字符串将是 属性 客户的名称,第二个是用于过滤的值。这种传递搜索条件的方式无法更改。
我这样构建我的表达式树,将搜索条件列表转换为字典 (Dictionary<string, List<string>>
),其中 属性 名称作为键和要搜索的实际值的值列表。 typesDictionary 包含有关 属性 类型(名称 -> 字符串等)的信息:
var param = Expression.Parameter(typeof(Customer), "c");
var andList = new List<Expression>();
foreach (var sc in searchCriteria){
var orList = new List<Expression>();
foreach (var value in sc.Value{
var expr = Expression.Equal(
Expression.Property(param, sc.Key),
Expression.Constant(Convert.ChangeType(value, typesDictionary[sc.Key]), typesDictionary[sc.Key]));
orList.Add(expr);
}
andList.Add(orList.Aggregate(Expression.Or));
}
var expression = Expression.Lambda<Func<Customer, bool>>(andList.Aggregate(Expression.And), param);
例如,结果表达式看起来像 c => ((c.Name == "Bob") OR (c.Name == "John")) AND (c.Age == 12)
。有点过度使用括号...
关于 SQL 我希望它是这样的:
SELECT c.Name, c.Age, c.Address
FROM someDB.someSchema.Customers as c
WHERE c.Name = "John" OR c.Name = "Bob" AND c.Age = 13
但是创建的内容类似于:
SELECT [c].[Name], [c].[Aage], [c].[Address]
FROM [Customer] AS [c]
WHERE ((CASE
WHEN [c].[Age] = CAST(12 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END | CASE
WHEN [c].[Age] = CAST(15 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END) | CASE
WHEN [c].[Age] = CAST(22 AS int) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END) = CAST(1 AS bit)
这是怎么回事? CASE WHEN
和 CASTS
从哪里来?
你的问题是 Expression.Or
是 bitwise or, so basically |
operator. And Expression.And
is bitwise and (&
)。所以你的表达是:
c => (c.Name == "John" | c.Name == "Bob" ) & c.Age = 13;
你想要的(你会如何手写)是这样的:
c => (c.Name == "John" || c.Name == "Bob" ) && c.Age = 13;
为此你需要使用 Expression.OrElse
和 Expression.AndAlso
:
foreach (var sc in searchCriteria){
var orList = new List<Expression>();
foreach (var value in sc.Value) {
var expr = Expression.Equal(
Expression.Property(param, sc.Key),
Expression.Constant(Convert.ChangeType(value, typesDictionary[sc.Key]), typesDictionary[sc.Key]));
orList.Add(expr);
}
andList.Add(orList.Aggregate(Expression.OrElse));
}
var expression = Expression.Lambda<Func<Customer, bool>>(andList.Aggregate(Expression.AndAlso), param);
之后您应该会生成更多看起来“正常”的 sql 查询。