2013-05-16 16 views
6

Say mam to wyrażenie:Runtime tworzenie LINQ ekspresji

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

Zasadniczo partycje „” zestaw elementów na 20 przegród i pobiera od każdego zestawu każdego pierwszego i czwartego elementu.

To wyrażenie jest przekazywane do MongoDB, które jest w stanie przetworzyć na "kwerendę" MongoDB. Predykatu można jednak używać również na liście obiektów (LINQ2Objects) itp. Chcę, aby to wyrażenie było wielokrotnego użytku (DRY). Jednakże chcę, aby być w stanie przejść w IEnumerable<int>, aby określić, które elementy do pobrania (czyli 1 i 4 nie są „na sztywno” do niego):

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

Z LINQPad przy użyciu tego kodu:

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

mogę zbadać zależność:

Expression

teraz chcę, aby móc zbudować dokładną reprodukcję tej wypowiedzi, ale ze zmienną liczbą liczb całkowitych do przekazania (więc zamiast 1 i 4 mógłbym przejść, na przykład, [1, 5, 9, 11] lub [8] lub [1, 2, 3, 4, 5, 6, ..., 16]).

Próbowałem używać BinaryExpressions itp., Ale nie udało się poprawnie utworzyć tej wiadomości. Głównym problemem jest to, że większość moich attempt s zawiedzie podczas przekazywania predykatu do MongoDB. W „ustalony” wersja działa dobrze ale jakoś wszystkie moje próby przekazać moje wyrazy dynamiczne nie być tłumaczone na zapytania MongoDB przez kierowcę C#:

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

Zasadniczo chcę, aby dynamicznie zbudować wyrażenie w czasie wykonywania w taki sposób, aby dokładnie replikował to, co generuje kompilator dla wersji "zakodowanej".

Każda pomoc zostanie doceniona.

EDIT

As requested in the comments (i posted on pastebin), jeden z poniższych moich próbach. Jestem umieszczenie go w pytaniu o furure odniesienia powinny Pastebin wziąć go lub zatrzymać ich serivce lub ...

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

Skutkuje: Unsupported where clause: <InvocationExpression>.

+0

Proszę pokazać niektóre - lub przynajmniej jedno - swoich prób. –

+0

Tutaj możesz: http://pastebin.com/qDwXGGit. Powoduje to: 'Nieobsługiwane gdzie klauzula: '. – RobIII

Odpowiedz

3

Spróbuj tego:

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

Można by nazwać tak:

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

Jeśli ma to być rodzajowy w Foo również, trzeba zmienić to tak:

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

Teraz , Rozmowa będzie wyglądać następująco:

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

W tym scenariuszu, że ważne jest, aby określić typ x wyraźnie, inaczej wpisać-wnioskowanie nie zadziała i trzeba by określić zarówno Foo i typ właściwość jako ogólne argumenty do GetExpression.

+0

ŚWIĘTY CRAP! To działa! Dzięki! Teraz, nie być osłem czy czymś, czy jest jakiś sposób uniknięcia używania ciągu '' Seed ''tak, aby podczas refaktoryzacji itp. Również został on odebrany? Dokładniej: czy nie mógłbym "po prostu" przekazać "lamba" czy coś, co uczyniłoby to nieco bardziej "ogólnym"? Tak czy inaczej: ta odpowiedź jest bardzo doceniana! – RobIII

+0

@RobIII: Zaktualizowałem moją odpowiedź, aby zapisać. –

+0

Ty, panie, jesteś czysty i wygrany! Wielkie dzięki! – RobIII