بارگذاری داده ها در 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
نتیجه
در نتیجه، بارگذاری تنبل زمانی که برای اولین بار از آنها بازدید می شود، به طور خودکار بارگذاری می شود و آن را به روشی کاربردی و ساده تبدیل می کند. با این حال، اگر تعداد زیادی نهاد مرتبط به طور همزمان بارگیری شوند، ممکن است باعث مشکلات عملکرد شود.
در مقابل، بارگذاری مشتاق، موجودیت های مرتبط را علاوه بر موجودیت اصلی در یک پرس و جو بارگیری می کند، که ممکن است در شرایطی که می دانید از قبل به نهادهای مرتبط نیاز دارید، مؤثرتر از بارگذاری تنبل باشد. با این حال، می تواند منجر به پرس و جوهایی شود که پیچیده تر هستند و ممکن است انجام آنها بیشتر طول بکشد.
در شرایطی که فقط برای زیرمجموعهای از موجودیتها نیاز به دسترسی به موجودیتهای مرتبط دارید، بارگذاری صریح یک تکنیک انتخابی است که پس از بارگیری موجودیت اولیه، موجودیتهای مرتبط را در صورت نیاز بارگذاری میکند. از طرف دیگر، استفاده از آن می تواند کمتر ساده باشد و برای پیاده سازی کد بیشتری نیاز دارد. استراتژی بهینه در نهایت به نیازهای خاص برنامه شما و معاوضه بین سادگی، عملکرد و پیچیدگی بستگی دارد.