简体   繁体   English

C# 动态 Linq 使用 Fluent Mongo 查询 - 不使用 Contains 或 PredicateBuilder

[英]C# Dynamic Linq Query with Fluent Mongo - Without using Contains or PredicateBuilder

I'm using Fluent Mongo and having issues creating a dynamic linq query because Fluent Mongo doesn't support Contains.我正在使用 Fluent Mongo 并在创建动态 linq 查询时遇到问题,因为 Fluent Mongo 不支持包含。 I basically need to have a nested OR statement within my Where to check if an Enum matches a list of enums.我基本上需要在我的 Where 中有一个嵌套的 OR 语句来检查枚举是否与枚举列表匹配。 I'm sure there's another way to do this without using Contains, I just don't know enought about linq... I'm assuming I need to separate the linq expressions out and add them dynamically but I can't figure it out.我确定有另一种方法可以在不使用包含的情况下执行此操作,我只是对 linq 不太了解...我假设我需要将 linq 表达式分开并动态添加它们,但我无法弄清楚.

I've tried using Dynamic Linq (ScottGu) but that doesn't seem to work with Enums, and I don't see how you can add a dynamic amount of where statements, the examples just show how to use dynamic values.我尝试过使用 Dynamic Linq (ScottGu) 但这似乎不适用于 Enums,而且我看不到如何添加动态数量的 where 语句,这些示例只是展示了如何使用动态值。

I've tested this to see if nested Or's work, and they do as expected, I just can't figure out how to build the nested Or's dynamically:我已经对此进行了测试以查看嵌套 Or 的工作,并且它们按预期工作,我只是无法弄清楚如何动态构建嵌套的 Or:

candidates.Where(p => p.CreatedOn >= _startDate && p.CreatedOn <= _endDateTime && (p.SomeEnum == enmSomeEnum.Value1 || p.SomeEnum == enmSomeEnum.Value2));

Thank you, Tim谢谢你,蒂姆

