Creating LINQ expression runtime

Let's say I have this expression:

int setsize = 20; Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4; 

This basically โ€œdividesโ€ a set of elements into 20 sections and extracts every first and fourth elements from each set.

This expression is passed to MongoDB , which driver perfectly translates into a MongoDB "query". The predicate can, however, also be used in the list of objects (LINQ2Objects), etc. I want this expression to be reused ( DRY ). However, I want to be able to pass an IEnumerable<int> to indicate which elements will be retrieved (therefore 1 and 4 are not "hard-coded" into it):

 public Expression<Func<Foo, bool>> GetPredicate(IEnumerable<int> items) { //Build expression here and return it } 

Using LINQPad with this code:

 int setsize = 20; Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4; predicate.Dump(); } class Foo { public int Seed { get; set; } 

I can check the expression:

Expression

Now I want to be able to build an exact reproduction of this expression, but with a variable number of integers (so that instead of 1 and 4 I could pass, for example, [1, 5, 9, 11] or [8] or [1, 2, 3, 4, 5, 6, ..., 16] ).

I tried using BinaryExpressions , etc., but could not build this message correctly. The main problem is that most of my attempt will fail when passing the MongoDB predicate. The "hardcoded" version works fine , but somehow all my attempts to pass my dynamic expressions cannot be translated into a MongoDB request using the C # driver:

 { "$or" : [{ "Seed" : { "$mod" : [20, 1] } }, { "Seed" : { "$mod" : [20, 4] } }] } 

Basically, I want to dynamically build the expression at run time so that it accurately reproduces what the compiler generates for the "hard-coded" version.

Any help would be appreciated.

EDIT

As requested in the comments (and posted on pastebin ), one of my attempts below. I post it in the question of links to the Fรผhrer if Pastebin takes it or stops them serivce or ...

 using MongoRepository; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; class Program { static void Main(string[] args) { MongoRepository<Foo> repo = new MongoRepository<Foo>(); var reporesult = repo.All().Where(IsInSet(new[] { 1, 4 }, 20)).ToArray(); } private static Expression<Func<Foo, bool>> IsInSet(IEnumerable<int> seeds, int setsize) { if (seeds == null) throw new ArgumentNullException("s"); if (!seeds.Any()) throw new ArgumentException("No sets specified"); return seeds.Select<int, Expression<Func<Foo, bool>>>(seed => x => x.Seed % setsize == seed).JoinByOr(); } } public class Foo : Entity { public int Seed { get; set; } } public static class Extensions { public static Expression<Func<T, bool>> JoinByOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters) { var firstFilter = filters.First(); var body = firstFilter.Body; var param = firstFilter.Parameters.ToArray(); foreach (var nextFilter in filters.Skip(1)) { var nextBody = Expression.Invoke(nextFilter, param); body = Expression.Or(body, nextBody); } return Expression.Lambda<Func<T, bool>>(body, param); } } 

The result is: Unsupported where clause: <InvocationExpression> .

+6
source share
1 answer

Try the following:

 public Expression<Func<Foo, bool>> GetExpression<T>( int setSize, int[] elements, Expression<Func<Foo, T>> property) { var seedProperty = GetPropertyInfo(property); var parameter = Expression.Parameter(typeof(Foo)); Expression body = null; foreach(var element in elements) { var condition = GetCondition(parameter, seedProperty, setSize, element); if(body == null) body = condition; else body = Expression.OrElse(body, condition); } if(body == null) body = Expression.Constant(false); return Expression.Lambda<Func<Foo, bool>>(body, parameter); } public Expression GetCondition( ParameterExpression parameter, PropertyInfo seedProperty, int setSize, int element) { return Expression.Equal( Expression.Modulo(Expression.Property(parameter, seedProperty), Expression.Constant(setSize)), Expression.Constant(element)); } public static PropertyInfo GetPropertyInfo(LambdaExpression propertyExpression) { if (propertyExpression == null) throw new ArgumentNullException("propertyExpression"); var body = propertyExpression.Body as MemberExpression; if (body == null) { throw new ArgumentException( string.Format( "'propertyExpression' should be a member expression, " + "but it is a {0}", propertyExpression.Body.GetType())); } var propertyInfo = body.Member as PropertyInfo; if (propertyInfo == null) { throw new ArgumentException( string.Format( "The member used in the expression should be a property, " + "but it is a {0}", body.Member.GetType())); } return propertyInfo; } 

You would call it like this:

 GetExpression(setSize, elements, x => x.Seed); 

If you want it to be common in Foo as well, you need to change it like this:

 public static Expression<Func<TEntity, bool>> GetExpression<TEntity, TProperty>( int setSize, int[] elements, Expression<Func<TEntity, TProperty>> property) { var propertyInfo = GetPropertyInfo(property); var parameter = Expression.Parameter(typeof(TEntity)); Expression body = null; foreach(var element in elements) { var condition = GetCondition(parameter, propertyInfo , setSize, element); if(body == null) body = condition; else body = Expression.OrElse(body, condition); } if(body == null) body = Expression.Constant(false); return Expression.Lambda<Func<TEntity, bool>>(body, parameter); } 

Now the call will look like this:

 GetExpression(setSize, elements, (Foo x) => x.Seed); 

In this scenario, it is important to explicitly specify the type x , otherwise the type inference will not work, and you will need to specify both Foo and the property type as general arguments for GetExpression .

+3
source

Source: https://habr.com/ru/post/945141/


All Articles