2015-09-24 13 views
7

में फ़ील्ड फ़िल्टर का पुन: उपयोग कैसे करें I EF का उपयोग कर रहा हूं और डेटाबेस तालिका है जिसमें कई दिनांक समय फ़ील्ड हैं जो विभिन्न ऑपरेशन के रूप में पॉप्युलेट किए जाते हैं, रिकॉर्ड पर किए जाते हैं। मैं वर्तमान में एक रिपोर्टिंग सिस्टम का निर्माण कर रहा हूं जिसमें इन तिथियों से फ़िल्टरिंग शामिल है, लेकिन क्योंकि फिल्टर (यह सीमा किसी सीमा के भीतर है, आदि ...) प्रत्येक फ़ील्ड पर एक ही व्यवहार है, इसलिए मैं अपने फ़िल्टरिंग तर्क का पुन: उपयोग करना चाहता हूं। केवल एक ही दिनांक फ़िल्टर लिखें और प्रत्येक फ़ील्ड पर इसका इस्तेमाल करें।LINQ से Entities

मेरे प्रारंभिक शुद्धिकरण कोड तरह दिखता है:

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .Where(record => ((!dateOneIsAfter.HasValue 
       || record.DateFieldOne > dateOneIsAfter.Value) 
      && (!dateOneIsBefore.HasValue 
       || record.DateFieldOne < dateOneIsBefore.Value))) 
     .Where(record => ((!dateTwoIsAfter.HasValue 
       || record.DateFieldTwo > dateTwoIsAfter.Value) 
      && (!dateTwoIsBefore.HasValue 
       || record.DateFieldTwo < dateTwoIsBefore.Value))) 
     .ToList(); 

    return result; 
} 

यह ठीक काम करता है, लेकिन मैं फिल्टर एल्गोरिथ्म के रूप में 'जहां' तरीकों डुप्लीकेट कोड को कम करना पसंद करेंगे प्रत्येक तिथि क्षेत्र के लिए एक ही है।

क्या मैं पसंद करेंगे कुछ है कि जैसा दिखता है निम्नलिखित (मैं बाद में फिल्टर मूल्यों के लिए एक वर्ग या struct पैदा करेगा) जहाँ मैं शायद एक विस्तार पद्धति का उपयोग करके मैच एल्गोरिथ्म संपुटित कर सकते हैं:

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
     .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
     .ToList(); 

    return result; 
} 

कहाँ

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Func<TestItemTable, DateTime> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
     && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

अगर मेरे छानने कोड इस प्रकार है ऊपर कोड का उपयोग करना,:

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
01 विस्तार विधि की तरह कुछ दिखाई दे सकता है

A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll 

Additional information: LINQ to Entities does not recognize the method 'System.DateTime Invoke(RAC.Scratch.ReusableDataFilter.FrontEnd.TestItemTable)' method, and this method cannot be translated into a store expression. 

समस्या मैं आह्वान के उपयोग विशेष क्षेत्र पाने के लिए इस तकनीक के रूप में पूछे जा रहा है, एसक्यूएल करने के लिए अच्छी तरह से हल नहीं होती है क्योंकि अगर मैं अपने को छानने कोड को संशोधित है:

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

IEnumerable<TestItemTable> result = context.TestItemTables 
    .ToList() 
    .AsQueryable() 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 

इस के साथ मुद्दा यह है कि कोड (विस्तार विधि के साथ फ़िल्टर करने से पहले पूरे मेज पर ToList का प्रयोग करके) पूरे डेटाबेस और प्रश्नों के रूप में के बजाय क्वेरी करने की वस्तुओं में खींचती है अंतर्निहित डेटाबेस इसलिए यह स्केल नहीं है ble।

मैंने लिंककिट से प्रिडिकेटबिल्डर का उपयोग करके भी जांच की है, लेकिन इसे Invoke विधि का उपयोग किये बिना कोड लिखने का कोई तरीका नहीं मिला।

मुझे पता है कि तकनीकें हैं जहां कोई क्वेरी के हिस्सों को स्ट्रिंग्स के रूप में व्यक्त कर सकता है जिसमें फ़ील्ड नाम शामिल हैं, लेकिन मैं इस कोड को लिखने का एक और प्रकार सुरक्षित तरीका उपयोग करना पसंद करूंगा।

इसके अलावा, एक आदर्श दुनिया में मैं डेटाबेस को एक 'आइटम' रिकॉर्ड से संबंधित कई 'तारीख' रिकॉर्ड रखने के लिए फिर से डिजाइन कर सकता हूं, लेकिन मुझे इस तरह से डेटाबेस स्कीमा को बदलने की स्वतंत्रता नहीं है।

क्या मुझे एक और तरीका है जो मुझे एक्सटेंशन लिखने की ज़रूरत है, इसलिए यह Invoke का उपयोग नहीं करता है, या क्या मुझे अपने फ़िल्टरिंग कोड का एक अलग तरीके से पुन: उपयोग करना चाहिए?

