بارگذاری داده ها در Entity Framework

بازیابی داده ها جنبه مهمی از طراحی برنامه های کاربردی کارآمد در توسعه نرم افزار است. معمول است که برنامه ها برای عملکرد به داده هایی از پایگاه داده نیاز دارند. دسترسی به داده ها یک عامل عملکرد حیاتی است که باید در این زمینه به دقت مورد توجه قرار گیرد. سه روش متداول بارگیری داده ها از پایگاه داده عبارتند از بارگذاری تنبل، مشتاق و صریح. در این مقاله به تفاوت های این روش ها و همچنین مزایا و معایب آن ها خواهیم پرداخت.
بارگذاری تنبل
بارگذاری تنبل تکنیکی است که به ما امکان می دهد بارگذاری اشیاء مرتبط را تا زمان دسترسی به آنها به تعویق بیندازیم. به عبارت دیگر، ما فقط زمانی که به آن نیاز داریم، داده های مورد نیاز را بارگذاری می کنیم، نه اینکه همه چیز را به یکباره بارگیری کنیم.
بیایید برای درک بهتر بارگذاری تنبل مثالی را در نظر بگیریم. فرض کنید ما دو موجودیت داریم، Author و Book، و ما می خواهیم همه نویسندگان و کتاب های مربوط به آنها را بازیابی کنیم. با بارگذاری تنبل، ابتدا همه نویسندگان را از پایگاه داده بازیابی می کنیم و سپس کتاب ها را برای هر نویسنده فقط در صورت نیاز بازیابی می کنیم. این رویکرد سودمند است زیرا تعداد پرس و جوهای ارسال شده به پایگاه داده را کاهش می دهد و از بارگذاری داده های غیر ضروری جلوگیری می کند.
پیاده سازی (توسط بسته پروکسی)
ما می خواهیم ببینیم که چگونه بارگذاری تنبل را در EF Core پیاده سازی کنیم:
روش اول با نصب یک بسته پروکسی ارائه شده توسط مایکروسافت است. تنها کاری که باید انجام دهید این است که نصب کنید Microsoft.EntityFrameworkCore.Proxies بسته ای که تمام پراکسی های مورد نیاز برای اجرای Lazy Loading و فعال کردن آن را با فراخوانی اضافه می کند UseLazyLoadingProxies.
این را می توان در هر دو مورد به دست آورد OnConfiguring متد کلاس Context شما:
using Microsoft.EntityFrameworkCore;
namespace DataLoading;
internal class MyContext : DbContext
{
protected override void OnConfiguring
(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies()
.UseInMemoryDatabase(databaseName: "MyDb");
}
}
یا هنگام استفاده از AddDbContext به احتمال زیاد در Program.cs:
builder.Services.AddDbContext<MyContext>(
b => b.UseLazyLoadingProxies()
.UseInMemoryDatabase("MyDb"));
هر ویژگی ناوبری که میتواند نادیده گرفته شود و مجازی است و روی کلاسی است که میتواند از آن به ارث برده شود، بارگذاری تنبلی توسط EF Core فعال میشود. برای مثال، ویژگیهای پیمایش Author.Books و Book.Author در موجودیتهای زیر بارگذاری میشوند.
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Book
{
public int Id { get; set; }
public int AuthorId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public virtual Author Author { get; set; }
}
در مثال بالا، از کلمه کلیدی مجازی برای علامت گذاری استفاده می کنیم Books ویژگی به عنوان مجازی، نشان می دهد که باید به صورت تنبل بارگذاری شود. وقتی نویسندگان را از پایگاه داده بازیابی می کنیم، Entity Framework کوئری های SQL را برای بازیابی فقط نویسندگان و نه کتاب ها ایجاد می کند. وقتی به Book دارایی برای یک خاص Author، Entity Framework یک جستجوی SQL دیگر برای بازیابی کتابهای مربوطه ایجاد می کند.
پیاده سازی (توسط سرویس ILazyLoader)
روش دوم از طریق تزریق است ILazyLoader خدمات به یک نهاد
بیایید به یک مثال نگاه کنیم:
public class Author
{
private ICollection<Book> _books_;
public Author()
{
}
private Author(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books
{
get => LazyLoader.Load(this, ref _books);
set => _books = value;
}
}
public class Book
{
private Author _author;
public Book()
{
}
private Book(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
public int AuthorId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public virtual Author Author
{
get => LazyLoader.Load(this, ref _author);
set => _author = value;
}
}
این تکنیک نمونه های موجودیت تولید شده با را قادر می سازد new زمانی که به یک زمینه متصل می شوند و نیازی به ارث بردن از انواع موجودیت یا مجازی بودن ویژگی های ناوبری ندارند، بارگذاری تنبلی را انجام دهند.
در حال حاضر، هنگامی که شما برای یک پرس و جو Author entity ، EF یک پرس و جوی SQL ایجاد می کند که به شکل زیر است:
SELECT [Id], [Name]
FROM [Authors]
WHERE [Id] = @id
این پرس و جو فقط انتخاب می کند Id و Name ستون ها از Authors جدول، زیرا EF می داند که نیازی به بارگذاری ندارد Books مجموعه هنوز
سپس، هنگامی که به Books اموال در Author موجودیت، EF پرس و جوی SQL دیگری را برای بارگیری موارد مرتبط ایجاد می کند Book موجودیت ها:
SELECT [Id], [Name], [Price], [AuthorId]
FROM [Books]
WHERE [AuthorId] = @authorId
به طور مشابه، هنگامی که شما برای a Book موجودیت، EF یک پرس و جوی SQL ایجاد می کند که به شکل زیر است:
SELECT [Id], [Name], [Price], [AuthorId]
FROM [Books]
WHERE [Id] = @id
این پرس و جو فقط انتخاب می کند Id، Name، Price، و AuthorId ستون ها از Books جدول، همانطور که EF می داند که نیازی به بارگیری موارد مرتبط ندارد Author موجودیت هنوز
سپس، هنگامی که به Author اموال در Book موجودیت، EF پرس و جوی SQL دیگری را برای بارگیری موارد مرتبط ایجاد می کند Author وجود، موجودیت:
SELECT [Id], [Name]
FROM [Authors]
WHERE [Id] = @authorId
این پرس و جو را انتخاب می کند Id و Name ستون ها از Authors جدول، که تمام چیزی است که برای پر کردن آن لازم است Author اموال در Book وجود، موجودیت.
مشتاق بارگیری
نقطه مقابل بارگذاری تنبل، بارگذاری مشتاق است. هنگامی که داده ها را از پایگاه داده با استفاده از بارگذاری مشتاق بازیابی می کنیم، شی اصلی و همچنین تمام اشیاء مرتبط را بارگذاری می کنیم. وقتی مطمئن هستیم که تمام داده های مربوطه را می خواهیم و می خواهیم تعداد پرس و جوهای پایگاه داده را کاهش دهیم، این استراتژی سودمند است.
برای درک بیشتر بارگذاری مشتاقانه، اجازه دهید به یک مثال نگاه کنیم. بیایید بگوییم که دو موجودیت مشابه داریم، Author و Book، و ما می خواهیم از بارگذاری مشتاق برای دریافت لیستی از همه نویسندگان و کتاب های آنها استفاده کنیم.
پیاده سازی
در اینجا مثالی از نحوه اجرای بارگذاری مشتاق با استفاده از Entity Framework در سی شارپ آورده شده است:
// To load authors with eager loading
using (var context = new MyContext())
{
var authors = context.Authors.Include(author => author.Books).ToList();
foreach (var author in authors)
{
Console.WriteLine(authors.Name);
foreach (var book in author.Books)
{
Console.WriteLine(book.Name);
}
}
}
این Include روش در مثال بالا برای بیان تمایل به بارگذاری مشتاقانه استفاده شده است Booksدارایی برای هر کدام Author. این Authors و Books جداول توسط Entity Framework به یکدیگر متصل می شوند تا یک پرس و جوی SQL ارائه کنند که تمام اطلاعات لازم را برمی گرداند.
SELECT [a].[Id], [a].[Name], [b].[Id], [b].[Name], [b].[Price], [b].[AuthorId]
FROM [Authors] AS [a]
LEFT JOIN [Books] AS [b] ON [a].[Id] = [b].[AuthorId]
WHERE [a].[Id] = @authorId
شایان ذکر است که شما می توانید سطوح مختلف داده های مرتبط را با استفاده از ThenInclude روش. مثلا:
using (var context = new MyContext())
{
var authors = context.Authors
.Include(author => author.Books)
.ThenInclude(book => book.CoverImage)
.Include(author => author.ContactDetails)
.ToList();
}
بهعلاوه، میتوانیم با استفاده از عبارت، پیکربندی کنیم تا همیشه یک پیمایش را در یک مدل لحاظ کنیم AutoInclude روش در modelBuilder.
modelBuilder.Entity<Author>().Navigation(author => author.Books).AutoInclude();
استفاده کنید IgnoreAutoIncludes اگر نمیخواهید دادههای مرتبط را از طریق پیمایشی که در سطح مدل تعریف شده است، بارگیری کنید تا بهطور خودکار گنجانده شود. در صورت استفاده از این روش، پیمایشهای شامل خودکار پیکربندی شده توسط کاربر بارگیری متوقف میشوند. استفاده از عبارت زیر همه نویسندگان را از پایگاه داده بازیابی می کند، اما Books بارگیری نمی شود حتی اگر روی آن تنظیم شده باشد AutoInclude.
using (var context = new MyContext())
{
var authors = context.Authors.IgnoreAutoIncludes().ToList();
}
وقتی از بارگیری مشتاقانه در ناوبری مجموعه استفاده می کنیم، باید بسیار محتاط باشیم، زیرا ممکن است باعث مشکلات جدی عملکرد شود.
بارگذاری صریح
پس از بارگذاری شی اصلی، بارگذاری صریح به شما امکان میدهد تا در صورت نیاز موجودیتهای مرتبط را بارگیری کنید. با محدود کردن مقدار دادههایی که باید از پایگاه داده وارد شوند، مکانیسمی برای بارگذاری انتخابی موجودیتهای مربوطه تنها در صورت نیاز ارائه میدهد که میتواند کارایی را افزایش دهد.
در بارگذاری صریح، شما از Load روش بر روی الف CollectionEntry یا ReferenceEntry برای بارگذاری صریح موجودیت های مرتبط با آن مخالفت کنید. آ CollectionEntry شی یک ویژگی ناوبری مجموعه را در یک موجودیت نشان می دهد، در حالی که a ReferenceEntry شی یک ویژگی ناوبری مرجع در یک موجودیت را نشان می دهد.
هنگامی که نمی خواهید هر بار که موجودیت اصلی بارگیری می شود، بارگذاری موجودیت های مرتبط را متحمل شوید، بارگیری صریح می تواند مفید باشد. به عنوان مثال، اگر تعداد زیادی موجودیت دارید، اما فقط گاهی نیاز به دسترسی به موجودیتهای مرتبط دارید، میتوانید از بارگذاری صریح برای بارگیری فقط موجودیتهای مرتبطی که در حال حاضر نیاز دارید استفاده کنید.
بارگذاری تنبل باید فعال شود تا بارگذاری واضح کار کند زیرا به آن بستگی دارد. صدا زدن Load روی یک CollectionEntry یا ReferenceEntry اگر بارگذاری تنبل فعال نباشد، شی اثری ندارد.
پیاده سازی
بیایید یک مثال و کوئری SQL تولید شده توسط EF Core را ببینیم:
var author = dbContext.Authors.Find(authorId);
dbContext.Entry(author).Collection(a => a.Books).Load();
با فرض اینکه بارگذاری تنبل فعال است، پرس و جو اولیه برای بازیابی Author موجودیت چیزی شبیه به این خواهد بود:
SELECT [a].[Id], [a].[Name]
FROM [Authors] AS [a]
WHERE [a].[Id] = @authorId
چه زمانی Load نامیده می شود CollectionEntry شی برای Books مجموعه، Entity Framework یک پرس و جو جداگانه برای بازیابی موارد مرتبط ایجاد می کند Book موجودیت ها:
SELECT [b].[Id], [b].[Name], [b].[Price], [b].[AuthorId]
FROM [Books] AS [b]
WHERE [b].[AuthorId] = @authorId
نتیجه
در نتیجه، بارگذاری تنبل زمانی که برای اولین بار از آنها بازدید می شود، به طور خودکار بارگذاری می شود و آن را به روشی کاربردی و ساده تبدیل می کند. با این حال، اگر تعداد زیادی نهاد مرتبط به طور همزمان بارگیری شوند، ممکن است باعث مشکلات عملکرد شود.
در مقابل، بارگذاری مشتاق، موجودیت های مرتبط را علاوه بر موجودیت اصلی در یک پرس و جو بارگیری می کند، که ممکن است در شرایطی که می دانید از قبل به نهادهای مرتبط نیاز دارید، مؤثرتر از بارگذاری تنبل باشد. با این حال، می تواند منجر به پرس و جوهایی شود که پیچیده تر هستند و ممکن است انجام آنها بیشتر طول بکشد.
در شرایطی که فقط برای زیرمجموعهای از موجودیتها نیاز به دسترسی به موجودیتهای مرتبط دارید، بارگذاری صریح یک تکنیک انتخابی است که پس از بارگیری موجودیت اولیه، موجودیتهای مرتبط را در صورت نیاز بارگذاری میکند. از طرف دیگر، استفاده از آن می تواند کمتر ساده باشد و برای پیاده سازی کد بیشتری نیاز دارد. استراتژی بهینه در نهایت به نیازهای خاص برنامه شما و معاوضه بین سادگی، عملکرد و پیچیدگی بستگی دارد.



