فرق Tracking و AsNoTracking در Entity Framework (EF) :
در Entity Framework (EF)، دو روش اصلی برای بازیابی دادهها از پایگاه داده وجود دارد: Tracking
و AsNoTracking
. این دو روش تاثیر مهمی بر عملکرد و رفتار پرس و جوها دارند. تفاوتهای کلیدی بین این دو روش عبارتند از:
Tracking (ردیابی)
- عملکرد: وقتی از ردیابی استفاده میکنید، Entity Framework موجودیتهای بازیابی شده از پایگاه داده را در
ChangeTracker
ردیابی میکند. این به این معنی است که هر تغییری که در این موجودیتها انجام شود، توسط EF شناسایی شده و هنگام ذخیره تغییرات (SaveChanges
) به طور خودکار به پایگاه داده اعمال میشود. - مزایا:
- قابلیت پیگیری تغییرات: اگر میخواهید تغییراتی که روی موجودیتها انجام میشود را ردیابی و در نهایت به پایگاه داده اعمال کنید، ردیابی مناسب است.
- همگامسازی خودکار: EF به طور خودکار تغییرات را به پایگاه داده اعمال میکند.
- معایب:
- عملکرد پایینتر: ردیابی موجودیتها نیازمند حافظه و پردازش بیشتری است که میتواند در پرس و جوهای بزرگ یا پیچیده منجر به افت عملکرد شود.
AsNoTracking (بدون ردیابی)
- عملکرد: وقتی از
AsNoTracking
استفاده میکنید، EF موجودیتهای بازیابی شده از پایگاه داده را درChangeTracker
ردیابی نمیکند. این باعث میشود که پرس و جوها سریعتر اجرا شوند و حافظه کمتری مصرف کنند. - مزایا:
- عملکرد بهتر: چون EF موجودیتها را ردیابی نمیکند، پرس و جوها سریعتر اجرا میشوند و منابع کمتری مصرف میشود.
- مناسب برای سناریوهای فقط خواندنی: اگر تنها به خواندن دادهها نیاز دارید و نمیخواهید آنها را به روزرسانی کنید،
AsNoTracking
انتخاب بهتری است.
- معایب:
- عدم پیگیری تغییرات: تغییراتی که روی موجودیتها انجام میشود توسط EF شناسایی نمیشود و باید به صورت دستی مدیریت شوند.
- نیاز به مدیریت دستی: اگر بعداً تصمیم به اعمال تغییرات داشتید، باید موجودیتها را مجدداً به
ChangeTracker
اضافه کنید.
مثال
در اینجا مثالی از استفاده از هر دو روش آورده شده است:
Tracking
using (var context = new MyDbContext())
{
var customers = context.Customers.ToList(); // By default, tracking is enabled
var customer = customers.First();
customer.Name = "Updated Name";
context.SaveChanges(); // Changes will be saved to the database
}
AsNoTracking
csharpCopy codeusing (var context = new MyDbContext())
{
var customers = context.Customers.AsNoTracking().ToList(); // No tracking
var customer = customers.First();
customer.Name = "Updated Name";
// Changes will not be saved to the database automatically
}
نکات نهایی
- استفاده از
AsNoTracking
برای سناریوهای فقط خواندنی (read-only) بسیار مناسب است زیرا عملکرد را بهبود میبخشد. - در صورت نیاز به ردیابی و اعمال تغییرات به پایگاه داده، باید از ردیابی استفاده کنید.
- Entity Framework Core به طور پیشفرض از ردیابی Tracking استفاده میکند مگر اینکه به طور صریح از
AsNoTracking
استفاده کنید.
تفاوت بین IQueryable و IEnumerable :
در اصل متد IQueryable از متد IEnumerable ارث بری میکنه و طبیعتا تمام ویژگی های آن را دارد اما در اصل IQueryable سمت SQL ریکوئستی را ارسال نمیکند و زمانی که دستور ()ToList را بهش میدهیم میره و اطلاعات را برای ما از سمت SQL فراخوانی میکنه به گ.نه ای که میتوان بارها با دستور where کوئری را مفصل تر نمود و در نهایت بعد از زدن ()ToList میره و اطلاعات را از SQL میاره ولی IEnumerable پس از هر بار where زدن می بایست دستور ()ToList را نوشت و هر بار دیتا را برای ما از سمت sql میاره
توضیحات تکمیلی:
متد زیر را که یکی از اشتباهات رایج حین استفاده از LINQ خصوصا جهت Binding اطلاعات است، در نظر بگیرید:
IQueryable<Customer> GetCustomers()
این متد در حقیقت هیچ چیزی را Get نمیکند! نام اصلی آن GetQueryableCustomers و یا GetQueryObjectForCustomers است.
IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی میباشد که مد نظر شما است و نه بیشتر.
IQueryable<Customer> youngCustomers = repo.GetCustomers().Where(m => m.Age < 15)
برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر میکنید نیز هنوز چیزی از بانک اطلاعاتی یا منبع دادهای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع دادهای صورت نگرفته است.
به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شدهی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته میشود (باید دقت داشت که IQueryable هم یک نوع IEnumerable است به علاوه expression trees که مهمترین وجه تمایز آن نیز میباشد.
برای مثال در عبارت زیر تنها در زمانیکه متد ToList فراخوانی میشود، کل عبارت LINQ ساخته شده، به عبارت SQL متناظر با آن ترجمه شده، اطلاعات از دیتابیس اخذ گردیده و حاصل به صورت یک لیست بازگشت داده میشود:
IList<Competitor> competitorRecords = competitorRepository
.Competitors
.Where(m => !m.Deleted)
.OrderBy(m => m.countryId)
.ToList(); //فقط اینجا است که اس کیوال نهایی تولید میشود
در مورد IEnumerable ها چطور؟
IEnumerable<Product> products = repository.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
دو سطر فوق به این معنا است:
لطفا ابتدا به بانک اطلاعاتی رجوع کن و تمام رکوردهای محصولات موجود را بازگشت بده. سپس بر روی این حجم بالای اطلاعات، محصولاتی را که قیمت بالای 25 دارند، فیلتر کن.
اگر همین دو سطر را با IQueryable بازنویسی کنیم چطور؟
IQueryable<Product> products = repository.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
در سطر اول تنها یک عبارت LINQ ساخته شده است و بس. در سطر دوم نیز به همین صورت. در طی این دو سطر حتی یک رفت و برگشت به بانک اطلاعاتی صورت نخواهد گرفت. در ادامه اگر این اطلاعات به نحوی Select شوند (یا ToList فراخوانی شود، یا در طی یک حلقه برای مثال Iteration ایی روی این حاصل صورت گیرد یا موارد مشابه دیگر)، آنگاه کوئری SQL متناظر با عبارت LINQ فوق ساخته شده و بر روی بانک اطلاعاتی اجرا خواهد شد.
بدیهی است این روش منابع کمتری را نسبت به حالتی که تمام اطلاعات ابتدا دریافت شده و سپس فیلتر میشوند، مصرف میکند (حالت بازگشت تمام اطلاعات ممکن است شامل 20000 رکورد باشد، اما حالت دوم شاید فقط 5 رکورد را بازگشت دهد).
سوال: پس IQueryable بسیار عالی است و از این پس کلا از IEnumerable ها دیگر نباید استفاده کرد؟
خیر! توصیه اکید طراحان این است که لطفا تا حد امکان متدهایی که IQueryable بازگشت میدهند ایجاد نکنید! IQueryable یعنی اینکه این نقطهی آغازین کوئری در اختیار شما، بعد برو هر کاری که دوست داشتی با آن در طی لایههای مختلف انجام بده و هر زمانیکه دوست داشتی از آن یک خروجی تهیه کن. خروجی IQueryable به معنای مشخص نبودن زمان اجرای نهایی کوئری و همچنین مبهم بودن نحوهی استفاده از آن است. به همین جهت متدهایی را طراحی کنید که IEnumerable بازگشت میدهند اما در بدنهی آنها به نحو صحیح و مطلوبی از IQueryable استفاده شده است. به این صورت حد و مرز یک متد کاملا مشخص میشود. متدی که واقعا همان فیلتر کردن محصولات را انجام میدهد، همان 5 رکورد را بازگشت خواهد داد؛ اما با استفاده از یک لیست یا یک IEnumerable و نه یک IQueryable که پس از فراخوانی متد نیز به هر نحو دلخواهی قابل تغییر است.
Left Join در Entity Framework با استفاده از LINQ :
برای انجام یک عملیات Left Join در Entity Framework (EF)، میتوانید از روشهای مختلفی استفاده کنید. یکی از رایجترین روشها استفاده از LINQ (Language Integrated Query) است. در ادامه، مثالی از نحوه اجرای یک Left Join در EF با استفاده از LINQ آمده است.
فرض کنید دو جدول Customers
و Orders
داریم و میخواهیم یک Left Join بین این دو جدول انجام دهیم تا تمام مشتریان و سفارشهای آنها (در صورت وجود) را به دست آوریم.
تعریف کلاسهای مدل
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public string ProductName { get; set; }
public Customer Customer { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
}
اجرای Left Join با استفاده از LINQ
در این مثال، یک Left Join بین Customers
و Orders
انجام میدهیم تا تمامی مشتریان و سفارشهای آنها (در صورت وجود) را بازیابی کنیم.
using (var context = new MyDbContext())
{
var query = from customer in context.Customers
join order in context.Orders
on customer.CustomerId equals order.CustomerId into customerOrders
from order in customerOrders.DefaultIfEmpty()
select new
{
CustomerId = customer.CustomerId,
CustomerName = customer.Name,
OrderId = order != null ? order.OrderId : (int?)null,
ProductName = order != null ? order.ProductName : null
};
var result = query.ToList();
foreach (var item in result)
{
Console.WriteLine($"Customer: {item.CustomerName}, Order ID: {item.OrderId}, Product: {item.ProductName}");
}
}
توضیح کد
- تعریف کوئری:
- از عبارت
from
وjoin
برای تعریف جوین بینCustomers
وOrders
استفاده میکنیم. - از
into customerOrders
برای تعریف گروه جوین استفاده میکنیم. - از
from order in customerOrders.DefaultIfEmpty()
برای انجام Left Join استفاده میکنیم.DefaultIfEmpty()
اطمینان حاصل میکند که اگر هیچ سفارشی برای یک مشتری وجود نداشته باشد، مقدارorder
برابر باnull
خواهد بود.
- از عبارت
- انتخاب دادهها:
- در قسمت
select
، اطلاعات مشتری و سفارش (در صورت وجود) را انتخاب میکنیم. - اگر سفارشی وجود نداشته باشد، مقادیر مربوط به سفارش برابر با
null
خواهند بود.
- در قسمت
- اجرا و نمایش نتیجه:
- کوئری با استفاده از
ToList()
اجرا میشود. - نتیجه کوئری چاپ میشود.
- کوئری با استفاده از
این روش سادهترین و رایجترین روش برای انجام Left Join در Entity Framework با استفاده از LINQ است.