带有EF Core的SQL的表达式树

时间:2018-08-25 12:11:45

标签: c# entity-framework-core

我们有一列,其中JSON数据存储为字符串。读取此JSON数据,并通过实例化将其转换为IDictionary<string, object>。一切正常,直到我要对其进行过滤。仅在从数据库中获取数据之后才应用过滤。我们将有数百万条记录,因此这是不可接受的。显然,我的过滤器被EF Core完全忽略为WHERE子句,因为它可能不知道如何解析MethodCallExpressions。

我正在寻找一种使与我下面具有表达式树的SQL查询尽可能接近的方法。

我需要进行以下转换:

.Call System.Linq.Queryable.Where(
    .Constant<QueryTranslator`1[Setting]>(QueryTranslator`1[Setting]),
    '(.Lambda #Lambda1<System.Func`2[Setting,System.Boolean]>))

.Lambda #Lambda1<System.Func`2[Setting,System.Boolean]>(Setting $$it)
{
    ((System.Nullable`1[System.Int32]).If (
        $$it.Value != null && .Call ($$it.Value).ContainsKey("Name")
    ) {
        ($$it.Value).Item["Name"]
    } .Else {
        null
    } > (System.Nullable`1[System.Int32]).Constant<Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Int32]>(Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Int32]).TypedProperty)
    == .Constant<System.Nullable`1[System.Boolean]>(True)
}

对此:

SELECT *
FROM [Setting]
WHERE JSON_VALUE([Value], 'lax $.Name') > 1; -- [Value_Name] > 1 is also fine

通过ExpressionVisitor,我成功地接近了 WHERE [Value] ='Something',但这仅适用于字符串,并且缺少键名。

2 个答案:

答案 0 :(得分:6)

在获得“官方”支持之前,您可以使用EF Core 2.0引入的Database scalar function mapping来映射JSON_VALUE

例如,在上下文派生类中或以下单独的静态类中添加以下静态方法:

public static class MyDbFunctions
{
    [DbFunction("JSON_VALUE", "")]
    public static string JsonValue(string source, string path) => throw new NotSupportedException();
}

,如果它在单独的类中,则将以下内容添加到您的上下文OnModelCreating覆盖中(如果该方法在上下文中则不需要):

modelBuilder.HasDbFunction(() => MyDbFunctions.JsonValue(default(string), default(string)));

现在,您可以在类似于EF.Functions的LINQ to Entities查询中使用它。请注意,该函数返回string,因此,为了诱使编译器将其“强制转换”为数字,可以使用下面显示的double cast技术(已在EF Core 2.1.2中进行了测试和工作): / p>

var query = db.Set<Setting>()
    .Where(s => (int)(object)MyDbFunctions.JsonValue(s.Value, "lax $.Name") > 1);

翻译成所需的

WHERE JSON_VALUE([Value], 'lax $.Name') > 1

另一种(可能类型更安全)执行转换的方法是使用Convert类方法(令人惊讶的是SqlServer EF Core提供程序支持):

var query = db.Set<Setting>()
    .Where(s => Convert.ToInt32(MyDbFunctions.JsonValue(s.Value, "lax $.Name")) > 1);

翻译为

WHERE CONVERT(int, JSON_VALUE([Value], 'lax $.Name')) > 1

答案 1 :(得分:0)

Entity Framework Core 3.X中发生了重大变化。 DbFunction.Schema being null or empty string configures it to be in model's default schema

只有在链接中有该示例,我才能够将DBFunction添加到我们的项目中。

MyDbContext内部的功能:

[DbFunction("JSON_VALUE", "dbo")]
public static string JsonValue(string source, string path) => throw new NotSupportedException();

[DbFunction("JSON_QUERY", "dbo")]
public static string JsonQuery(string source, string path) => throw new NotSupportedException();

设置:

modelBuilder
.HasDbFunction(typeof(MyDbContext).GetMethod(nameof(MyDbContext.JsonQuery)))
.HasTranslation(args => SqlFunctionExpression.Create("JSON_QUERY", args, typeof(string), null));
    
modelBuilder
.HasDbFunction(typeof(MyDbContext).GetMethod(nameof(MyDbContext.JsonValue)))
.HasTranslation(args => SqlFunctionExpression.Create("JSON_VALUE", args, typeof(string), null));

使用(简体):

var query = from sometable in _context.SomeEntity
            where MyDbContext.JsonValue(sometable.Data, "$.PrimaryKey.Id") == somevalue
            orderby sometable.Date descending
            select new SomeModel
            {
                SomeJsonArray = MyDbContext.JsonQuery(sometable.Data, "$.Changes")
            };