EF Core 8 - 使用表达式将查询转换为嵌入式集合 (OPENJSON)

问题描述 投票:0回答:1

以下是 EF Core 8 将 LINQ 转换为嵌入式集合的示例:

https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#translate-queries-into-embedded-collections

而且效果很好:

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'
//    )
.net-core entity-framework-core expression open-json ef-core-8.0
1个回答
0
投票

这是因为在第一个变体中,您在

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();
© www.soinside.com 2019 - 2024. All rights reserved.