如何泛化LINQ to SQL查询的一部分

jogvjijk  于 2022-12-06  发布在  其他
关注(0)|答案(2)|浏览(145)

下面的代码片段在不同的字段中重复出现:

if (filters.NameIsLike)
    query = query.Where (x => EF.Functions.Like (x.Name, $"%{filters.Name}%"));
else
    query = query.Where (x => x.Name.ToLower () == filters.Name.ToLower ());

我如何创建一个扩展方法来概括代码段的功能?
类似于:

public static IQueryable<T> EqualsOrLike (this IQueryable<T> query, ??? field, bool isLike, string filter)
{
    if (isLike)
        return query.Where (x => EF.Functions.Like (field.ToLower (), $"%{filter.ToLower ()}%"));
    else
        return query.Where (x => field.ToLower () == filter.ToLower ());
}

提前感谢!

更新日期:

以下是初始代码段的使用方式(作为示例):
在本例中,类是Company,但也可能是其他的东西......因此,我不知道类或字段。注解解释了我将如何使用EqualsOrLike函数:

protected   override            IQueryable<Company>                 _QueryAsync         (IQueryable<Company> query, QueryFilterBase filter)
{
    CompanyQueryFilter filters = (CompanyQueryFilter)filter;

    if (!string.IsNullOrEmpty (filters.Name))
    {
        if (filters.NameIsLike)
            query = query.Where (x => EF.Functions.Like (x.Name.ToLower (), $"%{filters.Name.ToLower ()}%"));
        else
            query = query.Where (x => x.Name.ToLower () == filters.Name.ToLower ());

        //query = EqualsOrLike (query, x => x.Name, filters.NameIsLike, filters.Name);
    }

    if (!string.IsNullOrEmpty (filters.AtecoCode))
        query = query.Where (x => EF.Functions.Like (x.AtecoCode, $"%{filters.AtecoCode}%"));

    if (!string.IsNullOrEmpty (filters.VatNumber))
    {
        if (filters.VatNumberIsLike)
            query = query.Where (x => EF.Functions.Like (x.VatNumber, $"%{filters.VatNumber}%"));
        else
            query = query.Where (x => x.VatNumber.ToLower () == filters.VatNumber.ToLower ());
            
        //query = EqualsOrLike (query, x => x.VatNumber, filters.VatNumberIsLike, filters.VatNumber);
    }

    return query;
}
qij5mzcb

qij5mzcb1#

由于您不知道要比较的属性,因此需要手动建立运算式。
第一步,我发现了解C#如何使用反编译器编译表达式是很有用的;

Expression<Func<C,bool>> expr = x => x.Name.ToLower () == filter;

稍微清理一下就会给予以下结果:

private class LambdaCaptures
{
    public string filter;
}

var locals = new LambdaCaptures();
locals.filter = "";

ParameterExpression parameterExpression =
    Expression.Parameter(typeof(C), "x");

var expr =
    Expression.Lambda<Func<C, bool>>(
        Expression.Equal(
            Expression.Call(
                Expression.MakeMemberAccess(
                    parameterExpression,
                    typeof(C).GetProperty(nameof(C.Name))
                ),
                typeof(string).GetMethod(nameof(string.ToLower), new Type[] { }),
                Array.Empty<Expression>()
            ),
            Expression.Field(
                Expression.Constant(locals, typeof(LambdaCaptures)),
                typeof(C).GetField(nameof(LambdaCaptures.filter))
            )
        ),
        parameterExpression);

您现在可以调整它来替换传入的类型和属性名。注意,您需要保留捕获的lambda,以便EF将此值绑定为参数。否则EF和Sql server每次都需要重新编译查询。
选项2,您可以使用ExpressionVisitor来调整模板表达式。例如,如果您既有要比较的字段的表达式,也有要执行的比较的表达式。您可以将一个表达式的参数替换为另一个表达式的主体。
给出一个完整的解决方案,看起来像这样:

public static Expression<Func<T, bool>> BuildExpr<T, V>(Expression<Func<V, bool>> operation, Expression<Func<T, V>> value)
    => Expression.Lambda<Func<T, bool>>(
        ReplacingExpressionVisitor.Replace(
            operation.Parameters.Single(),
            value.Body,
            operation.Body
        ),
        value.Parameters.Single());

public static IQueryable<T> EqualsOrLike<T>(this IQueryable<T> query, Expression<Func<T, string>> value, bool isLike, string filter)
{
    if (string.IsNullOrEmpty(filter))
        return query;

    if (isLike)
    {
        filter = $"%{filter.ToLower()}%";
        var expr = BuildExpr(x => EF.Functions.Like(x, filter), value);
        return query.Where(expr);
    }
    else
    {
        filter = filter.ToLower();
        var expr = BuildExpr(x => x.ToLower() == filter, value);
        return query.Where(expr);
    }
}

query = query.EqualsOrLike(d => d.Name, filters.NameIsLike, filters.NameValue);

由于这种类型的替换是一种相当常见的操作,因此您可以重用EF Core的ReplacingExpressionVisitor,而不是编写自己的ReplacingExpressionVisitor

b1uwtaje

b1uwtaje2#

可以使用Func从结果中选择字段:

public static IQueryable<T> EqualsOrLike<T>(this IQueryable<T> query, Func<T, string> fieldSelector, bool isLike, string filter)
{
    if (isLike)
        return query.Where(x => EF.Functions.Like(fieldSelector(x).ToLower(), $"%{filter.ToLower ()}%"));
    else
        return query.Where(x => fieldSelector(x).ToLower() == filter.ToLower ());
}

你可以这样称呼它:

var results = someQueryable.EqualsOrLike(x => x.Name, false, "Robert");

相关问题