C# 如何使用Where和OR表达式构建动态查询

C# 如何使用Where和OR表达式构建动态查询,c#,linq,lambda,asp.net-web-api2,C#,Linq,Lambda,Asp.net Web Api2,我希望有人能在这方面指导和帮助我。我们有一个使用ExpressionHelper类的继承项目。基本上,这个表达式帮助器将返回一个IQueryable,它基于用户提供的搜索词构建一个动态查询 例如,我在下面的代码中传递了2个搜索词 IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();; var searchTerms = new List<SearchTerm> {

我希望有人能在这方面指导和帮助我。我们有一个使用
ExpressionHelper
类的继承项目。基本上,这个表达式帮助器将返回一个
IQueryable
,它基于用户提供的搜索词构建一个动态查询

例如,我在下面的代码中传递了2个搜索词

    IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;

    var searchTerms = new List<SearchTerm>
    {
        new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
        new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
    };

    foreach (var searchTerm in searchTerms)
    {
        var propertyInfo = ExpressionHelper
            .GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);

        var obj = ExpressionHelper.Parameter<TEntity>();

        var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
        var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
        var comparisonExpression = searchTerm.ExpressionProvider
            .GetComparison(left, searchTerm.Operator, right);

        // x => x.Property == "Value"
        var lambdaExpression = ExpressionHelper
            .GetLambda<TEntity, bool>(obj, comparisonExpression);

        // query = query.Where...
        modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
    }
ExpressionHelper.cs

