簡體   English   中英

如何為 EF Core 創建可重用的“包含”表達式

[英]How to create a reusable 'Contains' expression for EF Core

問題

我需要通過使用表達式的通用存儲庫與其他過濾器一起執行部分文本搜索。

當前代碼的 State

我有一個通用方法,它從我的數據庫返回分頁結果(通過公共存儲庫層)。

在以下工作示例中;

  • PagedRequest包含當前頁面大小和頁碼,並在各自的Skip / Take操作期間使用。
  • PagedResult包含結果的集合以及記錄總數。
public Task<PagedResult<Person>> GetPeopleAsync(PersonSearchParams searchParams,
    PagedRequest pagedRequest = null)
{
    ParameterExpression argParam = Expression.Parameter(typeof(Locum), "locum");

    // start with a "true" expression so we have an expression to "AndAlso" with
    var alwaysTrue = Expression.Constant(true);
    var expr = Expression.Equal(alwaysTrue, alwaysTrue);

    if (searchParams != null)
    {
        BinaryExpression propExpr;

        if (searchParams.DateOfBirth.HasValue)
        {
            propExpr = GetExpression(searchParams.DateStart,
                nameof(Incident.IncidentDate), 
                argParam, 
                ExpressionType.GreaterThanOrEqual);

            expr = Expression.AndAlso(expr, propExpr);
        }

        if (searchParams.DateOfDeath.HasValue)
        {
            propExpr = GetExpression(searchParams.DateEnd,
                nameof(Incident.IncidentDate), 
                argParam, 
                ExpressionType.LessThanOrEqual);

            expr = Expression.AndAlso(expr, propExpr);
        }

        if (searchParams.BranchId.HasValue && searchParams.BranchId.Value != 0)
        {
            propExpr = GetExpression(searchParams.BranchId, 
                nameof(Incident.BranchId), argParam);

            expr = Expression.AndAlso(expr, propExpr);
        }
    }

    var lambda = Expression.Lambda<Func<Locum, bool>>(expr, argParam);
    return _unitOfWork.Repository.GetAsync(filter: lambda, pagedRequest: pagedRequest);
}

這是對Expression.EqualExpression.GreaterThanOrEqualExpression.LessThanOrEqual查詢使用我的 static GetExpression方法,如下所示;

private static BinaryExpression GetExpression<TValue>(TValue value,
    string propName, ParameterExpression argParam, ExpressionType? exprType = null)
{
    BinaryExpression propExpr;

    var prop = Expression.Property(argParam, propName);
    var valueConst = Expression.Constant(value, typeof(TValue));

    switch (exprType)
    {
        case ExpressionType.GreaterThanOrEqual:
            propExpr = Expression.GreaterThanOrEqual(prop, valueConst);
            break;
        case ExpressionType.LessThanOrEqual:
            propExpr = Expression.LessThanOrEqual(prop, valueConst);
            break;
        case ExpressionType.Equal:
        default:// assume equality
            propExpr = Expression.Equal(prop, valueConst);
            break;
    }
    return propExpr;
}

注意:此代碼工作正常。

問題

使用其他 SO 答案中的示例,我嘗試了以下方法;

表達式

我嘗試通過Expression獲取包含;

static Expression<Func<bool>> GetContainsExpression<T>(string propertyName, 
    string propertyValue)
{
    var parameterExp = Expression.Parameter(typeof(T), "type");
    var propertyExp = Expression.Property(parameterExp, propertyName);
    MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    var someValue = Expression.Constant(propertyValue, typeof(string));
    var containsMethodExp = Expression.Call(propertyExp, method, someValue);
    return Expression.Lambda<Func<bool>>(containsMethodExp);
}

這必須轉換為BinaryExpression ,以便可以使用AndAlso將其添加到表達式樹中。 我試圖將Expressiontrue值進行比較,但這不起作用

if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
    var propExpr = GetContainsExpression<Locum>(nameof(Locum.Firstname), 
        searchParams.FirstName);

    var binExpr = Expression.MakeBinary(ExpressionType.Equal, propExpr, propExpr);
    expr = Expression.AndAlso(expr, binExpr);
}

方法調用表達式

我還嘗試使用以下方法返回MethodCallExpression (而不是上面的 Lambda);

static MethodCallExpression GetContainsMethodCallExpression<T>(string propertyName, 
    string propertyValue)
{
    var parameterExp = Expression.Parameter(typeof(T), "type");
    var propertyExp = Expression.Property(parameterExp, propertyName);
    MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    var someValue = Expression.Constant(propertyValue, typeof(string));
    var containsMethodExp = Expression.Call(propertyExp, method, someValue);

    return containsMethodExp;
}

我按如下方式使用它;

if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
    var propExpr = GetContainsMethodCallExpression<Person>(nameof(Person.FirstName), 
        searchParams.FirstName);

    var binExpr = Expression.MakeBinary(ExpressionType.Equal, propExpr, alwaysTrue);
    expr = Expression.AndAlso(expr, binExpr);
}

例外

這些表達式被傳遞給從數據庫中分頁信息的通用方法,並且在查詢的第一次執行期間,當 I Count the total matching number of Record on theConstructed query時拋出異常。

System.InvalidOperationException: 'LINQ 表達式'DbSet().Where(p => True && p.FirstName.Contains("123") == True)' 無法翻譯。 以可翻譯的形式重寫查詢,或通過插入對“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的調用顯式切換到客戶端評估。 有關詳細信息,請參閱https://go.microsoft.com/fwlink/?linkid=2101038

我在分頁代碼中使用的Count方法引發了此異常。 此代碼已經在沒有任何過濾器的情況下工作,並且使用頂部描述的ExpressionType過濾器,因此我沒有包含此代碼,因為我認為它不相關。

pagedResult.RowCount = query.Count();

這必須轉換為 BinaryExpression,以便可以使用AndAlso將其添加到表達式樹中

消極的。 不要求Expression.AndAlso (或Expression.OrElse操作數是二進制表達式(如果要求&&||的左或右操作數始終是比較運算符,那會很奇怪)。 唯一的要求是它們是bool返回表達式,因此對字符串Contains調用是一個完全有效的操作數表達式。

因此,首先將內部局部變量的類型從BinaryExpression更改為Expression

if (searchParams != null)
{
    Expression propExpr;
    
    // ...
}

同樣的 btw 適用於初始表達式 - 你不需要true == true ,簡單的Expression expr = Expression.Constant(true); 也會這樣做。

現在您可以在一個單獨的方法中發出對string.Contains的方法調用,該方法類似於您發布的另一個方法(傳遞ParameterExpression和構建屬性選擇器表達式)或類似於以下內容的內聯:

if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
    var propExpr = Expression.Property(argParam, nameof(Person.FirstName));
    var valueExpr = Expression.Constant(searchParams.FirstName);
    var containsExpr = Expression.Call(
        propExpr, nameof(string.Contains), Type.EmptyTypes, valueExpr);
    expr = Expression.AndAlso(expr, containsExpr);
}

暫無
暫無

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

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