بالنسبة لمعظم المطورين ، يقتصر استخدام شجرة التعبير على تعبيرات لامدا في LINQ. في كثير من الأحيان لا نولي أي أهمية لكيفية عمل التكنولوجيا "تحت غطاء المحرك".
في هذه المقالة سأوضح لك تقنيات متقدمة للعمل مع أشجار التعبير: القضاء على ازدواجية التعليمات البرمجية في LINQ ، إنشاء التعليمات البرمجية ، metaprogramming ، transpilation ، أتمتة الاختبار.
سوف تتعلم كيفية استخدام شجرة التعبير مباشرة ، وما هي المزالق التي أعدتها التكنولوجيا ، وكيفية الالتفاف عليها.

تحت القص - نص الفيديو والنص من
تقريري مع DotNext 2018 Piter.
اسمي مكسيم Arshinov ، وأنا مؤسس مشارك لشركة Hi-Tech Group الاستعانة بمصادر خارجية. نحن بصدد تطوير برامج للأعمال ، وسأتحدث اليوم عن كيفية استخدام تقنية شجرة التعبير في العمل اليومي وكيف بدأت في مساعدتنا.
لم أكن أرغب أبدًا في دراسة البنية الداخلية لأشجار التعبير ، وبدا أن هذا كان نوعًا من التكنولوجيا الداخلية لكي يعمل فريق .NET لـ LINQ ، ولا يحتاج المبرمجون التطبيقيون إلى معرفة API الخاص به. اتضح أن هناك بعض المشاكل التطبيقية التي تحتاج إلى حل. حتى أحببت الحل ، كان علي أن أتسلق إلى الأمعاء.
امتدت هذه القصة بأكملها في الوقت المناسب ، كانت هناك مشاريع مختلفة ، حالات مختلفة. حدث شيء ، وانتهيت منه ، لكنني سأسمح لنفسي بالتضحية بالصدق التاريخي لصالح عرض أكثر فنية ، لذلك ستكون جميع الأمثلة على نفس نموذج الموضوع - متجر عبر الإنترنت.

تخيل أننا جميعًا نكتب متجرًا عبر الإنترنت. يحتوي على منتجات وعلامة اختيار "للبيع" في لوحة المشرف. سوف نعرض فقط تلك المنتجات التي تحمل علامة الاختيار هذه على الجزء العام.

نأخذ بعض DbContext أو NHibernate ، ونكتب Where () ، ونخرج IsForSale.
كل شيء على ما يرام ، لكن قواعد العمل ليست هي نفسها حتى نكتبها مرة واحدة وإلى الأبد. تتطور بمرور الوقت. يأتي أحد المديرين ويقول إنه لا يزال يتعين علينا مراقبة الرصيد وعرض السلع التي تحتوي على أرصدة من جانب الجمهور فقط ، مع عدم نسيان علامة الاختيار.

نضيف بسهولة مثل هذه الخاصية. الآن يتم تغليف قواعد أعمالنا ، يمكننا إعادة استخدامها.

لنحاول تحرير LINQ. هل كل شيء بخير هنا؟
لا ، لن يعمل هذا ، لأن IsAvailable لا يعين قاعدة البيانات ، فهذا هو رمزنا ، ولا يعرف موفر الاستعلام كيفية تحليلها.

يمكننا أن نقول له أن ممتلكاتنا لديها مثل هذه القصة. ولكن الآن هذه المضاعفة مكررة في كل من تعبير Linq والممتلكات.
Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0;
لذا ، في المرة التالية التي تتغير فيها لامدا ، سيتعين علينا القيام Ctrl + Shift + F في المشروع. بطبيعة الحال ، لن نجد جميعًا - البق والوقت. اريد ان اتجنب هذا

يمكننا الانتقال من هذا الجانب ووضع قائمة أخرى () أمام أين (). هذا قرار سيئ ، لأنه إذا كان هناك مليون سلعة في قاعدة البيانات ، فإن الجميع يرتفع إلى ذاكرة الوصول العشوائي والمرشحات هناك.

إذا كان لديك ثلاثة منتجات في متجر ، فإن الحل جيد ، ولكن في التجارة الإلكترونية هناك عادة المزيد. نجح هذا فقط لأنه على الرغم من تشابه لامداس مع بعضها البعض ، لديهم نوع مختلف تمامًا. في الحالة الأولى ، هذا هو مفوض فونك ، وفي الحالة الثانية ، شجرة تعبير. تبدو متشابهة ، الأنواع مختلفة ، الرمز الثانوي مختلف تمامًا.

للانتقال من التعبير إلى المفوض ، ما عليك سوى استدعاء الأسلوب Compile (). يوفر هذا API .NET: هناك تعبير - تجميعها ، تلقى مفوض.
لكن كيف أعود؟ هل هناك أي شيء في .NET للانتقال من مندوب إلى أشجار التعبير؟ إذا كنت معتادًا على LISP ، على سبيل المثال ، فهناك آلية استشهاد تسمح بتفسير الرمز على أنه بنية بيانات ، ولكن في .NET ليس كذلك.
صريحة أم مندوبين؟
بالنظر إلى أن لدينا نوعين من lambdas ، يمكننا أن نفلسف ما هو أساسي: شجرة التعبير أو المندوبين.
للوهلة الأولى ، يكون الجواب واضحًا: نظرًا لوجود طريقة Compile () رائعة ، فإن شجرة التعبير أساسية. وينبغي أن نستقبل المندوب عن طريق تجميع التعبير. لكن التجميع عملية بطيئة ، وإذا بدأنا في القيام بذلك في كل مكان ، نحصل على تدهور في الأداء. بالإضافة إلى ذلك ، سوف نستقبلها في أماكن عشوائية ، حيث كان يجب تجميع التعبير في مندوب ، وسيكون هناك انخفاض في الأداء. يمكنك العثور على هذه الأماكن ، ولكنها ستؤثر على وقت استجابة الخادم ، وبشكل عشوائي.

لذلك ، يجب تخزينها مؤقتًا بطريقة ما. إذا استمعت إلى حديث عن هياكل البيانات المتزامنة ، فأنت تعرف عن ConcurrentDictionary (أو تعرف عنها فقط). سأحذف تفاصيل حول طرق التخزين المؤقت (مع الأقفال وليس الأقفال). الأمر فقط أن ConcurrentDictionary يحتوي على طريقة GetOrAdd () بسيطة ، وأبسط تطبيق هو إدخاله في ConcurrentDictionary وتخزينه مؤقتًا. في المرة الأولى التي نحصل فيها على التجميع ، ولكن بعد ذلك سيكون كل شيء سريعًا ، لأن المندوب قد تم تجميعه بالفعل.

ثم يمكنك استخدام طريقة التمديد هذه ، يمكنك استخدام وإعادة بناء الكود الخاص بنا مع IsAvailable () ، ووصف التعبير ، وتجميع خصائص IsAvailable () واستدعائها بالنسبة للكائن الحالي.
هناك حزمتان على الأقل لتطبيق هذا:
Microsoft.Linq.Translations و
Signum Framework (
إطار عمل مفتوح المصدر كتبه شركة تجارية). هناك وهناك نفس القصة مع تجميع المندوبين. واجهة برمجة تطبيقات مختلفة قليلاً ، ولكن كل شيء كما أظهرته في الشريحة السابقة.
ومع ذلك ، ليس هذا هو النهج الوحيد ، ويمكنك الانتقال من المندوبين إلى التعبيرات. لفترة طويلة كان هناك
مقال عن حبري حول المندوب Decompiler ، حيث يدعي المؤلف أن كل التجميع سيئ ، لأنه لفترة طويلة.
بشكل عام ، كان المندوبون قبل التعبيرات ، ويمكنك الانتقال إليهم من المندوبين. للقيام بذلك ، يستخدم المؤلف methodBody.GetILAsByteArray ()؛ من Reflection ، الذي يعيد رمز IL بأكمله للطريقة كمصفوفة من البايتات. إذا وضعته في انعكاس أكبر ، يمكنك الحصول على تمثيل كائن لهذه الحالة ، وتصفحها باستخدام حلقة وبناء شجرة تعبير. وبالتالي ، فإن الانتقال العكسي ممكن أيضًا ، ولكن يجب القيام به يدويًا.

لكي لا تتصفح جميع الخصائص ، يقترح المؤلف تعليق السمة المحسوبة للإشارة إلى أن هذه الخاصية تحتاج إلى تضمينها. قبل الطلب ، نتسلق إلى IsAvailable () ، وسحب رمز IL الخاص به ، وقم بتحويله إلى شجرة التعبير واستبدال استدعاء IsAvailable () بما هو مكتوب في هذا النص. اتضح مثل هذا التضمين اليدوي.

لكي يعمل هذا ، قبل تمرير كل شيء إلى ToList () ، نسمي الطريقة الخاصة Decompile (). يوفر ديكور للغة الأصلية القابلة للاستعلام والمبطنة. فقط بعد ذلك نمرر كل شيء إلى مزود الاستعلام ، وكل شيء على ما يرام معنا.

المشكلة الوحيدة في هذا النهج هي أن التفويض Decompiler 0.23.0 لن يمضي قدمًا ، ولا يوجد دعم Core ، ويقول المؤلف نفسه أن هذا هو ألفا عميقة ، وهناك العديد منها غير مكتمل ، لذلك لا يمكنك استخدامه في الإنتاج. على الرغم من أننا سنعود إلى هذا الموضوع.
العمليات المنطقية
اتضح أننا حللنا مشكلة ازدواجية شروط معينة.

لكن غالبًا ما يتعين الجمع بين الظروف باستخدام المنطق البولياني. كان لدينا IsForSale () ، InStock ()> 0 ، وبينهما الشرط "AND". إذا كان هناك أي شرط آخر ، أو مطلوب شرط "أو".

في حالة "و" ، يمكنك الغش وإلقاء كل العمل على موفر الاستعلام ، أي كتابة الكثير من أين () على التوالي ، فهو يعرف كيفية القيام بذلك.

إذا كان "OR" مطلوبًا ، فلن ينجح ذلك ، لأن WhereOr () ليس في LINQ ، ولا يتم تحميل عامل التشغيل | | بشكل زائد مع التعبيرات.
المواصفات
إذا كنت معتادًا على كتاب DDD الخاص بـ Evans أو كنت تعرف شيئًا عن نمط المواصفات ، أي نمط تصميم مصمم خصيصًا لهذا الغرض. هناك العديد من قواعد العمل وتريد دمج العمليات في منطق منطقي - تنفيذ المواصفات.

المواصفات هي مثل هذا المصطلح ، وهو نمط قديم من جافا. وفي Java ، خاصة في القديم ، لم يكن هناك LINQ ، لذلك تم تنفيذه هناك فقط في شكل isSatisfiedBy () ، أي المندوبين فقط ، ولكن لم يكن هناك أي حديث عن التعبيرات. هناك تطبيق على الإنترنت يسمى
LinqSpecs ، على الشريحة
ستراه . لقد أودعتها بنفسي ، لكن الفكرة تعود للمكتبة.
هنا ، يتم تحميل جميع عوامل التشغيل المنطقية بشكل زائد ، ويتم تحميل عوامل التشغيل الحقيقية والباطلة بشكل زائد بحيث يعمل العاملان "&&" و "||" ، بدون عمل علامة واحدة واحدة فقط.

بعد ذلك ، نضيف عبارات ضمنية تجعل المترجم يفترض أن المواصفات عبارة عن تعبيرات ومندوبين. في أي مكان يجب أن يأتي فيه التعبير <> أو Func <> إلى الوظيفة ، يمكنك تمرير المواصفات. نظرًا لزيادة عامل التشغيل الضمني ، سيقوم المحول البرمجي بتحليل واستبدال خصائص Expression أو IsSatisfiedBy.

يمكن تنفيذ IsSatisfiedBy () عن طريق تخزين التعبير الذي جاء. على أي حال ، اتضح أننا قادمون من Expression ، والمندوب يتوافق معها ، وأضفنا دعمًا لمشغلي Boolean. الآن يمكن ترتيب كل هذا. يمكن وضع قواعد العمل في مواصفات ثابتة ، معلنة ومدمجة.
public static readonly Spec<Product> IsForSaleSpec = new Spec<Product>(x => x.IsForSale); public static readonly Spec<Product> IsInStockSpec = new Spec<Product>(x => x.InStock > 0);

تتم كتابة كل قاعدة عمل مرة واحدة فقط ، ولن تضيع في أي مكان ، ولن يتم تكرارها ، ويمكن دمجها. يمكن للأشخاص القادمين إلى المشروع معرفة ما لديك ، وما هي الشروط ، وفهم نموذج الموضوع.

توجد مشكلة صغيرة: لا يحتوي التعبير على أساليب And () و Or () و Not (). هذه طرق إرشادية ، يجب تنفيذها بشكل مستقل.

كانت المحاولة الأولى في التنفيذ هذا. حول شجرة التعبير ، هناك القليل من التوثيق على الإنترنت ، وكلها غير مفصلة. لذلك ، حاولت فقط أخذ Expression ، والضغط على Ctrl + Space ، ورؤية OrElse () ، وقراءة عنه. اجتازت اثنين من التعبير لتجميع والحصول على لامدا. هذا لن يعمل.

والحقيقة أن هذا التعبير يتكون من جزأين: المعلمة والجسم. يتكون الثاني أيضًا من معلمة وجسم. في OrElse () ، تحتاج إلى تمرير أجسام التعبيرات ، أي أنه من غير المجدي مقارنة lambdas مع "AND" و "OR" ، هذا لن ينجح. نحن نصلح ، لكنه لن يعمل مرة أخرى.
ولكن إذا كان هناك آخر مرة لم يكن هناك دعم غير متوقع بأن لامدا لم تكن مدعومة ، فهناك الآن قصة غريبة عن المعلمة 1 ، المعلمة 2 ، "هناك خطأ ما ، لن أعمل".
ج # 7.0 في باختصار
ثم اعتقدت أن طريقة الوخز العلمية لن تنجح ، فأنا بحاجة إلى اكتشافها. بدأ غوغل ووجد موقع كتاب البحري "
C # 7.0 في باختصار ".

يصف جوزيف الباهري ، وهو أيضًا مطور مكتبة LINQKit و LINQPad ، هذه المشكلة. أنه لا يمكنك فقط استخدام Expression والجمع بينها ، وإذا كنت تأخذ Expression السحري ، فإن Invoke () ستعمل.
السؤال: ما هو Expression.Invoke ()؟ انتقل إلى Google مرة أخرى. يقوم بإنشاء InvocationExpression الذي يطبق تعبير مفوض أو لامدا على قائمة الوسائط.

إذا قرأت هذا الرمز لك الآن بعد أن أخذنا Expression.Invoke () ، فإننا نمرر المعلمات ، ثم يكتب نفس الشيء باللغة الإنجليزية. لا يتضح الأمر أكثر. يوجد بعض Expression.Invoke السحري () الذي يحل هذه المشكلة مع المعلمات لسبب ما. يجب أن نؤمن ، ليس من الضروري أن نفهم.

في نفس الوقت ، إذا حاولت تغذية EFs مثل هذا التعبير المدمج ، فسوف تسقط مرة أخرى وتقول أن Expression.Invoke () غير مدعوم. بالمناسبة ، بدأت EF Core في الدعم ، ولكن EF 6 لا تصمد. لكن البحري يقدم فقط كتابة AsExpandable () ، وكلها تعمل.

ويمكنك استبدال الاستعلامات الفرعية في Expression حيث نحتاج إلى مندوب. لجعلها متطابقة ، نكتب Compile () ، ولكن في نفس الوقت ، إذا كتبنا AsExpandable () ، كما يقترح البحري ، فإن هذا Compile () لن يحدث في الواقع ، ولكن كل شيء سيتم بطريقة سحرية بطريقة صحيحة.

لم أصدق كلمة وتسلقت إلى المصدر. ما هي طريقة AsExpandable ()؟ يحتوي على استعلام و QueryOptimizer. سنترك الثانية من بين الأقواس ، لأنها غير مثيرة للاهتمام ، ولكن ببساطة الغراء التعبير: إذا كان هناك 3 + 5 ، فسيضع 8.

من المثير للاهتمام أن يتم استدعاء طريقة Expand () لاحقًا ، وبعدها queryOptimizer ، ثم يتم تمرير كل شيء إلى موفر الاستعلام بطريقة ما بعد إعادة التوسيع بطريقة ().

نفتحه ، إنه الزائر ، من الداخل نرى Compile () غير الأصلي ، الذي يجمع شيئًا آخر. لن أخبرك ما هو بالضبط ، على الرغم من أن هذا له معنى معين ، لكننا نزيل مجموعة واحدة ونستبدلها بأخرى. رائع ، لكنه يتفوق على مستوى 80 للتسويق لأن تأثير الأداء لن يذهب إلى أي مكان.
بحثا عن بديل
اعتقدت أن هذا لن ينجح وبدأت في البحث عن حل آخر. ووجدته. هناك مثل بيت مونتغمري الذي
يكتب أيضًا عن هذه المشكلة ويزعم أن البحري تزييفها.

تحدث بيت مع مطوري EF ، وعلموه أن يجمع بين كل شيء دون التعبير. الفكرة بسيطة للغاية: كان الكمين مع المعلمات. والحقيقة أنه مع تركيبة التعبير ، توجد معلمة للتعبير الأول ومعلمة للتعبير الثاني. لا تتطابق. تم لصق الجثث معًا ، ولكن المعلمات ظلت معلقة في الهواء. يجب أن يتم تضميدهم بالطريقة الصحيحة.
للقيام بذلك ، تحتاج إلى تجميع القاموس من خلال النظر في معلمات التعبيرات ، إذا لم تكن لامدا من معلمة واحدة. نحن ننشئ قاموسًا ، ونعيد ربط جميع معلمات الثانية بالمعلمات الأولى ، بحيث تدخل المعلمات الأولية Expression ، تدفع عبر الجسم كله الذي قمنا بلصقه معًا.

تسمح لك هذه الطريقة البسيطة بالتخلص من كل الكمائن باستخدام Expression.Invoke (). علاوة على ذلك ، في تنفيذ Pete Montgomery ، يصبح هذا أكثر برودة. يحتوي على طريقة Compose () التي تسمح لك بدمج أي تعبير.

نأخذ التعبير ومن خلال AndAlso أيضًا ، نعمل بدون توسيع (). هذا التطبيق هو الذي يستخدم في العمليات المنطقية.
المواصفات والوحدات
كان كل شيء على ما يرام حتى أصبح من الواضح أن المجاميع موجودة في الطبيعة. بالنسبة إلى الأشخاص غير المألوفين ، سأشرح: إذا كان لديك نموذج نطاق وتمثل جميع الكيانات المرتبطة ببعضها البعض في شكل أشجار ، فإن الشجرة المعلقة بشكل منفصل هي إجمالي. سيُطلق على الطلب مع عناصر الطلب اسم التجميع ، وجوهر الطلب هو جذر التجميع.

إذا ، بالإضافة إلى المنتجات ، لا تزال هناك فئات مع إعلان عن قاعدة عمل لها في شكل مواصفة ، أن هناك تصنيفًا معينًا يجب أن يكون أكثر من 50 ، كما قال المسوقون ونريد استخدامه بهذه الطريقة ، فهذا جيد.

ولكن إذا أردنا سحب البضائع من فئة جيدة ، فهذا أمر سيئ مرة أخرى ، لأن أنواعنا لم تتطابق. مواصفات الفئة ، ولكن المنتجات مطلوبة.

مرة أخرى ، نحن بحاجة إلى حل المشكلة بطريقة أو بأخرى. الخيار الأول: استبدل Select () بـ SelectMany (). لا أحب شيئين هنا. أولاً ، لا أعرف كيف يتم تنفيذ دعم SelectMany () في جميع موفري طلبات البحث الشائعة. ثانيًا ، إذا قام شخص ما بكتابة موفر استعلام ، فإن أول شيء سيقوم به هو كتابة رمي لم يتم تطبيق الاستثناء و SelectMany (). والنقطة الثالثة: يعتقد الناس أن SelectMany () إما وظيفية ، أو ينضمون ، وعادة لا ترتبط باستعلام SELECT.
تكوين
أود استخدام Select () وليس SelectMany ().

في نفس الوقت تقريبًا ، قرأت عن نظرية الفئات ، حول التكوين الوظيفي ، واعتقدت أنه إذا كانت هناك مواصفات من المنتج في المنطقة أدناه ، فهناك بعض الوظائف التي يمكن أن تنتقل من المنتج إلى الفئة ، فهناك مواصفات تتعلق بالفئة ، ثم استبدال الأول تعمل كحجة للثاني ، نحصل على ما نحتاج إليه ، مواصفات تتعلق بالمنتج. تماما مثل يعمل التركيب الوظيفي ، ولكن لأشجار التعبير.

بعد ذلك سيكون من الممكن كتابة طريقة Where () بحيث يكون من الضروري الانتقال من المنتجات إلى الفئات وتطبيق المواصفات على هذا الكيان ذي الصلة. تبدو هذه الصيغة لذوقي الشخصي مفهومة جدًا.
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable, Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where) { return queryable.Where(prop.Compose(where)); }
باستخدام طريقة Compose () ، يمكن القيام بذلك بسهولة. نأخذ مدخلات التعبير من المنتجات وندمجها مع مواصفات المنتج وهذا كل شيء.

الآن يمكنك كتابة Where (). سيعمل هذا إذا كان لديك آلة من أي طول. تحتوي الفئة على SuperCategory وأي عدد من الخصائص الأخرى التي يمكن استبدالها.
"نظرًا لأن لدينا أداة للتكوين الوظيفي ، وبما أننا نستطيع تجميعها ، وبما أننا يمكننا تجميعها ديناميكيًا ، فهذا يعني أن هناك رائحة البرمجة الوصفية!"
الإسقاطات
أين يمكننا تطبيق البرمجة الوصفية حتى نتمكن من كتابة كود أقل.

الخيار الأول هو الإسقاط. غالبًا ما يكون سحب كيان بأكمله أمرًا مكلفًا للغاية. غالبًا ما نمررها إلى الأمام ، ونسلسل JSON. لكنها لا تحتاج إلى الجوهر كله مع الركام. يمكنك القيام بذلك باستخدام LINQ بأكبر قدر ممكن من الكفاءة عن طريق كتابة Select () يدويًا. ليس صعبًا ، لكنه ممل.

بدلاً من ذلك ، أقترح على الجميع استخدام ProjectToType (). هناك مكتبتان على الأقل يمكنهما القيام بذلك: Automapper و Mapster. لسبب ما ، يعرف الكثير من الناس أن AutoMapper يمكنه إجراء رسم الخرائط في الذاكرة ، ولكن لا يعلم الجميع أنه يحتوي على ملحقات Queryable ، كما أنه يحتوي على Expression ، ويمكنه إنشاء تعبير SQL. إذا كنت لا تزال تكتب استعلامات يدوية وتستخدم LINQ ، نظرًا لأنه ليس لديك قيود أداء فائقة الخطورة ، فلا فائدة من القيام بذلك بيديك ، فهذا هو عمل الجهاز ، وليس الشخص.
التصفية
إذا كان بإمكاننا القيام بذلك مع الإسقاطات ، فلماذا لا نفعل ذلك للتصفية.

هذا هو الرمز أيضًا. يأتي مرشح. تبدو الكثير من تطبيقات الأعمال كما يلي: جاء عامل التصفية ، وأضف Where () ، وأتى عامل تصفية آخر ، وأضف Where (). كم عدد المرشحات هناك ، وتكرارها. لا شيء معقد ، ولكن الكثير من النسخ واللصق.

إذا قمنا ، بصفتنا AutoMapper ، بالقيام بذلك ، وكتابة التصفية التلقائية ، Project وفلتر حتى يفعل كل شيء بنفسه ، سيكون الأمر رائعًا - رمز أقل.

هذا ليس معقدًا. خذ Expression.Property ، قم بالسير عبر DTO وبشكل جوهري. نجد خصائص مشتركة تسمى متطابقة. إذا تم تسميتها بنفس الاسم ، فستبدو كمرشح.
بعد ذلك ، تحتاج إلى التحقق من وجود قيمة فارغة ، استخدم ثابتًا لسحب القيمة من DTO ، واستبدالها في التعبير وإضافة تحويل في حالة وجود Int و NullableInt أو Nullable آخر بحيث تتطابق الأنواع. ونضع ، على سبيل المثال ، Equals () ، مرشحًا يتحقق من المساواة.

ثم اجمع لامدا وانتقل إلى كل عقار: إذا كان هناك الكثير منهم ، فاستجمع إما من خلال "و" أو "أو" ، اعتمادًا على كيفية عمل المرشح لك.

يمكن إجراء نفس الشيء للفرز ، ولكنه أكثر تعقيدًا بعض الشيء ، نظرًا لأن طريقة OrderBy () لها نوعان من الجينات ، لذلك يجب عليك ملئهما بيديك ، استخدم التأملات لإنشاء طريقة OrderBy () من نوعين ، أدخل نوع الكيان الذي نأخذه ، نوع النوع القابل للفرز الملكية. بشكل عام ، يمكنك أيضًا القيام بذلك ، ليس صعبًا.
السؤال الذي يطرح نفسه: أين تضع أين () - على مستوى الكيان ، كما تم الإعلان عن المواصفات أو بعد الإسقاط ، وهناك وستعمل هناك.

هذا صحيح على حد سواء ، وهكذا ، لأن المواصفات ، بحكم تعريفها ، هي قواعد العمل ، ويجب علينا أن نعتز بها ونعتز بها ولا نخطئ فيها. هذه طبقة أحادية البعد. والمرشحات أكثر حول واجهة المستخدم ، مما يعني أنها تقوم بالتصفية حسب DTO. لذلك ، يمكنك وضع أين (). هناك أسئلة أكثر ترجيحًا حول مدى تعامل مزود الاستعلام مع هذا بشكل جيد ، ولكن أعتقد أن حلول ORM تكتب SQL سيئة على أي حال ، ولن يكون الأمر أسوأ بكثير. إذا كان هذا مهمًا جدًا بالنسبة لك ، فإن هذه القصة ليست عنك على الإطلاق.

