كيفية استخدام Left Join بشكل أنيق مع SQL المجمّع في LINQ

عند العمل مع قواعد البيانات، يجد المطورون أنفسهم غالبًا في حاجة إلى إجراء استعلامات معقدة تتطلب معالجة البيانات واسترجاعها بشكل فعال. إحدى المهام الشائعة هي استخدام LEFT JOIN في استعلامات SQL بالاقتران مع الدوال المجمعة. كيف يمكننا تحويل مثل هذه الاستعلامات SQL إلى تعبير أنيق في LINQ بلغة C#؟ في هذا المنشور، سوف نستعرض مثالًا على SQL ثم نغوص في معادله LINQ، مع تحليل العملية لزيادة الوضوح.

فهم استعلام SQL

لنلقي نظرة على عبارة SQL التي نريد ترجمتها:

SELECT
   u.id,
   u.name,
   isnull(MAX(h.dateCol), '1900-01-01') dateColWithDefault
FROM universe u
LEFT JOIN history h 
   ON u.id = h.id 
   AND h.dateCol < GETDATE() - 1
GROUP BY u.Id, u.name

تحليل استعلام SQL

  • اختيار الأعمدة: يستخلص الاستعلام معرف المستخدم (u.id)، اسم المستخدم (u.name)، وأقصى تاريخ من سجلات التاريخ التي تتعلق بكل مستخدم (MAX(h.dateCol))، والذي يتم تعيينه إلى ‘1900-01-01’ في حال عدم وجود سجلات تاريخ.
  • الانضمام إلى الجداول: يتم استخدام LEFT JOIN لدمج البيانات من جدول universe (u) وجدول history (h)، مع شرط يقوم بتصفية سجلات history حيث يكون dateCol أقدم من يوم واحد.
  • التجميع: يتم تجميع النتائج حسب معرف المستخدم واسم المستخدم، مما يضمن ظهور كل مستخدم مرة واحدة فقط في النتيجة.

ترجمة SQL إلى LINQ

للحصول على نفس نتيجة استعلام SQL باستخدام LINQ، يمكننا هيكلة استعلامنا على النحو التالي:

DateTime yesterday = DateTime.Now.Date.AddDays(-1);

var collection = 
    from u in db.Universe
    select new
    {
        u.id,
        u.name,
        MaxDate = (DateTime?)
        (
            from h in db.History
            where u.Id == h.Id
            && h.dateCol < yesterday
            select h.dateCol
        ).Max()
    };

تفسير استعلام LINQ

  1. إعلان المتغيرات: نقوم بإنشاء متغير من نوع DateTime يسمى yesterday، والذي يمثل التاريخ المحدد ليوم واحد قبل التاريخ الحالي. هذا يعكس منطق SQL الذي يقارن dateCol مع التاريخ الحالي ناقص واحد.

  2. هيكلة الاستعلام:

    • فقرة From: نبدأ باختيار المستخدمين من جدول Universe.
    • Select New: يقوم هذا بإنشاء كائن مجهول لكل مستخدم يتضمن معرفه، واسم، وأقصى تاريخ من جدول History.
  3. الاستعلام الفرعي للتاريخ الأقصى:

    • نبدأ استعلامًا فرعيًا لاختيار dateCol من جدول History حيث تتطابق المعرفات و dateCol أقل من يوم أمس.
    • بعد ذلك، نحسب التاريخ الأقصى باستخدام الطريقة .Max().

ملاحظات إضافية

  • معالجة القيم الفارغة: نظرًا لأن التاريخ الأقصى قد يكون فارغًا (لا توجد سجلات تاريخ)، نقوم بتحويله إلى DateTime? للسماح بالقيم القابلة للإفراغ.
  • اختلافات في المخرجات: على الرغم من أن نهج LINQ لا ينتج بالضبط نفس SQL، إلا أنه ينتج منطقياً نفس النتائج، مما يوضح أن ترجمة SQL المعقدة إلى LINQ يمكن أن تكون دقيقة.

الخاتمة

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

مع الممارسة، ستصبح معالجة الاستعلامات المعقدة في LINQ طبيعة ثانية لك، مما يوفر لك وسيلة فعالة لضمان أن يكون استرجاع البيانات لديك فعالًا قدر الإمكان.