الدقيقة من مزود الاستعلام
لا يمكن لموفر Queryable معالجة هذا:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ToList();
لن يتعامل مع أي تعبير يستخدم السلسلة المحرف ، لكنه سيحلل ذلك دون صعوبة:
var result = _context.Humans .Select(x => "Name " + x.Name + " Age " + x.Age) .Where(x => x != "") .ToList();
من الأمور المؤلمة بشكل خاص تصحيح الأخطاء بعد تشغيل ClientEvaluation (استثناء عند الحساب على العميل) ، ويجب إخضاع جميع ملفات تخصيص المخطط التلقائي لتحليل دقيق للعثور على هذا الاستيفاء. دعنا نتعرف على الأمر ونقدم حلنا للمشكلة.
نحن تصحيح
تتم ترجمة الإقحام في شجرة Expression Tree بالشكل التالي (وهذا نتيجة لطريقة ExpressionStringBuilder.ExpressionToString ، فقد حذفت بعض العقد ، لكن بالنسبة لنا
ليست قاتلة):
أو هكذا ، عندما يكون هناك أكثر من 3 وسيطات
Format("Name:{0} Age:{1}", new [] {x.Name, Convert(x.Age, Object)))
يمكننا أن نستنتج أن الموفر ببساطة لم يتعلم كيفية التعامل مع مثل هذه الحالات ، ولكن يمكنهم تعليمه لتقليل هذه الحالات إلى ToString () القديم الجيد ، والتي يتم فرزها مثل هذا:
((("Name: " + x.Name) + " Age: ") + Convert(x.Age, Object)))
أريد أن أكتب زائرًا يمر عبر شجرة التعبير ، وتحديداً من خلال عقد MethodCallExpression واستبدل طريقة التنسيق بالسلسلة. إذا كنت معتادًا على تعبيرات Expression Trees ، فأنت تعلم أن C # توفر لنا زائرها لاجتياز الشجرة - ExpressionVisitor ، بالنسبة لأولئك الذين ليسوا على دراية ، سيكون ذلك مثيراً للاهتمام .
يكفي لتجاوز أسلوب VisitMethodCall وتعديل القيمة المرجعة له قليلاً. معلمة الطريقة هي من النوع MethodCallExpression ، والتي تحتوي على معلومات حول الطريقة نفسها وحول الوسائط التي يتم تمريرها إليها.
لنقسم المهمة إلى عدة أجزاء:
- تحديد أنه كان الأسلوب Format الذي جاء إلى VisitMethodCall
- استبدال هذه الطريقة بسلسلة السلسلة
- معالجة جميع الأحمال الزائدة من طريقة التنسيق التي يمكن تلقيها
- اكتب طريقة تمديد والتي سيتصل بها زائرنا
الجزء الأول بسيط للغاية ، يحتوي أسلوب التنسيق 4 على حمولات زائدة ليتم بناؤها
في شجرة التعبير
public static string Format(string format, object arg0) public static string Format(string format, object arg0,object arg1) public static string Format(string format, object arg0,object arg1,object arg2) public static string Format(string format, params object[] args)
نحصل على استخدام انعكاس MethodInfo بهم
private IEnumerable<MethodInfo> FormatMethods => typeof(string).GetMethods().Where(x => x.Name.Contains("Format"))
فئة ، الآن يمكننا تحديد أن طريقة التنسيق "تأتي" إلى MethodCallExpression.
عند اجتياز شجرة ، قد يأتي "VisitMethodCall":
- تنسيق الأسلوب مع وسيطات الكائن
- تنسيق الأسلوب مع كائن [] الوسيطة
- ليس طريقة التنسيق على الإطلاق
نمط مخصص بعض ماتشينج
حتى الآن ، لا يمكن حل سوى 3 شروط بمساعدة إذا ، ولكن ، على افتراض أنه في المستقبل سيكون علينا توسيع هذه الطريقة ، نضع جميع الحالات في بنية البيانات هذه:
public class PatternMachingStructure { public Func<MethodInfo, bool> FilterPredicate { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>> SelectorArgumentsFunc { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>, Expression> ReturnFunc { get; set; } } var patternMatchingList = new List<PatternMachingStructure>()
باستخدام FilterPredicate ، نحدد أي من الحالات الثلاث التي نتعامل معها. هناك حاجة إلى SelectorArgumentFunc لإحضار وسيطات أسلوب Format إلى نموذج موحد ، وهو أسلوب ReturnFunc ، الذي سيعيد التعبير الجديد إلينا.
الآن دعونا نحاول استبدال تمثيل الاستيفاء بسلسلة ، لذلك سنستخدم الطريقة التالية:
private Expression InterpolationToStringConcat(MethodCallExpression node, IEnumerable<Expression> formatArguments) {
سيتم استدعاء InterpolationToStringConcat من الزائر ، حيث يتم إخفاؤه خلف ReturnFunc
(عندما node.Method == string.Format)
protected override Expression VisitMethodCall(MethodCallExpression node) { var pattern = patternMatchingList.First(x => x.FilterPredicate(node.Method)); var arguments = pattern.SelectorArgumentsFunc(node); var expression = pattern.ReturnFunc(node, arguments); return expression; }
الآن نحن بحاجة إلى كتابة منطق للتعامل مع الأحمال الزائدة المختلفة للأسلوب Format ، إنه تافه للغاية ويقع في patternMachingList
patternMatchingList = new List<PatternMachingStructure> {
وفقًا لذلك ، في أسلوب VisitMethodCall ، سننتقل إلى هذه الورقة حتى أول FilterPredicate الموجب ، ثم نقوم بتحويل الوسائط (SelectorArgumentFunc) وتنفيذ ReturnFunc.
دعنا نكتب Extention ، داعيا أننا يمكن أن تحل محل الاستيفاء.
يمكننا الحصول على التعبير ، ونقله إلى الزائر لدينا ، ثم استدعاء طريقة واجهة IQuryableProvider CreateQuery ، والتي ستحل محل شجرة التعبير الأصلية بشعارنا:
public static IQueryable<T> ReWrite<T>(this IQueryable<T> qu) { var result = new InterpolationStringReplacer<T>().Visit(qu.Expression); var s = (IQueryable<T>) qu.Provider.CreateQuery(result); return s; }
انتبه إلى Cast qu.Provider.CreateQuery (نتيجة) من النوع IQueryable في IQueryable <T> ، هذه ممارسة عامة بشكل عام لـ c # (انظر IEtnableable <T>) ، نشأت بسبب الحاجة إلى معالجة جميع الواجهات العامة في فئة واحدة ، من يريد قبول IQueryable / IEnumerable ، ومعالجته باستخدام طرق واجهة شائعة.
كان من الممكن تجنب ذلك عن طريق إرسال T إلى الفئة الأساسية ، وهذا ممكن باستخدام التباين المشترك ، لكنه يفرض أيضًا بعض القيود على أساليب الواجهة (المزيد حول هذا الموضوع في المقالة التالية).
يؤدي
قم بتطبيق "إعادة الكتابة" على التعبير في بداية المقالة
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ReWrite() .ToList();
جيثب