First off I would agree that PredicateBuilder is a much simpler approach.首先,我同意 PredicateBuilder 是一种更简单的方法。 However, if you want a lengthy solution that provides (in my opinion) more flexibility you can look at the below solution.但是,如果您想要一个提供(在我看来)更大灵活性的冗长解决方案,您可以查看以下解决方案。 I put it together for some work on generating dynamic OData queries and is compiled from several other sources (see the links in the code).我将它放在一起用于生成动态 OData 查询的一些工作,并从其他几个来源编译(请参阅代码中的链接)。 It provides a generic way to apply an IsIn list to a Queryable.它提供了一种将 IsIn 列表应用于 Queryable 的通用方法。 The code you want is in the QueryExt class and the rest is just a example program showing how it works and one way how it might solve your problem.您想要的代码在 QueryExt class 中,而 rest 只是一个示例程序,展示了它的工作原理以及它如何解决您的问题的一种方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace OrTree
{
public static class QueryExt
{
    /// <summary>
    /// Adapted from http://blogs.msdn.com/b/phaniraj/archive/2008/07/17/set-based-operations-in-ado-net-data-services.aspx
    /// </summary>
    /// 
    public static IQueryable<T> IsIn<T, TV>(this IQueryable<T> query, IEnumerable<TV> values, Expression<Func<T, TV>> selector = null)
    {
        if (values == null)
        {
            return query;
        }

        // The parameter expression containing the Entity Type
        var param = Expression.Parameter(typeof(T), "param");

        var propertyExpr = selector == null ? (Expression)param : Expression.Invoke(selector, param); 

        var expressions = new List<Expression>();
        foreach (var value in values)
        {
            // Build a comparision expression which equates the selector with a value in the list
            // ex : e.Id == 1
            var nexps = NullableExpressionCheck(propertyExpr, Expression.Constant(value));
            expressions.Add(Expression.Equal(nexps.Item1, nexps.Item2));
        }

        // Convert the Filter Expressions into a Lambda expression of type Func<Lists,bool>
        // which means that this lambda expression takes an instance of type EntityType and returns a Bool
        if (expressions.Any())
        {
            Expression filterPredicate = GenerateOrTree(expressions, 0, expressions.Count - 1);
            var filterLambdaExpression = Expression.Lambda<Func<T, bool>>(filterPredicate, param);
            return query.Where(filterLambdaExpression);
        }
        return query;
    }

    /// <summary>
    /// Take a list of expression and build a balanced or tree.  This is useful when there is a large number
    /// of expressions that will be or'ed together.  Linq, by default builds a recursive tree and you may hit the
    /// recursion limit of 100.  By building a balanced tree you will get the same results but with a much shallower 
    /// tree. 
    /// Added from http://stackoverflow.com/questions/2940045/building-flat-rather-than-tree-linq-expressions
    /// </summary>
    /// <param name="exprs">List of expressions to add</param>
    /// <param name="start">Start index in the list</param>
    /// <param name="end">End index in the list</param>
    /// <returns>Combined expression tree as a single expression</returns>
    public static Expression GenerateOrTree(IList<Expression> exprs, int start, int end)
    {
        // End of the recursive processing - return single element
        if (start == end)
        {
            return exprs[start];
        }

        // Split the list between two parts of (roughly the same size)
        var mid = start + ((end - start) / 2);

        // Process the two parts recursively and join them using OR
        var left = GenerateOrTree(exprs, start, mid);
        var right = GenerateOrTree(exprs, mid + 1, end);
        return Expression.Or(left, right);
    }

    public static Tuple<Expression, Expression> NullableExpressionCheck(Expression e1, Expression e2)
    {
        if (e1.Type.IsValueType && e2.Type == typeof(Object))
        {
            e2 = Expression.Convert(e2, typeof(Nullable<>).MakeGenericType(e1.Type));
        }

        if (e2.Type.IsValueType && e1.Type == typeof(Object))
        {
            e1 = Expression.Convert(e1, typeof(Nullable<>).MakeGenericType(e2.Type));
        }

        if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
            e2 = Expression.Convert(e2, e1.Type);
        else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
            e1 = Expression.Convert(e1, e2.Type);
        return new Tuple<Expression, Expression>(e1, e2);
    }

    public static bool IsNullableType(Type t)
    {
        return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

public enum TestEnum
{
    One,
    Two,
    Three,
    Four
}

public class TestClass
{
    public int Id { get; set; }

    public TestEnum MyEnum { get; set; }
}

public class Test2
{
    public DateTime StartDate { get; set; }

    public TestEnum MyEnum { get; set; }
}

public class Program
{
    public Program()
    {
        var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

        var result = list.AsQueryable().IsIn(new List<int> { 1, 10, 13, 21 });

        var enumList = new List<TestEnum> { TestEnum.One, TestEnum.Two, TestEnum.Three, TestEnum.Four };

        var enumResult = enumList.AsQueryable().IsIn(new List<TestEnum> { TestEnum.Four });

        var classList = new List<TestClass>
            {
                new TestClass { Id = 1, MyEnum = TestEnum.One }, 
                new TestClass { Id = 2, MyEnum = TestEnum.Two }, 
                new TestClass { Id = 3, MyEnum = TestEnum.Three },
                new TestClass { Id = 4, MyEnum = TestEnum.Four }
            };

        var classResult = classList.AsQueryable().IsIn(new List<TestEnum> { TestEnum.Four }, r=>r.MyEnum);

        var dateList = new List<Test2> 
        { 
            new Test2 { StartDate = DateTime.Now.AddDays(1), MyEnum = TestEnum.One },
            new Test2 { StartDate = DateTime.Now.AddDays(2), MyEnum = TestEnum.Two },
            new Test2 { StartDate = DateTime.Now.AddDays(3), MyEnum = TestEnum.Three },
            new Test2 { StartDate = DateTime.Now.AddDays(4), MyEnum = TestEnum.Four },
            new Test2 { StartDate = DateTime.Now.AddDays(5), MyEnum = TestEnum.Four } 
        };

        var startDate = DateTime.Now.AddDays(2);
        var endDate = DateTime.Now.AddDays(4);

        // Update function dynamically if needed
        Func<DateTime, bool> dateRange = a => a >= startDate && a <= endDate;

        // Build this list dynamically
        var orValues = new List<TestEnum> { TestEnum.One, TestEnum.Four };

        //Call the where clause, convert to Queryable, and apply IsIn to create needed where clauses
        var dateResult = dateList.Where(t => dateRange(t.StartDate)).AsQueryable().IsIn(orValues, t=>t.MyEnum);
    }

    static void Main(string[] args)
    {
        new Program();
    }
}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM