IEnumerable<T> مقابل IQueryable<T>: مبارزة تحدد مصير تطبيقك
\n\nهل تساءلت يومًا لماذا استعلام قاعدة البيانات الخاص بك بطيء، على الرغم من أن البيانات قليلة؟ أو لماذا بعد إضافة سطر واحد من الكود يتحول التطبيق إلى سلحفاة؟ على الأرجح، واجهت المعضلة الكلاسيكية: IEnumerable<T> أم IQueryable<T>؟ هذان ليسا مجرد واجهتين من عالم C# و LINQ. بل هما عالمين مختلفين، نهجين مختلفين للعمل مع البيانات. أحدهما للمجموعات المحلية، والآخر للمصادر البعيدة مثل قواعد البيانات. في هذه المقالة، لن نعدد الاختلافات فحسب، بل سنحللها بأمثلة حقيقية، حتى لا تقع في فخ الأداء مرة أخرى.
\n\nمن هو من: المفاهيم الأساسية
\n\nلنبدأ بالأساسيات. IEnumerable<T> هي واجهة تمثل سلسلة من العناصر في الذاكرة. وهي مثالية للعمل مع المجموعات مثل List<T> و Array و Dictionary. عند استخدام IEnumerable، يتم تنفيذ جميع العمليات (التصفية، الترتيب) على جانب العميل، أي في تطبيقك.
IQueryable<T> هي واجهة أكثر تقدمًا، مصممة للعمل مع مصادر البيانات الخارجية (قواعد البيانات، خدمات الويب). لا تخزن البيانات، بل تبني شجرة تعبيرات (Expression Tree). وهذا يسمح لموفر LINQ (مثل Entity Framework) بتحويل كود C# الخاص بك إلى استعلام SQL فعال وتنفيذه على جانب الخادم.
\n\nالعبارة الرئيسية التي يجب تذكرها: IEnumerable يعمل في الذاكرة، IQueryable يعمل على جانب مصدر البيانات.
\n\nالفرق الرئيسي: أين ومتى يتم تنفيذ المنطق
\n\nتخيل أنك بحاجة إلى تصفية قائمة من 10,000 منتج أغلى من 1000 روبل، وترتيبها حسب السعر.
\n\nمثال 1: IEnumerable (تصفية في الذاكرة)
\n// لنفترض أن products هي List<Product> (IEnumerable)\nvar expensiveProducts = products\n .Where(p => p.Price > 1000)\n .OrderBy(p => p.Price)\n .ToList();\n\nماذا يحدث هنا؟ أولاً، يتم تحميل جميع المنتجات العشرة آلاف إلى الذاكرة. ثم يطبق C# عامل التصفية Where والترتيب OrderBy في تطبيقك. إذا كان لديك 10,000 سجل، فهذا ليس حرجًا. ولكن إذا كان لديك 10 ملايين، فسينهار التطبيق أو يتجمد.
مثال 2: IQueryable (تصفية على خادم قاعدة البيانات)
\n// لنفترض أن dbContext.Products هي DbSet<Product> (IQueryable)\nvar expensiveProducts = dbContext.Products\n .Where(p => p.Price > 1000)\n .OrderBy(p => p.Price)\n .ToList();\n\nهنا السحر: IQueryable لا يقوم بتحميل البيانات فورًا. بدلاً من ذلك، يبني استعلام SQL: SELECT * FROM Products WHERE Price > 1000 ORDER BY Price. يتم إرسال هذا الاستعلام إلى قاعدة البيانات، وتقوم بإرجاع البيانات المصفاة والمرتبة فقط. في الذاكرة، تحصل فقط على ما تحتاجه. توفير هائل في الموارد.
مثال عملي: فخ ToList()
\n\nمن أكثر الأخطاء شيوعًا للمطورين المبتدئين هو الاستدعاء المبكر لـ ToList() أو ToArray(). هذا يحول IQueryable إلى IEnumerable.
كود سيء (يقتل الأداء):
\nvar allProducts = dbContext.Products.ToList(); // تحميل جميع البيانات إلى الذاكرة!\nvar filteredProducts = allProducts.Where(p => p.Price > 1000).ToList();\n\nفي هذا المثال، نقوم أولاً بتحميل جدول Products بأكمله إلى الذاكرة، ثم نقوم بتصفيته. هذا يعادل تفريغ المستودع بأكمله في موقف السيارات، ثم البحث عن الصندوق المناسب في الكومة.
\n\nكود جيد (أمثل):