كما يقولون ، من الأفضل أن ترى مرة واحدة من أن تسمع مائة مرة.
يحتوي المتجر الآن على ثلاثة منتجات: سنيكرز وسوبارو إمبريزا ومارس. متجر غريب. دعونا نحاول العثور على سنيكرز. يوجد. دعونا نرى ما مائة روبل. أيضا سنيكرز. و 500؟ تكبير ، لا يوجد شيء. و 100500 سوبارو امبريزا. عظيم ، نفس الشيء ينطبق على الفرز.
فرز أبجديًا والسعر. رمز هناك مكتوب تماما كما كان. تعمل هذه الفلاتر لأي فئات ، أيا كان. إذا حاولت البحث بالاسم ، فإن Subaru موجود أيضًا. وفي عرضي التقديمي كان يساوي (). كيف ذلك؟ والحقيقة هي أن الرمز هنا وفي العرض التقديمي مختلفان قليلاً. لقد علقت على السطر حول Equals () وأضفت بعض سحر الشارع الخاص. إذا كان لدينا نوع String ، فلن نحتاج إلى Equals () ، ولكن اتصل بـ StartWith () ، الذي تلقيته أيضًا. لذلك ، تم إنشاء مرشح مختلف للصفوف.

هذا يعني أنه هنا يمكنك الضغط على Ctrl + Shift + R ، وتحديد الطريقة والكتابة ليس إذا ، ولكن التبديل ، أو يمكنك حتى تنفيذ نمط "الإستراتيجية" ثم الجنون. يمكنك تحقيق أي رغبات حول تشغيل الفلاتر. كل هذا يتوقف على الأنواع التي تعمل بها. الأهم من ذلك ، ستعمل المرشحات بنفس الطريقة.
يمكنك الموافقة على أن تعمل الفلاتر في جميع عناصر واجهة المستخدم على النحو التالي: يتم البحث عن السلاسل بطريقة ما ، ويتم البحث عن الأموال بطريقة أخرى. نسق كل هذا ، اكتب مرة واحدة ، كل شيء سيتم بشكل صحيح في واجهات مختلفة ، ولن يكسرها مطورو آخرون ، لأن هذا الرمز ليس على مستوى التطبيق ، ولكن في مكان ما إما في مكتبة خارجية أو في نواة الخاص بك.
التحقق
بالإضافة إلى التصفية والإسقاط ، يمكنك إجراء التحقق.
توصلت مكتبة JS
TComb.validation إلى هذه الفكرة. TComb هو اختصار لـ Combinators من النوع ويستند إلى نظام النوع وما إلى ذلك.
التحسينات والتحسينات.
أولاً ، يتم الإعلان عن البدائيات المقابلة لجميع أنواع JS ، ونوع nill إضافي يقابل إما غير محدد أو صفر.
ثم يبدأ المرح. يمكن تعزيز كل نوع مع المسند. إذا كنا نريد أرقامًا أكبر من الصفر ، فإننا نعلن عن المسند x> = 0 ونقوم بالتحقق فيما يتعلق بالنوع موجب. لذلك من اللبنات ، يمكنك جمع أي من عمليات التحقق الخاصة بك. لاحظنا ، على الأرجح ، أن هناك أيضًا عبارات لامدا.
تم قبول المكالمة. نأخذ نفس التحسين ، ونكتبه في C # ، ونكتب طريقة IsValid () ، ونجمع وننفذ التعبير أيضًا. الآن لدينا الفرصة لإجراء التحقق. public class RefinementAttribute: ValidationAttribute { public IValidator<object> Refinement { get; } public RefinementAttribute(Type refinmentType) { Refinement = (IValidator<object>) Activator.CreateInstance(refinmentType); } public override bool IsValid(object value) => Refinement.Validate(value).IsValid(); }
نحن نتكامل مع نظام DataAnnotations القياسي في ASP.NET MVC حتى يعمل كل شيء خارج الصندوق. نعلن RefinementAttribute () ، نمرر النوع إلى المُنشئ. الحقيقة هي أن RefinementAttribute عامة ، لذا يجب عليك استخدام نوع مثل هذا لأنه لا يمكنك التصريح عن سمة عامة في .NET ، للأسف.
لذا قم بتمييز فئة المستخدم بإعادة التمويل. AdultRefinement ، هذا العمر أكبر من 18 عامًا.
لكي تكون جيدًا تمامًا ، دعنا نجعل التحقق على العميل والخادم هو نفسه. يقترح أنصار NoJS كتابة الدعم والمقدمة على JS. حسنًا ، سأكتب في الأمام والخلف في C # ، لا بأس وأقوم فقط بنقلها إلى JS. يمكن لجافا سكريبت كتابة JSX و ES6 وترجمته إلى JavaScript. لماذا لا نستطيع؟ نكتب الزائر ، ونذهب من خلال أي عامل مطلوب وكتابة جافا سكريبت.
حالة التحقق المتكرر المنفصلة هي التعبيرات المنتظمة ، ويجب أيضًا تفكيكها. إذا كان لديك regexp ، خذ StringBuilder ، وقم ببناء regexp. لقد استخدمت هنا نقطتي تعجب ، لأن JS هي لغة مكتوبة ديناميكيًا ، وسيتم إلقاء هذا التعبير على نحو صاخب دائمًا ، بحيث يكون كل شيء على ما يرام مع النوع. دعونا نرى كيف يبدو. { predicate: “x=> (x >= 18)”, errorMessage: “For adults only» }
هنا هو التكرير الخاص بنا ، الذي يأتي من الواجهة الخلفية ، وهو مسند خط ، كما هو الحال في JS لا توجد لامدا و errorMessage "للبالغين فقط". دعنا نحاول ملء النموذج. لا يمر. نحن ننظر كيف تصنع.هذا رد فعل ، نطلب من الواجهة الخلفية من طريقة UserRefinment () التعبير و errorMessage ، قم بإنشاء صقل نسبة إلى الرقم ، استخدم Eval للحصول على lambda. إذا أعدت إجراء ذلك وأزلت قيود النوع ، فاستبدله بالرقم المعتاد ، وستسقط عملية التحقق على JS. أدخل الوحدة ، أرسل. لا أعرف ما إذا كانت مرئية أم لا ، يتم عرض خطأ هنا.
الرمز هو تنبيه. عندما نرسل onSubmit ، نبه ما جاء من الخلفية. والواجهة الخلفية هي مثل هذا الرمز البسيط.
نعود ببساطة Ok (ModelState.IsValid) ، فئة المستخدم التي نحصل عليها من النموذج في JavaScript. هذه هي سمة التحسين. using … namespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } }
أي أن التحقق من الصحة يعمل أيضًا على الواجهة الخلفية ، التي يتم الإعلان عنها في هذا اللمدا. ونترجمها إلى JavaScript. اتضح أننا نكتب تعبيرات لامدا في C # ، ويتم تنفيذ الكود هناك وهناك. جوابنا هو NoJS ، يمكننا القيام بذلك أيضًا.الاختبار
عادةً ما يكون Timlids هم الأكثر اهتمامًا بعدد الأخطاء في الشفرة ، والذين يكتبون اختبارات الوحدة يعرفون مكتبة Moq. هل تريد كتابة نموذج صوري أو الإعلان عن فئة ما - هناك موك ، لديها بنية بطلاقة. يمكنك أن ترسم كيف تريد أن يتصرف وأن يسلم طلبه للاختبار.هذه lambdas في موك هي تعبير أيضًا ، وليس مندوبين. يمر عبر أشجار التعبير ، ويطبق منطقه ، ثم يتغذى في Castle.DynamicProxy. ويخلق الطبقات اللازمة في وقت التشغيل. لكن يمكننا القيام بذلك أيضًا.
سألني صديق مؤخرًا عما إذا كان هناك شيء مثل WCF في قلبنا. أجبت أن هناك WebAPI. أراد في WebAPI ، كما هو الحال في WCF على WSDL لبناء وكيل. هناك فقط اختيال في WebAPI. لكن التبجح هو مجرد نص ، ولا يريد أحد الأصدقاء أن يشاهد في كل مرة عندما تتغير واجهة برمجة التطبيقات وما ينكسر. عندما كان هناك WCF ، قام بتمكين WSDL ، إذا تغيرت المواصفات في واجهة برمجة التطبيقات ، تعطل التجميع.هذا أمر منطقي ، لأنه لا يرغب في البحث ، ويمكن أن يساعد المترجم. عن طريق القياس مع moq ، يمكنك أن تعلن عن أسلوب GetResponse <> () العام مع ProductController الخاص بك ، ويتم تحديد المعلمة التي تدخل في هذه الطريقة بواسطة وحدة التحكم. أي عندما تبدأ في كتابة لامدا ، اضغط على Ctrl + Space وشاهد جميع الطرق التي تمتلكها وحدة التحكم هذه ، شريطة أن تكون هناك مكتبة ، dll مع التعليمات البرمجية. هناك Intellisense ، اكتب كل هذا كما لو كنت تتصل بوحدة التحكم.علاوة على ذلك ، مثل Moq ، لن نسميها ، ولكن ببساطة نقوم ببناء شجرة تعبيرات ، ونذهب من خلالها ، وسحب جميع معلومات التوجيه من تكوين API. وبدلاً من القيام بشيء ما مع وحدة التحكم ، التي لا يمكننا تنفيذها ، حيث يجب علينا تنفيذها على الخادم ، نقوم فقط بتقديم طلب POST أو GET الذي نحتاجه ، وفي الاتجاه المعاكس نقوم بإلغاء تسلم الطلب المستلم ، لأنه من شجرة التحسس والتعبير التي نعرفها عن جميع أنواع الإرجاع. اتضح أننا نكتب الكود حول وحدات التحكم ، ولكن في الحقيقة نقوم بتقديم طلبات الويب.تحسين الانعكاس إنكل شيء يتعلق بالبرمجة الوصفية له الكثير من القواسم المشتركة مع Reflection.
نحن نعلم أن التأمل بطيء ، وأود أن أتجنب ذلك. هنا أيضًا ، هناك حالات جيدة للعمل مع Expression. الأول هو منشئ CreateInstance. يجب ألا تستخدمه مطلقًا ، لأن هناك Expression.New () ، والذي يمكنك ببساطة دفعه إلى لامدا ، وتجميعه ، ثم الحصول على المنشئين.
لقد استعرت هذه الشريحة من متحدث رائع وموسيقي فاجيف. كان يفعل نوعا من قياس الأداء على بلوق. ها هو المنشط ، هذا هو ذروة الشيوعية ، ترى كم يحاول أن يفعل كل شيء. Constructor_Invoke ، إنها تقريبًا نصف الجودة. وعلى اليسار لامدا جديدة ومجمعة. هناك زيادة طفيفة في الأداء بسبب حقيقة أن هذا مندوب ، وليس منشئًا ، لكن الاختيار واضح ، فمن الواضح أن هذا أفضل بكثير.
يمكن القيام بنفس الشيء مع getters أو المستوطنين.
يتم ذلك ببساطة شديدة. إذا كنت غير راضٍ لسبب ما عن Fast Memember أو Mark Gravelli أو Fast Reflect ، إذا كنت لا ترغب في سحب هذه التبعية ، فيمكنك القيام بنفس الشيء. الصعوبة الوحيدة هي أنك تحتاج إلى مراقبة كل هذه المجموعات وتخزينها وتدفئتها في مكان ما. أي إذا كان هناك الكثير من هذا ، فمن الضروري في البداية تجميعه مرة واحدة.
نظرًا لوجود مُنشئ ، وجرس ، ومستوطنون ، فلا يوجد سوى السلوك والأساليب. ولكن يمكن أيضًا تجميعها في مندوبين ، وستحصل على حديقة حيوانات كبيرة للمفوض ستحتاج إلى إدارتها. بمعرفة كل ما تحدثت عنه ، قد يأتي إلى فكرة شخص ما أنه إذا كان هناك الكثير من المندوبين ، والكثير من التعبيرات ، فقد يكون هناك مكان لما يسمى DSL أو Little Languages أو نمط مترجم ، monad مجاني.هذه كلها نفس الأشياء عندما نصل لمجموعة من الأوامر لبعض المهام ونكتب له مترجمنا الخاص الذي يقوم بذلك. أي داخل التطبيق نكتب مترجمًا أو مترجمًا آخر يعرف كيفية استخدام هذه الأوامر. هذا هو بالضبط ما يتم في DLR ، في الجزء الذي يعمل مع لغات IronPython و IronRuby. يتم استخدام شجرة التعبير هناك لتنفيذ التعليمات البرمجية الديناميكية في CLR. يمكن فعل الشيء نفسه في تطبيقات الأعمال ، ولكن حتى الآن لم نلاحظ مثل هذه الحاجة ولا يزال هذا خارج الأقواس.الملخص
في الختام ، أود أن أتحدث عن الاستنتاجات التي توصلنا إليها بعد التنفيذ والاختبار. كما قلت ، حدث هذا في مشاريع مختلفة. كل شيء كتبته ، لا نستخدمه في كل مكان ، ولكن في مكان ما إذا لزم الأمر ، تم استخدام بعض الأشياء.زائد الأول هو القدرة على أتمتة الروتين. إذا كان لديك 100 ألف قالب بالفلترة وترقيم الصفحات وكل ذلك. كان لدى موزارت نكتة باستخدام النرد ووقت كاف وكأس من النبيذ الأحمر ، يمكنك كتابة الفالس بأي كمية. هنا بمساعدة Expression Trees ، القليل من البرمجة الفوقية ، يمكنك كتابة النماذج بأي كمية.يتم تقليل كمية التعليمات البرمجية بشكل كبير ، كبديل لتوليد التعليمات البرمجية ، إذا لم تعجبك ، نظرًا لأنك تحصل على الكثير من التعليمات البرمجية ، لا يمكنك كتابتها ، اترك كل شيء في وقت التشغيل.باستخدام مثل هذا الرمز للمهام البسيطة ، نقوم أيضًا بتقليل متطلبات المؤدين ، نظرًا لوجود القليل من التعليمات البرمجية الحتمية ولا يوجد مجال للخطأ أيضًا. بعد سحب كمية كبيرة من التعليمات البرمجية إلى مكونات قابلة لإعادة الاستخدام ، نقوم بإزالة هذه الفئة من الأخطاء.من ناحية أخرى ، نقوم بزيادة متطلبات تأهيل المصمم كثيرًا ، لأن الأسئلة تأتي من المعرفة حول العمل مع Expression و Reflection وتحسينها ، حول الأماكن التي يمكنك فيها إطلاق النار على نفسك في القدم. هناك العديد من هذه الفروق الدقيقة ، لذلك لن يفهم شخص غير مألوف لواجهة برمجة التطبيقات هذه على الفور سبب عدم دمج Expression. يجب أن يكون المصمم أكثر برودة.في بعض الحالات ، من خلال Expression.Compile () ، يمكنك التقاط تدهور الأداء. في مثال التخزين المؤقت ، كان لدي قيود على أن التعبيرات ثابتة لأن القاموس يستخدم للتخزين المؤقت. إذا كان شخص ما لا يعرف كيف يتم ترتيب ذلك داخليًا ، فإنه يبدأ في القيام بذلك دون تفكير ، ويعلن أن المواصفات غير ثابتة في الداخل ، ولن تعمل طريقة التخزين المؤقت ، وسوف نتلقى مكالمات إلى Compile () في أماكن عشوائية. بالضبط ما أردت تجنبه.أكثر ما هو غير سار هو أن الرمز يتوقف عن الظهور وكأنه رمز C # ، ويصبح أقل اصطلاحيًا ، ومكالمات ثابتة ، وتظهر طرق Where () الإضافية الغريبة ، يتم تحميل بعض العوامل الضمنية بشكل زائد. هذا ليس في وثائق MSDN في الأمثلة. على سبيل المثال ، إذا جاء إليك شخص لديه خبرة قليلة ، ولم يعتاد الدخول إلى شفرة المصدر في الحدث ، فمن المرجح أن يكون في سجود صغير لأول مرة ، لأن هذا لا يتناسب مع صورة العالم ، ولا توجد مثل هذه الأمثلة على StackOverflow ، ولكن مع هذا سوف تضطر إلى العمل بطريقة أو بأخرى.بشكل عام ، هذا كل ما أردت التحدث عنه اليوم. الكثير من ما قلته ، بمزيد من التفصيل ، مع التفاصيل مكتوب على حبري. يتم نشر رمز المكتبة على جيثب ، ولكن لديه عيب واحد قاتل - النقص الكامل في التوثيق.22-23 DotNext 2018 Moscow . , ( ).