تفاصيل QueryProvider
يتعذر على QueryProvider التعامل مع هذا:
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 (باستثناء الحساب من جانب العميل) ، نظرًا لأن جميع ملفات تعريف Automapper ينبغي تحليلها بدقة من أجل الاستيفاء. دعونا نعرف ما هو واقتراح حلنا للمشكلة.
إصلاح الأشياء
يتم تحويل الاستيفاء في شجرة التعبير مثل هذا (وهذا نتيجة لطريقة 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 ) واستبدل طريقة التنسيق بالتسلسل . إذا كنت معتادًا على أشجار التعبير ، فأنت تعلم أن C # توفر لزائرها تجاوز الشجرة - ExpressionVisitor . مزيد من المعلومات للمهتمين .
كل ما نحتاج إليه هو تجاوز أسلوب VisitMethodCall وتعديل القيمة التي تم إرجاعها قليلاً. معلمة الطريقة هي من نوع MethodCallExpression ، والتي تحتوي على معلومات حول الطريقة نفسها والوسائط التي يتم إمدادها بها.
لنقسم المهمة إلى عدة أجزاء:
- تحديد أنها طريقة التنسيق التي جاءت في VisitMethodCall ؛
- استبدال الأسلوب مع تسلسل السلاسل؛
- التعامل مع جميع الأحمال الزائدة من طريقة تنسيق يمكن أن يكون لدينا ؛
- اكتب طريقة التمديد للاتصال بزائرنا.
الجزء الأول بسيط: تحتوي الطريقة Format على 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 شروط فقط ، يمكننا التعامل معها باستخدام if ، ولكن بما أننا نفترض أننا سنحتاج إلى توسيع هذه الطريقة في المستقبل ، فلنزيل جميع الحالات في بنية البيانات هذه:
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 :
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; }
الآن نحن بحاجة إلى كتابة منطق للتعامل مع جميع الأحمال الزائدة طريقة التنسيق . انها تافهة إلى حد ما وتقع داخل نمطخطة القائمة :
patternMatchingList = new List<PatternMachingStructure> {
وفقًا لذلك ، سوف نتبع تلك القائمة في طريقة VisitMethodCall حتى أول مرشح إيجابي ، ثم تحويل الوسائط ( SelectorArgumentFunc ) وتنفيذ ReturnFunc .
دعنا نكتب طريقة تمديد يمكننا أن ندعو لاستبدال الاستيفاء.
يمكننا الحصول على تعبير ، تسليمه للزائر ، ومن ثم استدعاء الأسلوب 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; }
انتبه إلى أن يلقي qu.Provider.CreateQuery (نتيجة) التي لديها طريقة IQueryable في IQueryable <T>. يستخدم على نطاق واسع في C # (انظر إلى واجهة <T> IEnumerable !) ، وقد جاء من الحاجة إلى التعامل مع جميع الواجهات العامة بفئة واحدة تريد الحصول على IQueryable / IEnumerable ، والتعامل معها باستخدام طرق واجهة عامة.
كان بإمكاننا تجنب ذلك من خلال جلب T إلى فئة أساسية (من خلال التغاير) ، لكنه يضع بعض القيود على أساليب الواجهة.
نتيجة
قم بتطبيق ReWrite على تعبير linq في أعلى المقالة:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ReWrite() .ToList();
جيثب