簡體   English   中英

來自 Entity Framework Core 中表達式樹的參數化查詢

[英]Parameterized Query from an Expression Tree in Entity Framework Core

我正在嘗試通過構建表達式樹在通用存儲庫(.NET Core 3.1 + EF Core 3.1)中實現動態過濾器,但生成的 SQL 查詢從未參數化(我正在通過"Microsoft.EntityFrameworkCore.Database.Command": "Information"驗證生成的查詢"Microsoft.EntityFrameworkCore.Database.Command": "Information" in appsettings.json 並在 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重用,管理它以使用 Where 方法,但生成的 SQL 仍然相同。

嘗試使用 System.Linq.Dynamic.Core,但它有同樣的問題(生成的 SQL 查詢未參數化)。

有沒有辦法強制 Entity Framework Core 從表達式樹生成參數化查詢?

您提供的鏈接解釋了 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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM