【问题标题】:Parameterized Query from an Expression Tree in Entity Framework CoreEntity Framework Core 中表达式树的参数化查询
【发布时间】:2020-03-03 20:42:33
【问题描述】:

我正在尝试在通用存储库(.NET Core 3.1 + EF Core 3.1)中实现动态过滤器 通过构建表达式树,但生成的 SQL 查询永远不会参数化(我正在通过 appsettings.json 中的"Microsoft.EntityFrameworkCore.Database.Command": "Information" 验证生成的查询,并在 Startup.cs 中有 EnableSensitiveDataLogging

构建表达式树的代码如下(为简单起见,仅在此处使用字符串值):

    public static IQueryable<T> WhereEquals<T>(IQueryable<T> query, string propertyName, object propertyValue)
    {
        var pe = Expression.Parameter(typeof(T));

        var property = Expression.PropertyOrField(pe, propertyName);
        var value = Expression.Constant(propertyValue);

        var predicateBody = Expression.Equal(
            property,
            value
        );

        var whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new[] { typeof(T) },
            query.Expression,
            Expression.Lambda<Func<T, bool>>(predicateBody, new ParameterExpression[] { pe })
        );

        return query.Provider.CreateQuery<T>(whereCallExpression);
    }

该方法有效,但值总是包含在生成的 SQL 查询中,我担心它会导致 SQL 注入。

这是一个生成的查询示例:

Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (33ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [p].[Id], [p].[Name], [p].[FirstName], [p].[Created], [p].[CreatedBy], [p].[Updated], [p].[UpdatedBy]
FROM [Persons] AS [p]
WHERE [p].[Name] = N'smith'

从 EF 团队成员 (@divega) 那里找到了可能的答案: Force Entity Framework to use SQL parameterization for better SQL proc cache reuse, 管理它使用 Where 方法,但生成的 SQL 仍然是一样的。

尝试使用 System.Linq.Dynamic.Core, 但它有同样的问题(生成的 SQL 查询没有参数化)。

有没有办法强制 Entity Framework Core 从表达式树生成参数化查询?

【问题讨论】:

标签: c# .net-core linq-to-sql entity-framework-core


【解决方案1】:

您提供的链接解释了 EF 对变量值使用 SQL 参数,因此如果您创建变量引用(在 C# 中始终是字段引用),而不是为传入的值创建 Expression.Constant,然后你会得到一个参数化的查询。最简单的解决方案似乎是复制编译器如何处理 lambda 外部范围变量引用,即创建一个类对象来保存该值并引用它。

Expression.Constant 不同,要获取object 参数的实际类型并不容易,因此将其更改为泛型类型:

public static class IQueryableExt {
    private sealed class holdPropertyValue<T> {
        public T v;
    }

    public static IQueryable<T> WhereEquals<T, TValue>(this IQueryable<T> query, string propertyName, TValue propertyValue) {
        // p
        var pe = Expression.Parameter(typeof(T), "p");

        // p.{propertyName}
        var property = Expression.PropertyOrField(pe, propertyName);
        var holdpv = new holdPropertyValue<TValue> { v = propertyValue };
        // holdpv.v
        var value = Expression.PropertyOrField(Expression.Constant(holdpv), "v");

        // p.{propertyName} == holdpv.v
        var whereBody = Expression.Equal(property, value);
        // p => p.{propertyName} == holdpv.v
        var whereLambda = Expression.Lambda<Func<T, bool>>(whereBody, pe);

        // Queryable.Where(query, p => p.{propertyName} == holdpv.v)
        var whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new[] { typeof(T) },
            query.Expression,
            whereLambda
        );

        // query.Where(p => p.{propertyName} == holdpv.v)
        return query.Provider.CreateQuery<T>(whereCallExpression);
    }
}

如果您需要传入object,则添加转换为正确类型(这不会影响生成的SQL)更简单,而不是动态创建holdPropertyValue的正确类型并分配它是一个值,所以:

public static IQueryable<T> WhereEquals2<T>(this IQueryable<T> query, string propertyName, object propertyValue) {
    // p
    var pe = Expression.Parameter(typeof(T), "p");
    // p.{propertyName}
    var property = Expression.PropertyOrField(pe, propertyName);

    var holdpv = new holdPropertyValue<object> { v = propertyValue };
    // Convert.ChangeType(holdpv.v, p.{propertyName}.GetType())
    var value = Expression.Convert(Expression.PropertyOrField(Expression.Constant(holdpv), "v"), property.Type);

    // p.{propertyName} == Convert.ChangeType(holdpv.v, p.{propertyName}.GetType())
    var whereBody = Expression.Equal(property, value);
    // p => p.{propertyName} == Convert.ChangeType(holdpv.v, p.{propertyName}.GetType())
    var whereLambda = Expression.Lambda<Func<T, bool>>(whereBody, pe);

    // Queryable.Where(query, p => p.{propertyName} == Convert.ChangeType(holdpv.v, p.{propertyName}.GetType()))
    var whereCallExpression = Expression.Call(
        typeof(Queryable),
        "Where",
        new[] { typeof(T) },
        query.Expression,
        whereLambda
    );

    // query.Where(query, p => p.{propertyName} == Convert.ChangeType(holdpv.v, p.{propertyName}.GetType()))
    return query.Provider.CreateQuery<T>(whereCallExpression);
}

【讨论】:

  • 非常感谢您提供的答案!在弦上完美运行。但是对于对象,转换仍然存在问题,例如在 int 值系统上抛出 InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Int32'. lambda_method(Closure )
  • 通过以下代码解决了`var propertyType = ((PropertyInfo)property.Member).PropertyType; var 转换器 = TypeDescriptor.GetConverter(propertyType); var valueConverted = converter.ConvertFrom(propertyValue); var holdpv = new holdPropertyValue { v = valueConverted }; ` 这让你的答案非常顺利。
  • @Vitaly 如果您传递了错误的类型(例如,当字段为 Int32 时为 string),那么就会发生这种情况。我使用int 列测试了代码,但我将int 传递给WhereEquals2 方法。我假设你会传入与列类型匹配的类型。
  • 你是完全正确的 - 你的代码运行完美。当我从查询字符串中解析参数时,这是我这边的一个问题。再次感谢!
  • @Vitaly Ah - 也许参数应该是 string 而不是 object,然后您转换为您指示的正确类型的 object :)
猜你喜欢
  • 1970-01-01
  • 2022-01-25
  • 2020-05-29
  • 2019-02-09
  • 2020-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
相关资源
最近更新 更多