繁体   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