उत्तर

2

हां, LinqKit यहां जाने का तरीका है। लेकिन, जब आप अपने एक्सटेंशन विधि में कुछ टुकड़े छूट रहा है:

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Expression<Func<TestItemTable, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.AsExpandable().Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
      && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

मैं Expression<Func<TestItemTable, DateTime>> को 2 पैरामीटर बदल गया है और LinqKit के AsExpandable() विधि से लापता कॉल गयी। इस तरह, Invoke() लिंककिट के Invoke() पर कॉल करेगा जो उसके जादू को करने में सक्षम है।

उपयोग:

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
3

आपके मामले में वहाँ बहुत दोहराव नहीं है, लेकिन फिर भी मैं दिखाता हूँ कैसे तुम क्या आप कच्चे भाव के साथ चाहते हैं कर सकते हैं (एक उदाहरण के रूप में):

internal static class QueryableExtensions { 
    internal static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) { 
     if (dateIsAfter == null && dateIsBefore == null) 
      return source; 
     // this represents you "record" parameter in lambda 
     var arg = Expression.Parameter(typeof(T), "record"); 
     // this is the name of your field ("DateFieldOne") 
     var dateFieldName = ((MemberExpression)fieldData.Body).Member.Name; 
     // this is expression "c.DateFieldOne" 
     var dateProperty = Expression.Property(arg, typeof(T), dateFieldName); 
     Expression left = null; 
     Expression right = null; 
     // this is "c.DateFieldOne > dateIsAfter" 
     if (dateIsAfter != null) 
      left = Expression.GreaterThan(dateProperty, Expression.Constant(dateIsAfter.Value, typeof(DateTime))); 
     // this is "c.DateFieldOne < dateIsBefore" 
     if (dateIsBefore != null) 
      right = Expression.LessThan(dateProperty, Expression.Constant(dateIsBefore.Value, typeof(DateTime))); 
     // now we either combine with AND or not, depending on values 
     Expression<Func<T, bool>> combined; 
     if (left != null && right != null) 
      combined = Expression.Lambda<Func<T, bool>>(Expression.And(left, right), arg); 
     else if (left != null) 
      combined = Expression.Lambda<Func<T, bool>>(left, arg); 
     else 
      combined = Expression.Lambda<Func<T, bool>>(right, arg); 
     // applying that to where and done. 
     source = source.Where(combined); 
     return source; 
    } 
} 
आपकी अपेक्षा के अनुरूप होगा

कॉल है:

WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 

भाव के साथ कार्य करना पहली बार में अजीब लग सकता है, लेकिन कम से कम सरल मामलों के लिए कुछ भी जटिल नहीं है।

+0

आप एक गैर-शून्य तारीख फ़िल्टर मान के साथ धन्यवाद, मैं हालांकि कुछ कमी हो सकती है ... (मेरी क्षमा याचना - मैं शायद नमूना कोड में शामिल किया जाना चाहिए था), उदाहरण के लिए: दिनांक समय? dateOneIsAfter = नया डेटटाइम (2000, 12, 31); मैं अपवाद: प्रकार 'System.InvalidCastException' की एक बिना क्रिया का अपवाद ReusableDataFilter.FrontEnd.exe में हुई अतिरिक्त जानकारी: प्रकार की वस्तु कास्ट करने के लिए 'System.Linq.Expressions.UnaryExpression' टाइप करने में असमर्थ 'सिस्टम .Linq.Expressions.MemberExpression '। लाइन पर: var dateFieldName = ((सदस्य एक्स्पेरियन) फ़ील्डडेटा। बॉडी) .Member.Name; – NvR

+0

@NvR गैर-शून्य तिथियों के साथ काम करने का उत्तर अपडेट किया गया। – Evk

+0

जबकि ओपी ने इस कोड के बारे में शिकायत की लेकिन अपवाद से, इसका मतलब है कि उसे किसी भी तरह कोड को संशोधित करना होगा, 'फ़ील्डडाटा' 'e => e.DateFieldOne' जैसा होगा और इसके 'बॉडी' निश्चित रूप से 'सदस्य एक्सप्शन' होना चाहिए (लेकिन किसी भी तरह यह 'UnaryExpresion' है, वह वास्तव में 'e => e.DateFieldOne.Value' का उपयोग कर सकता है)। आपका मूल कोड ('dateTime? 'के साथ' e => DateFieldOne' के लिए काम करना चाहिए, वर्तमान कोड 'e => e.DateFieldOne.Value' के लिए काम करना चाहिए। तो मैं प्रयास के लिए +1 होगा। ओपी अभिव्यक्ति के बारे में जानने का मौका छोड़ सकता है। – Hopeless

संबंधित मुद्दे