2012-11-27 40 views
7

में गतिशील रूप से "या" LIKE क्वेरी का निर्माण करें मेरे पास एक LINQ क्वेरी है जो किसी अज्ञात ऑब्जेक्ट द्वारा बनाई गई है।LINQ से SQL

किसी दिए गए बिंदु पर, मैं आने वाले खोज पैरामीटर द्वारा परिणामों को सीमित करना चाहता हूं, लेकिन यह एक या अधिक पैरामीटर हो सकता है, और मैं उन लोगों का उपयोग करके "जैसे x या पसंद या या ज़ेड" करना चाहता हूं।

कोड में, यह इस तरह दिखेगा:

reservations = reservations.Where(r => 
    r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || 
    r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || 
    // Parameter 3, 4, 5,.. 
); 

मैं इस गतिशील रूप से निर्माण कर सकता है, जानते हुए भी कि reservations प्रकार IQueryable<'a> (anonymous object) की है? मैंने विभिन्न संसाधनों पर चारों ओर देखा है और मुझे लगता है कि जब मैं अज्ञात प्रकारों का उपयोग नहीं करता हूं, तो मुझे ऐसा करने का कोई तरीका मिल सकता है।

यह पता चला है कि यह एसक्यूएल को Linq है महत्वपूर्ण है, तो यह एक SQL क्वेरी करने के लिए अनुवाद किया जाना चाहिए और स्मृति में फ़िल्टर किया जा नहीं ...

उत्तर

2

इसके दो संभावित तरीके हैं:

  1. एक बिल्डिंग Expression, के रूप में Coincoin
  2. द्वारा
  3. एक सरणी में अपने सभी मापदंडों लाना और Any का उपयोग कर ने कहा:

    var parameters = new [] { parameter1, parameter2, /*...*/ } 
    reservations = reservations 
        .Where(r => 
         parameters.Any(p => r.GuestFirstName.Contains(p) 
              || r.GuestLastName.Contains(p))); 
    
1

मैं अपने खुद के सामान्य विस्तार विधि लिखना होगा:

public static class CollectionHelper 
{ 
    public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values) 
    { 
     var lambda = CombineLambdas<T>(properties, values); 
     var result = typeof (Queryable).GetMethods().First(
      method => method.Name == "Where" 
         && method.IsGenericMethodDefinition) 
             .MakeGenericMethod(typeof (T)) 
             .Invoke(null, new object[] {source, lambda}); 
     return (IQueryable<T>) result; 
    } 

    // combine lambda expressions using OR operator 
    private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values) 
    { 
     var param = Expression.Parameter(typeof (T)); 
     LambdaExpression prev = null; 
     foreach (var value in values) 
     { 
      foreach (var property in properties) 
      { 
       LambdaExpression current = GetContainsExpression<T>(property, value); 
       if (prev != null) 
       { 
        Expression body = Expression.Or(Expression.Invoke(prev, param), 
                Expression.Invoke(current, param)); 
        prev = Expression.Lambda(body, param); 
       } 
       prev = prev ?? current; 
      } 
     } 
     return prev; 
    } 

    // construct expression tree to represent String.Contains 
    private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue) 
    { 
     var parameterExp = Expression.Parameter(typeof (T), "type"); 
     var propertyExp = Expression.Property(parameterExp, propertyName); 
     var method = typeof (string).GetMethod("Contains", new[] {typeof (string)}); 
     var someValue = Expression.Constant(propertyValue, typeof (string)); 
     var containsMethodExp = Expression.Call(propertyExp, method, someValue); 

     return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp); 
    } 
} 

और उपयोग:

var reservations = new List<TheType>() // sample collection 
    { 
     new TheType {FirstName = "aa", LastName = "bb"}, 
     new TheType {FirstName = "cc", LastName = "dd"}, 
     new TheType {FirstName = "ee", LastName = "ff"} 
    }.AsQueryable(); 

var filtered = reservations 
    .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"}); 
/* returnes 2 elements: 
* {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */ 

मैं एक सामान्य समाधान नहीं जानते कि तुम करना चाहते हैं - यदि कोई मौजूद है , लेकिन मुझे उम्मीद है कि यह स्वीकार्य विकल्प हो सकता है जो आपके मामले को वांछित फ़िल्टर को गतिशील रूप से बनाकर हल करता है।

0

मैं कुछ डिबगिंग के बाद समाधान मिल गया, लेकिन मैं कई चयनकर्ताओं, प्रथम के लिए एक और अंतिम नाम के लिए एक साथ एक WhereFilter बनाने ..

इस विस्तार विधि है:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors) 
{ 
    List<Expression> expressions = new List<Expression>(); 

    var param = Expression.Parameter(typeof(T), "p"); 

    var bodies = new List<MemberExpression>(); 
    foreach (var s in selectors) 
    { 
     bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name)); 
    } 

    foreach (var v in possibleValues) 
    { 
     foreach(var b in bodies) { 
      expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v))); 
     } 
    } 

    var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); 

    return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param)); 
} 

यह हो सकता है इस तरह इस्तेमाल किया गया:

reservations = reservations.WhereFilter(
    array_of_allowed_values, 
    r => r.GuestFirstName, 
    r => r.GuestLastName 
); 

मैंने क्वेरी की ट्रेस स्ट्रिंग की जांच की और वास्तव में एसक्यूएल में अनुवाद किया, इसलिए फ़िल्टरिंग डेटाबेस पर किया जाता है।

+0

इस समाधान के लिए आपको फ़ंक्शन हस्ताक्षर में लैम्ब्डा अभिव्यक्तियों को पार करने की आवश्यकता है। जहां तक ​​मैं आपका प्रश्न समझ गया, आपकी आवश्यकता उन्हें गतिशील रूप से बनाना था। उदाहरण के तौर पर, वेब वातावरण में जीईटी अनुरोध के परिणामस्वरूप आपके पास एक कार्रवाई की गई है। क्वेरी पैरामीटर में गुणों को फ़िल्टर करने के लिए नाम उनके संभावित मानों के साथ सर्वर को भेजते हैं। आपके मामले में, आपको फ़िल्टर फ़ंक्शन पर आगे जाने के बजाय, क्लाइंट से प्राप्त गुणों के आधार पर लैम्ब्डा अभिव्यक्तियों को बनाने के लिए मजबूर होना पड़ता है। – jwaliszko