مقدمه

در درس‌های گذشته، زیرساخت Entity Framework Core را تنظیم کرده، مدل‌های خود را تعریف و پایگاه داده‌ی متناظر با آن‌ها را ایجاد کردیم. اکنون به یکی از جذاب‌ترین بخش‌های کار با EF Core می‌رسیم: کوئری زدن به داده‌ها با استفاده از LINQ.
EF Core به ما اجازه می‌دهد تا با استفاده از همان سینتکس آشنای LINQ که برای کار با کالکشن‌ها یاد گرفتیم، کوئری‌های خود را به زبان C# بنویسیم. سپس، EF Core این کوئری‌های LINQ را به کد SQL بهینه ترجمه کرده، آن را بر روی پایگاه داده اجرا می‌کند و نتایج را به صورت اشیاء C# به ما برمی‌گرداند. این فرآیند به ما اجازه می‌دهد تا بدون نوشتن حتی یک خط کد SQL، با پایگاه داده‌ی خود به صورت کاملاً شیءگرا تعامل داشته باشیم.

DbContext و DbSet به عنوان نقطه‌ی شروع

همانطور که قبلاً دیدیم، نقطه‌ی شروع ما برای هرگونه تعامل با پایگاه داده، یک نمونه از کلاس DbContext سفارشی ماست. پراپرتی‌های DbSet<T> که در این کلاس تعریف کرده‌ایم، نماینده‌ی جداول پایگاه داده هستند و به عنوان منبع اصلی برای کوئری‌های LINQ عمل می‌کنند.

بیایید ابتدا چند داده‌ی نمونه را به جدول Products خود اضافه کنیم تا بتوانیم روی آن‌ها کوئری بزنیم.

Copy Icon Program.cs
using (var db = new MyDbContext())
{
    // Add some sample data if the table is empty.
    if (!db.Products.Any())
    {
        db.Products.Add(new Product { Name = "Laptop", Price = 1200m, Stock = 10 });
        db.Products.Add(new Product { Name = "Mouse", Price = 25m, Stock = 50 });
        db.Products.Add(new Product { Name = "Keyboard", Price = 75m, Stock = 30 });
        db.SaveChanges(); // Save the changes to the database.
    }
}

در این کد، ما ابتدا با استفاده از db.Products.Any() بررسی می‌کنیم که آیا جدولی خالی است یا خیر. اگر خالی بود، چند محصول جدید به DbSet اضافه کرده و در نهایت، با فراخوانی متد SaveChanges() این تغییرات را در پایگاه داده ذخیره می‌کنیم.

خواندن و فیلتر کردن داده‌ها

اکنون که داده داریم، می‌توانیم با استفاده از LINQ آن‌ها را بخوانیم.

Copy Icon Program.cs
using (var db = new MyDbContext())
{
    // Query for all products with a price greater than 100.
    var expensiveProducts = db.Products
        .Where(p => p.Price > 100m)
        .ToList(); // Execute the query and get a List.
    
    Console.WriteLine("--- Expensive Products ---");
    foreach (var product in expensiveProducts)
    {
        Console.WriteLine($"{product.Name} costs {product.Price:C}");
    }
}

این کد بسیار شبیه به کار با یک کالکشن عادی در حافظه است، اما یک اتفاق بسیار مهم در پشت صحنه می‌افتد: عبارت db.Products.Where(p => p.Price > 100m) به کد SQL زیر (یا مشابه آن) ترجمه می‌شود:

SELECT [Id], [Name], [Price], [Stock]
FROM [Products]
WHERE [Price] > 100

این کوئری SQL بر روی سرور پایگاه داده اجرا می‌شود و تنها نتایجی که با شرط مطابقت دارند، به برنامه‌ی ما برگردانده می‌شوند. این فرآیند بسیار بهینه است، زیرا ما تمام داده‌ها را به کلاینت منتقل نکرده و فیلترینگ را در سمت سرور انجام می‌دهیم.

نکته مهم: مانند LINQ to Objects، کوئری‌های EF Core نیز دارای اجرای تأخیری (Deferred Execution) هستند. کوئری تنها زمانی به SQL ترجمه و اجرا می‌شود که شما شروع به پیمایش نتایج آن کنید (مثلاً با یک حلقه‌ی foreach) یا با فراخوانی متدهایی مانند ToList()، ToArray() یا FirstOrDefault() صراحتاً درخواست اجرای آن را بدهید.

ترکیب عملگرهای LINQ

ما می‌توانیم تمام عملگرهای استاندارد LINQ را با هم زنجیره کنیم تا کوئری‌های پیچیده‌تری بسازیم.

Copy Icon Program.cs
using (var db = new MyDbContext())
{
    // Find the names of products with stock less than 40, ordered by name.
    var lowStockProductNames = db.Products
        .Where(p => p.Stock < 40)
        .OrderBy(p => p.Name)
        .Select(p => p.Name) // Projecting to a collection of strings.
        .ToList();

    Console.WriteLine("\n--- Low Stock Products (Names Only) ---");
    foreach (var name in lowStockProductNames)
    {
        Console.WriteLine(name);
    }
}

در این مثال، EF Core کل این زنجیره را به یک کوئری SQL واحد و بهینه ترجمه می‌کند که فیلتر کردن، مرتب‌سازی و انتخاب ستون Name را مستقیماً بر روی سرور پایگاه داده انجام می‌دهد. این کارایی بالایی را تضمین می‌کند، زیرا تنها داده‌های مورد نیاز (یک لیست از نام‌ها) از طریق شبکه به برنامه‌ی ما منتقل می‌شوند.