2012-06-22 13 views
10

में संपत्ति संदर्भों के साथ आदिम पैरामीटर को प्रतिस्थापित करने के लिए एक LINQ अभिव्यक्ति विजिटर का उपयोग करना, मैं अपने सिस्टम के एक हिस्से के लिए एक डेटा परत लिखने की प्रक्रिया में हूं जो हर रोज चलने वाली स्वचालित नौकरियों के बारे में जानकारी लॉग करता है - नौकरी का नाम , यह कितना समय चल रहा था, परिणाम क्या था, आदिलैम्बडा अभिव्यक्ति

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

हालांकि, मैं अपने इंटरफेस को नौकरी की जानकारी देखने के लिए उपयोग किए जाने वाले मानदंडों में बहुत लचीला बनाना चाहता हूं। उदाहरण के लिए, उपयोगकर्ता इंटरफ़ेस को उपयोगकर्ता को जटिल प्रश्नों को निष्पादित करने की अनुमति देनी चाहिए जैसे "मुझे सभी नौकरियां 'हैलो' दें जो 10:00 बजे से 11:00 बजे के बीच चलती थीं।" जाहिर है, यह गतिशील रूप से निर्मित Expression पेड़ों के लिए नौकरी की तरह दिखता है।

तो मैं अपने डेटा परत (भंडार) और प्रकार Expression<Func<string, DateTime, ResultCode, long, bool>> (लैम्ब्डा अभिव्यक्ति) के LINQ एक्सप्रेशन स्वीकार किया जाता है ऐसा करने में सक्षम होना चाहते हैं क्या तो पीछे के दृश्य परिवर्तित कि एक अभिव्यक्ति के लिए लैम्ब्डा कि मेरी इकाई की रूपरेखा ObjectContext उपयोग कर सकते हैं एक Where() खंड के अंदर एक फिल्टर के रूप में।

संक्षेप में, मैं Expression<Func<svc_JobAudit, bool>>, जहां svc_JobAudit इकाई की रूपरेखा डेटा वस्तु जो तालिका जहां काम के बारे में जानकारी संग्रहीत किया जाता है से मेल खाती है है के प्रकार Expression<Func<string, DateTime, ResultCode, long, bool>> की एक लैम्ब्डा अभिव्यक्ति कन्वर्ट करने के लिए कोशिश कर रहा हूँ। (पहले प्रतिनिधि में चार पैरामीटर नौकरी के नाम से मेल खाते हैं, परिणामस्वरूप, और क्रमशः एमएस में कितना समय लगेगा)

मैं ExpressionVisitor कक्षा का उपयोग करके बहुत अच्छी प्रगति कर रहा था जब तक कि मैं हिट नहीं करता

जब 'VisitLambda' से कहा जाता है, प्रकार 'System.Linq.Expressions.ParameterExpression' का एक नोड को फिर से लिखने की एक गैर-शून्य मान लौटाना चाहिए: एक ईंट की दीवार और इस त्रुटि संदेश के साथ एक InvalidOperationException प्राप्त इसी प्रकार का। वैकल्पिक रूप से, 'VisitLambda' को ओवरराइड करें और इसे इस प्रकार के बच्चों से न देखने के लिए बदलें।

मैं पूरी तरह से परेशान हूं। क्यों बिल्ली यह अभिव्यक्ति नोड्स को परिवर्तित करने की अनुमति नहीं देगा जो संदर्भ गुणों को संदर्भित करता है जो नोड्स को संदर्भित करता है? क्या इस बारे में जाने का कोई और तरीका है?

यहां कुछ नमूना कोड है:

namespace ExpressionTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Expression<Func<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello"; 
      var result = ConvertExpression(expression); 
     } 

     private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression) 
     { 
      var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit))); 
      return newExpression; 
     } 
    } 

    class ReplaceVisitor : ExpressionVisitor 
    { 
     public Expression Modify(Expression expression) 
     { 
      return Visit(expression); 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (node.Type == typeof(string)) 
      { 
       return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName"); 
      } 
      return node; 
     } 
    } 
} 
+1