public static class ExpressionHelper
{
    private static readonly MethodInfo LambdaMethod = typeof(Expression)
        .GetMethods()
        .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);

    private static MethodInfo[] QueryableMethods = typeof(Queryable)
        .GetMethods()
        .ToArray();

    private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
    {
        var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
        return LambdaMethod.MakeGenericMethod(predicateType);
    }

    public static PropertyInfo GetPropertyInfo<T>(string name)
        => typeof(T).GetProperties()
        .Single(p => p.Name == name);

    public static ParameterExpression Parameter<T>()
        => Expression.Parameter(typeof(T));

    public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
        => Expression.Property(obj, property);

    public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
        => GetLambda(typeof(TSource), typeof(TDest), obj, arg);

    public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
    {
        var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
        return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
    }

    public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
    {
        var whereMethodBuilder = QueryableMethods
            .First(x => x.Name == "Where" && x.GetParameters().Length == 2)
            .MakeGenericMethod(new[] { typeof(T) });

        return (IQueryable<T>)whereMethodBuilder
            .Invoke(null, new object[] { query, predicate });
    }

    public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
        IQueryable<TEntity> modifiedQuery,
        bool useThenBy,
        bool descending,
        Type propertyType,
        LambdaExpression keySelector)
    {
        var methodName = "OrderBy";
        if (useThenBy) methodName = "ThenBy";
        if (descending) methodName += "Descending";

        var method = QueryableMethods
            .First(x => x.Name == methodName && x.GetParameters().Length == 2)
            .MakeGenericMethod(new[] { typeof(TEntity), propertyType });

        return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
    }
}
公共静态类ExpressionHelper
{
私有静态只读方法信息LambdaMethod=typeof(表达式)
.GetMethods()
.First(x=>x.Name==“Lambda”&&x.ContainsGenericParameters&&x.GetParameters()。长度==2);
私有静态方法信息[]QueryableMethods=typeof(可查询)
.GetMethods()
.ToArray();
私有静态MethodInfo GetLambdaFuncBuilder(类型source,类型dest)
{
var-predicateType=typeof(Func).MakeGenericType(source,dest);
返回LambdaMethod.MakeGenericMethod(谓词类型);
}
公共静态属性信息GetPropertyInfo(字符串名称)
=>typeof(T).GetProperties()
.Single(p=>p.Name==Name);
公共静态参数expression参数()
=>表达式参数(typeof(T));
公共静态成员表达式GetPropertyExpression(ParameterExpression obj,PropertyInfo属性)
=>表达式属性(obj,属性);
公共静态LambdaExpression GetLambda(参数Expression obj,表达式arg)
=>GetLambda(typeof(TSource)、typeof(TDest)、obj、arg);
公共静态LambdaExpression GetLambda(类型source、类型dest、参数Expression obj、表达式arg)
{
var lambdaulder=getlambdauncbuilder(源、目标);
return(LambdaExpression)lambdbuilder.Invoke(null,新对象[]{arg,新对象[]{obj});
}
公共静态IQueryable调用where(IQueryable查询,LambdaExpression谓词)
{
var whereMethodBuilder=QueryableMethods
.First(x=>x.Name==“Where”&&x.GetParameters().Length==2)
.MakeGenericMethod(新[]{typeof(T)});
返回(IQueryable)方法生成器
.Invoke(null,新对象[]{query,谓词});
}
公共静态IQueryable CallOrderByOrThenBy(
IQueryable modifiedQuery,
布尔乌塞恩比,
布尔下降,
类型propertyType,
LambdaExpression键选择器)
{
var methodName=“OrderBy”;
if(useThenBy)methodName=“ThenBy”;
如果(降序)methodName+=“降序”;
var方法=QueryableMethods
.First(x=>x.Name==methodName&&x.GetParameters().Length==2)
.MakeGenericMethod(新[]{typeof(tenty),propertyType});
return(IQueryable)方法。Invoke(null,新对象[]{modifiedQuery,keySelector});
}
}
我很难理解查询是如何创建的,以及如何在创建的查询中将其更改为


希望有人能给我指点方向。谢谢大家!

添加到
SearchTerm
新属性(此处为C#6.0语法):

然后:

其中
A
B
C
D
SearchTerm[0]
SearchTerm[1]
SearchTerm[2]
SearchTerm[3]
opB
opC
opD
中定义的运算符.LogicalConnector
搜索项[3]。LogicalConnector

虽然放括号很容易,但选择如何“描述”它们却很复杂,除非您对
SearchTerm
集合进行重大更改(它不能是“线性”数组,但需要是一棵树)


另外,我错了,你不需要ExpressionVisitor
。当您试图“合并”多个具有不同
参数表达式的
LambdaExpression
时,需要一个
表达式访问者。在这段代码中,我们可以为所有查询使用一个
var obj=ExpressionHelper.Parameter()
,因此合并条件时不会出现问题。明确地说:如果您想将
x1=>x1.Foo==“Foo1”
x2=>x2.Foo==“Foo2”
进行“合并”,那么您需要一个
ExpressionVisitor
x2
替换为
x1
,否则您将得到一个错误的查询,如
x1==“Foo1”| x2.Foo==“Foo2”
。在给定的代码中,我们只有
x1
(即
var obj=ExpressionHelper.Parameter()
),因此没有问题。

您应该花一些时间阅读有关表达式树的官方文档。这不是一个简单的主题,但在充分理解表达式树之后,您可能会绞尽脑汁。您可以从这里开始:您必须重写
ExpressionHelper.CallWhere
。这是在使用“诡计”<代码>查询。其中(A)。其中(B)
转换为
Query.Where(A&&B)
。要使用
|
,您必须编写一些东西来完成它。从这里开始变得更复杂。手动“合并”表达式(您希望将A合并到B和| |)是一件痛苦的事情,您需要一个名为“
ExpressionVisitor
”@xanatos的工具-谢谢您的评论。我会尽我最大的努力去理解如何实施它。但我仍处于了解代码中实际发生的情况以及如何将其应用于当前助手的阶段。@xanatos再次感谢!我会努力做到这一点,因为目前我陷入困境,代码更新没有进展。非常感谢您@xanatos的努力。我一定会尝试这一点,并会回到结果。再次感谢你!
public static class ExpressionHelper
{
    private static readonly MethodInfo LambdaMethod = typeof(Expression)
        .GetMethods()
        .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);

    private static MethodInfo[] QueryableMethods = typeof(Queryable)
        .GetMethods()
        .ToArray();

    private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
    {
        var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
        return LambdaMethod.MakeGenericMethod(predicateType);
    }

    public static PropertyInfo GetPropertyInfo<T>(string name)
        => typeof(T).GetProperties()
        .Single(p => p.Name == name);

    public static ParameterExpression Parameter<T>()
        => Expression.Parameter(typeof(T));

    public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
        => Expression.Property(obj, property);

    public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
        => GetLambda(typeof(TSource), typeof(TDest), obj, arg);

    public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
    {
        var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
        return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
    }

    public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
    {
        var whereMethodBuilder = QueryableMethods
            .First(x => x.Name == "Where" && x.GetParameters().Length == 2)
            .MakeGenericMethod(new[] { typeof(T) });

        return (IQueryable<T>)whereMethodBuilder
            .Invoke(null, new object[] { query, predicate });
    }

    public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
        IQueryable<TEntity> modifiedQuery,
        bool useThenBy,
        bool descending,
        Type propertyType,
        LambdaExpression keySelector)
    {
        var methodName = "OrderBy";
        if (useThenBy) methodName = "ThenBy";
        if (descending) methodName += "Descending";

        var method = QueryableMethods
            .First(x => x.Name == methodName && x.GetParameters().Length == 2)
            .MakeGenericMethod(new[] { typeof(TEntity), propertyType });

        return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
    }
}
    // This is quite wrong. We should have an enum here, but Operator is 
    // done as a string, so I'm maintaining the "style"
    // Supported LogicalConnector: and, or
    public string LogicalConnector { get; set; } = "and";
}
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
    Expression comparisonExpressions = null;

    var obj = ExpressionHelper.Parameter<TEntity>();

    foreach (var searchTerm in searchTerms)
    {
        var propertyInfo = ExpressionHelper
            .GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);

        var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
        var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
        var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);

        if (comparisonExpressions == null)
        {
            comparisonExpressions = comparisonExpression;
        }
        else if (searchTerm.LogicalConnector == "and")
        {
            comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
        }
        else if (searchTerm.LogicalConnector == "or")
        {
            comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
        }
        else
        {
            throw new NotSupportedException(searchTerm.LogicalConnector);
        }
    }

    if (comparisonExpressions != null)
    {
        // x => x.Property == "Value"
        var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
        // query = query.Where...
        modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
    }

    return modifiedQuery;
}
var searchTerms = new List<SearchTerm>
{
    new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
    new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
    new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};

IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
(((A opB b) opC C) opD D)