مقدمه
در درسهای گذشته، با مفاهیم پایهای EF Core مانند تعریف DbContext، ایجاد مدلها و
انجام عملیات پایهای CRUD (ایجاد، خواندن، بهروزرسانی، حذف) آشنا شدیم. دیدیم که EF Core از LINQ برای ترجمهی کوئریهای C# به SQL استفاده
میکند. این ویژگی، یکی از قدرتمندترین جنبههای EF Core است، زیرا به ما اجازه میدهد
تا با استفاده از یک زبان واحد و امن از نظر نوع (type-safe)، کوئریهای پیچیدهای را بر روی پایگاه
داده اجرا کنیم.
در این درس، به بررسی عمیقتر نحوهی تعامل LINQ و EF Core میپردازیم.
خواهیم دید که چگونه میتوانیم کوئریهای پیچیدهتری شامل join، گروهبندی و محاسبات تجمعی را با
استفاده از LINQ بنویسیم و EF Core چگونه آنها را به SQL بهینه
ترجمه میکند.
مدل دادهی توسعهیافته
برای نمایش کوئریهای پیچیدهتر، مدل دادهی خود را کمی گسترش میدهیم. فرض کنید هر محصول به یک
"دسته" (Category) تعلق دارد. ابتدا کلاس Category را اضافه میکنیم:
Category.cs
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; } = new();
}
سپس، کلاس Product را برای ایجاد رابطهی یک-به-چند با Category بهروزرسانی میکنیم:
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
در نهایت، DbSet مربوط به دستهبندیها را به MyDbContext اضافه کرده و با اجرای دستورات
add-migration و update-database، پایگاه داده را بهروزرسانی میکنیم.
کوئریهای شامل Join
یکی از رایجترین کارها در کار با پایگاههای دادهی رابطهای، اتصال (join) جداول به یکدیگر است.
در EF Core، این کار به صورت بسیار طبیعی و با استفاده از پراپرتیهای ناوبری
(navigation properties) در کوئری LINQ انجام میشود.
فرض کنید میخواهیم نام محصول و نام دستهبندی آن را برای تمام محصولات به دست آوریم.
Program.cs
using (var db = new MyDbContext())
{
var productWithCategory = db.Products
.Select(p => new
{
ProductName = p.Name,
CategoryName = p.Category.Name
})
.ToList();
foreach (var item in productWithCategory)
{
Console.WriteLine($"{item.ProductName} is in category '{item.CategoryName}'");
}
}
EF Core به اندازهی کافی هوشمند است که با دیدن p.Category.Name، بفهمد که شما نیاز
به اتصال جدول Products به جدول Categories بر اساس کلید خارجی دارید و یک کوئری SQL
بهینه با INNER JOIN تولید میکند.
گروهبندی و محاسبات تجمعی
ما میتوانیم از عملگر GroupBy برای گروهبندی نتایج و سپس از عملگرهای تجمعی مانند Count,
Sum, Average, Max, Min برای انجام محاسبات بر روی هر گروه استفاده کنیم.
برای مثال، بیایید تعداد محصولات در هر دستهبندی را محاسبه کنیم.
Program.cs
using (var db = new MyDbContext())
{
var categoryCounts = db.Products
.GroupBy(p => p.Category.Name)
.Select(g => new
{
Category = g.Key,
ProductCount = g.Count()
})
.ToList();
Console.WriteLine("\n--- Product Count per Category ---");
foreach (var item in categoryCounts)
{
Console.WriteLine($"{item.Category}: {item.ProductCount}");
}
}
EF Core این کوئری پیچیده را به یک کوئری SQL کارآمد که از GROUP BY و
COUNT(*) استفاده میکند، ترجمه خواهد کرد. این قابلیت به ما اجازه میدهد تا گزارشهای آماری
پیچیدهای را تنها با چند خط کد C# بنویسیم.
محدودیتهای ترجمه LINQ
هرچند مترجم LINQ در EF Core بسیار قدرتمند است، اما نمیتواند
تمام متدهای .NET را به SQL ترجمه کند. شما فقط میتوانید
از متدهایی در کوئری LINQ خود استفاده کنید که EF Core یک معادل
SQL برای آنها میشناسد. برای مثال، شما نمیتوانید یک متد سفارشی که خودتان نوشتهاید
را مستقیماً در عبارت Where یا Select فراخوانی کنید، زیرا پایگاه داده هیچ درکی از آن متد
ندارد.
اگر یک عملیات پیچیده دارید که در SQL قابل پیادهسازی نیست، باید ابتدا دادههای لازم
را از پایگاه داده واکشی کرده (مثلاً با ToList()) و سپس عملیات خود را بر روی آن کالکشن در حافظه
و با استفاده از LINQ to Objects انجام دهید.