2013-05-16 29 views
6

Nói rằng tôi có biểu hiện này:Runtime tạo LINQ biểu

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

này về cơ bản 'phân vùng' một tập hợp các yếu tố thành 20 phân vùng và lấy từ mỗi mỗi yếu tố đầu tiên và thứ tư được thiết lập.

Biểu thức này được chuyển đến MongoDB mà nó là driver hoàn toàn có khả năng dịch thành truy vấn MongoDB "". Vị từ có thể, tuy nhiên, cũng được sử dụng trên một danh sách các đối tượng (LINQ2Objects) vv Tôi muốn biểu thức này có thể tái sử dụng (DRY). Tuy nhiên, tôi muốn để có thể vượt qua trong một IEnumerable<int> để xác định các mục để lấy (do 1 và 4 không phải là "mã hóa cứng" vào nó):

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

Với LINQPad sử dụng mã này:

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

tôi có thể kiểm tra các biểu hiện:

Expression

Bây giờ, tôi muốn để có thể xây dựng một tái tạo chính xác của biểu thức này nhưng với số lượng biến số nguyên cần vượt qua (vì vậy thay vì 1 và 4 tôi có thể vượt qua, ví dụ: [1, 5, 9, 11] hoặc [8] hoặc [1, 2, 3, 4, 5, 6, ..., 16]).

Tôi đã thử sử dụng BinaryExpressions v.v. nhưng không thể xây dựng thư này chính xác. Vấn đề chính là hầu hết các attempt s của tôi sẽ thất bại khi chuyển vị từ sang MongoDB. Phiên bản "hardcoded" hoạt động tốt nhưng bằng cách nào đó tất cả những nỗ lực của tôi để vượt qua biểu hiện năng động của tôi không được dịch ra tiếng một truy vấn MongoDB bằng C# tài xế:

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

Về cơ bản, tôi muốn tự động xây dựng các biểu hiện tại thời gian chạy theo cách sao chép chính xác những gì trình biên dịch tạo ra cho phiên bản 'hardcoded'.

Mọi trợ giúp sẽ được đánh giá cao.

EDIT

As requested in the comments (và posted on pastebin), một trong những cố gắng của tôi dưới đây. Tôi gửi bài nó trong câu hỏi để tham khảo furure nên Pastebin mang nó xuống hoặc ngừng serivce của họ hoặc ...

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

Điều này dẫn đến: Unsupported where clause: <InvocationExpression>.

+0

Vui lòng hiển thị một số - hoặc ít nhất một trong số các lần thử của bạn. –

+0

Ở đây bạn truy cập: http://pastebin.com/qDwXGGit. Điều này dẫn đến: 'Không được hỗ trợ where khoản: '. – RobIII

Trả lời

3

Hãy thử điều này:

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

Bạn sẽ gọi nó là như thế này:

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

Nếu bạn muốn nó được generic trong Foo cũng có, bạn cần phải thay đổi nó như thế này:

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

Bây giờ , Cuộc gọi sẽ trông như thế này:

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

Trong kịch bản này, điều quan trọng là phải xác định loại x một cách rõ ràng, nếu không gõ-suy luận sẽ không làm việc và bạn sẽ phải xác định cả hai Foo và loại của là đối số chung cho GetExpression.

+0

HOLY CRAP! Điều đó hoạt động! Cảm ơn! Bây giờ, không phải là một ass hoặc một cái gì đó, là có cách nào tôi có thể tránh bằng cách sử dụng '" Seed "' chuỗi để khi refactoring vv này cũng sẽ được chọn? Cụ thể hơn: tôi không thể "chỉ" vượt qua "lamba" hay cái gì đó để làm cho điều này "chung chung" hơn một chút? Dù bằng cách nào: câu trả lời này được đánh giá rất nhiều! – RobIII

+0

@RobIII: Tôi đã cập nhật câu trả lời của mình để được tái cấu trúc lưu. –

+0

Bạn, thưa bạn, là chiến thắng tinh khiết sử thi! Cảm ơn rất nhiều! – RobIII

Các vấn đề liên quan