以下是 EF Core 8 将 LINQ 转换为嵌入式集合的示例:
而且效果很好:
public class Person
{
public string Name { get; set; } = null!;
public string Address { get; set; } = null!;
}
DbSet<Person> db;
var searchTerms = new[] { "Search #1", "Search #2", "Search #3"};
var linqQuery = db.Where(a => searchTerms.Contains(a.Address)).ToQueryString();
// DECLARE @__searchTerms_0 nvarchar(4000) = N'["Search #1","Search #2","Search #3"]';
// SELECT
// [a].[Name], [a].[Address]
// FROM
// [PERSON] AS[a]
// WHERE
// [a].[Address] IN(
// SELECT [s].[value]
// FROM OPENJSON(@__searchTerms_0) WITH([value] varchar(8000) '$') AS[s]
// )
但是我无法使用表达式获得相同的结果。
这是我尝试并期望得到的
OPENJSON
:
DbSet<Person> db;
var searchTerms = new[] { "Search #1", "Search #2", "Search #3"};
var propInfo = typeof(Person).GetProperty(nameof(Person.Address))!;
var parameterExp = Expression.Parameter(typeof(Person), "a");
var method = typeof(Enumerable)
.GetMethods()
.First(n => n.Name == "Contains" && n.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(string));
var containsExp = Expression.Call(
genericMethod,
Expression.Constant(searchTerms, typeof(IEnumerable<string>)),
Expression.Property(parameterExp, propInfo));
var predicate = Expression.Lambda<Func<Person, bool>>(containsExp, parameterExp);
var expressionQuery = db.Where(predicate).ToQueryString();
// SELECT
// SELECT[a].[Name], [a].[Address]
// FROM
// [PERSON] AS[a]
// WHERE
// [a].[Address] IN(
// 'Search #1',
// 'Search #2',
// 'Search #3'
// )
这是因为在第一个变体中,您在
searchTerms
数组上使用了闭包,而在第二个变体中,您直接在表达式树中使用了 searchTerms
数组作为常量。
对于常量,EF Core 只是决定生成静态查询
IN
,因为 LINQ 转换器决定此类查询永远不会更改。
我们可以通过定义假持有者类来模拟这种情况:
class ClosureHolder
{
public IEnumerable<string> Value;
}
DbSet<Person> db;
var searchTerms = new[] { "Search #1", "Search #2", "Search #3"};
var propInfo = typeof(Person).GetProperty(nameof(Person.Address))!;
var parameterExp = Expression.Parameter(typeof(Person), "a");
var method = typeof(Enumerable)
.GetMethods()
.First(n => n.Name == "Contains" && n.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(string));
// instead of generating just constant, we generate MemberExpression to constant
var holderExpr = Expression.Constant(new ClosureHolder { Value = searchTerms });
var searchTermsExpr = Expression.Property(holderExpr, nameof(ClosureHolder.Value));
var containsExp = Expression.Call(
genericMethod,
searchTermsExpr,
Expression.Property(parameterExp, propInfo));
var predicate = Expression.Lambda<Func<Person, bool>>(containsExp, parameterExp);
var expressionQuery = db.Where(predicate).ToQueryString();