【问题标题】:Expression tree to SQL with EF Core使用 EF Core 到 SQL 的表达式树
【发布时间】:2018-08-25 12:11:45
【问题描述】:

我们有一列将 JSON 数据存储为字符串。此 JSON 数据通过物化读取并转换为 IDictionary<string, object>。这一切都很好,直到我想过滤它。仅在从数据库中获取数据后才应用过滤。我们将拥有数百万条记录,因此这是不可接受的。显然,我的过滤器作为 WHERE 子句被 EF Core 完全忽略了,因为它可能不知道如何解析 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' 但这仅适用于字符串并且缺少键名。

【问题讨论】:

标签: c# entity-framework-core


【解决方案1】:

在获得“官方”支持之前,您可以使用 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,因此为了欺骗编译器将其“强制转换”为数字,您可以使用如下所示的双重强制转换技术(在 EF Core 2.1.2 中测试和工作):

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

【讨论】:

  • 呵呵。相当黑客!我猜,代码中应该有几英尺的 cmets。
  • @Gert 是的,我很惊讶它确实有效:) 我想映射CONVERTCAST,但它们不适合DbFunction 的定义。所有这些只是为了找到(在您发表评论之后)我们可以使用Convert.ToXyz 方法 - 很酷:)
  • 我会接受你的回答。然而,我目前选择了一种不同的方法,但它是情境性的,因为我们使用的是 IQueryable 资源。我不能强迫最终用户使用这些功能。
【解决方案2】:

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")
            };

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多