Say I have this expression:
int setsize = 20;
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1
|| x.Seed % setsize == 4;
This basically 'partitions' a set of elements into 20 partitions and retrieves from each set each first and fourth element.
This expression is passed to MongoDB which it's driver is perfectly capable of translating into a MongoDB "query". The predicate can, however, also be used on a list of objects (LINQ2Objects) etc. I want this expression to be reusable (DRY). However, I want to be able to pass in an IEnumerable<int>
to specify which items to retrieve (so 1 and 4 aren't "hardcoded" into it):
public Expression<Func<Foo, bool>> GetPredicate(IEnumerable<int> items) {
//Build expression here and return it
}
With LINQPad using 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 examine the expression:
Now, I want to be able to build an exact reproduction of this expression but with a variable amount of integers to pass (so 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 have tried using BinaryExpressions etc. but haven't been able to construct this message correctly. The main issue is that most of my attempts will fail when passing the predicate to MongoDB. The "hardcoded" version works fine but somehow all my attempts to pass my dynamic expressions fail to be translated into a MongoDB query by the C# driver:
{
"$or" : [{
"Seed" : { "$mod" : [20, 1] }
}, {
"Seed" : { "$mod" : [20, 4] }
}]
}
Basically, I want to dynamically build the expression at runtime in such a way that it exactly replicates what the compiler generates for the 'hardcoded' version.
Any help will be appreciated.
EDIT
As requested in the comments (and posted on pastebin), one of my tries below. I'm posting it in the question for furure reference should pastebin take it down or stop their 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);
}
}
This results in: Unsupported where clause: <InvocationExpression>
.