[लैम्ब्डा अभिव्यक्ति में पैरामीटर बदलें] (http://stackoverflow.com/questions/11159697/replace-parameter-in-lambda-expression) –

उत्तर

6

समस्या दो गुना था:

  • मैं लैम्ब्डा अभिव्यक्ति प्रकार यात्रा करने के लिए कैसे गलत समझ रहा था। मैं अभी भी एक लैम्ब्डा लौट रहा था जो नए प्रतिनिधि से मेल खाने के लिए एक नया लैम्ब्डा लौटने के बजाय पुराने प्रतिनिधि से मेल खाता था।

  • मुझे नए ParameterExpression इंस्टेंस का संदर्भ रखने की आवश्यकता है, जो मैं नहीं कर रहा था।

नए कोड इस तरह दिखता है (ध्यान दें कि किस आगंतुक अब एक ParameterExpression इकाई की रूपरेखा डेटा वस्तु मिलान के लिए एक संदर्भ स्वीकार करता है):

class Program 
{ 
    const string conString = @"myDB"; 

    static void Main(string[] args) 
    { 
     Expression<Func<string, DateTime, byte, long, bool>> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed; 
     var criteria = ConvertExpression(expression); 

     using (MyDataContext dataContext = new MyDataContext(conString)) 
     { 
      List<svc_JobAudit> jobs = dataContext.svc_JobAudit.Where(criteria).ToList(); 
     } 
    } 

    private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, byte, long, bool>> expression) 
    { 
     var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit"); 
     var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression.Body, jobAuditParameter), jobAuditParameter); 
     return newExpression; 
    } 
} 

class ReplaceVisitor : ExpressionVisitor 
{ 
    private ParameterExpression parameter; 

    public Expression Modify(Expression expression, ParameterExpression parameter) 
    { 
     this.parameter = parameter; 
     return Visit(expression); 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     return Expression.Lambda<Func<svc_JobAudit, bool>>(Visit(node.Body), Expression.Parameter(typeof(svc_JobAudit))); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     if (node.Type == typeof(string)) 
     { 
      return Expression.Property(parameter, "JobName"); 
     } 
     else if (node.Type == typeof(DateTime)) 
     { 
      return Expression.Property(parameter, "RanAt"); 
     } 
     else if (node.Type == typeof(byte)) 
     { 
      return Expression.Property(parameter, "Result"); 
     } 
     else if (node.Type == typeof(long)) 
     { 
      return Expression.Property(parameter, "Elapsed"); 
     } 
     throw new InvalidOperationException(); 
    } 
} 
+0

यह उपयोगी है, धन्यवाद था। –

+0

ExpressionVisitor में VisitLambda के ओवरराइड नेस्टेड लैम्ब्डा अभिव्यक्तियों में समस्या है। मान लीजिए (एक बना हुआ उदाहरण), अभिव्यक्ति को परिवर्तित किया गया था: (जॉबनाम, रनएट, परिणाम कोड, समाप्त हो गया) => AuditLogTable.Any (a => a.JobName == jobName && a.Date> = ranAt) ; उप लैम्ब्डा "एक => a.JobName ..." VisitLambda द्वारा कार्रवाई की जाएगी और में बदल दिया: svc_JobAudit => a.JobName == jobName && a.Date> = रानात ... जो असफल हो जायेगी। मेरे मामले में, मैंने अभी ओवरराइड हटा दिया है (जिसे केवल उप-लैम्ब्डा के लिए बुलाया जाता है, और मुख्य शीर्ष-स्तर लैम्ब्डा के लिए नहीं कहा जाता है) –

1

स्वीकार किए जाते हैं जवाब कुछ विशेष करने के लिए 'hardcoded' है प्रकार के। किसी भी अन्य अभिव्यक्ति (लैम्ब्डा, निरंतर, ...) के लिए पैरामीटर को प्रतिस्थापित करने से अधिक सामान्य अभिव्यक्ति पुनरावर्तक यहां दिया गया है। लैम्ब्डा अभिव्यक्ति के मामले में अभिव्यक्ति के हस्ताक्षर को प्रतिस्थापित मूल्य द्वारा आवश्यक पैरामीटर को शामिल करने के लिए बदलने की आवश्यकता है।

public class ExpressionParameterSubstitute : System.Linq.Expressions.ExpressionVisitor 
{ 
    private readonly ParameterExpression from; 
    private readonly Expression to; 
    public ExpressionParameterSubstitute(ParameterExpression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     if (node.Parameters.All(p => p != this.from)) 
      return node; 

     // We need to replace the `from` parameter, but in its place we need the `to` parameter(s) 
     // e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool> 
     // e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool> 

     var toLambda = to as LambdaExpression; 
     var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>(); 

     ReadOnlyCollection<ParameterExpression> substitutedParameters 
      = new ReadOnlyCollection<ParameterExpression>(node.Parameters 
       .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1)) 
       .ToList()); 

     var updatedBody = this.Visit(node.Body);  // which will convert parameters to 'to' 
     return Expression.Lambda(updatedBody, substitutedParameters); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     var toLambda = to as LambdaExpression; 
     if (node == from) return toLambda?.Body ?? to; 
     return base.VisitParameter(node); 
    } 
} 
संबंधित मुद्दे