简体   繁体   中英

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. I basically need to have a nested OR statement within my Where to check if an Enum matches a list of enums. 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.

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.

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:

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. 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). It provides a generic way to apply an IsIn list to a 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.

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();
    }
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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