۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

نحوه بهبود عملکرد دسترسی به داده در EF Core

هنگام استفاده از Entity Framework Core در برنامه های NET مبتنی بر داده، از این 10 استراتژی برای بهبود عملکرد دسترسی به داده ها استفاده کنید.

هنگام استفاده از Entity Framework Core در برنامه های NET مبتنی بر داده، از این ۱۰ استراتژی برای بهبود عملکرد دسترسی به داده ها استفاده کنید.

Entity Framework Core (EF Core) یک چارچوب منبع باز ORM (نقشه نگاشت شی – رابطه) است که شکاف بین مدل شی برنامه شما و مدل داده پایگاه داده شما را پر می کند. EF Core با اجازه دادن به شما برای کار با پایگاه داده با استفاده از اشیاء NET، به جای نوشتن کد دسترسی به داده، زندگی را ساده‌تر می‌کند.

به عبارت دیگر، EF Core به شما امکان می‌دهد تا برای اجرای اقدامات CRUD (ایجاد، خواندن، به‌روزرسانی و حذف) کد بنویسید بدون اینکه بدانید چگونه داده‌ها در پایگاه داده زیربنایی باقی می‌مانند. با کار مستقیم در سی شارپ می‌توانید موجودیت‌ها را راحت‌تر از فروشگاه داده بازیابی کنید، موجودیت‌ها را اضافه کنید، تغییر دهید، حذف کنید، و از نمودارهای موجودیت عبور کنید.

می‌توانید عملکرد دسترسی به داده‌ها را در EF Core به روش‌های مختلف بهبود بخشید، از استفاده از بارگیری مشتاقانه تا کاهش رفت‌وآمدهای پایگاه‌داده مورد نیاز درخواست‌های شما. در این مقاله، ۱۰ نکته و ترفند یا استراتژی را بررسی خواهیم کرد که می‌توانیم در EF Core برای بهبود عملکرد دسترسی به داده‌های برنامه‌های NET Core خود استفاده کنیم.

برای کار با نمونه کدهای ارائه شده در زیر، باید Visual Studio 2022 را در سیستم خود نصب کنید. اگر قبلاً نسخه‌ای ندارید، می‌توانید Visual Studio 2022 را از اینجا بارگیری کنید.

ایجاد یک پروژه برنامه کاربردی کنسول در ویژوال استودیو

ابتدا، اجازه دهید یک پروژه برنامه کاربردی کنسول NET Core در ویژوال استودیو ایجاد کنیم. با فرض اینکه Visual Studio 2022 در سیستم شما نصب شده است، مراحل ذکر شده در زیر را برای ایجاد یک پروژه برنامه کاربردی کنسول NET Core جدید دنبال کنید.

  1. Visual Studio IDE را راه اندازی کنید.
  2. روی “ایجاد پروژه جدید” کلیک کنید.
  3. در پنجره “ایجاد پروژه جدید”، “Console App (.NET Core)” را از لیست الگوهای نمایش داده شده انتخاب کنید.
  4. بعدی را کلیک کنید.
  5. در پنجره “پیکربندی پروژه جدید خود”، نام و مکان پروژه جدید را مشخص کنید.
  6. بعدی را کلیک کنید.
  7. در پنجره “اطلاعات اضافی” نشان داده شده در ادامه، “NET 7.0 (Standard Term Support)” را به عنوان نسخه چارچوبی که می خواهید استفاده کنید انتخاب کنید.
  8. روی ایجاد کلیک کنید.

ما از این پروژه برای کار با EF Core 7 در طول این مقاله استفاده خواهیم کرد. در بخش‌های بعدی، ۱۰ روشی را که می‌توانیم سرعت دسترسی به داده‌ها را در EF Core بهبود ببخشیم، مورد بحث قرار می‌دهیم که هر جا که مناسب باشد با نمونه‌های کد نشان داده شده است. بیایید شروع کنیم!

فقط داده هایی را که نیاز دارید بازیابی کنید

هنگامی که با حجم انبوهی از داده‌ها سروکار دارید، باید سعی کنید فقط سوابق مورد نیاز برای پرس و جوی خاص را بازیابی کنید. هنگام واکشی داده ها، باید از پیش بینی ها استفاده کنید تا فقط فیلدهای مورد نیاز را انتخاب کنید و از بازیابی فیلدهای غیر ضروری خودداری کنید.

قطعه کد زیر نحوه به دست آوردن داده ها را به صورت صفحه بندی شده نشان می دهد. توجه کنید که چگونه از نمایه صفحه ابتدایی و اندازه صفحه برای انتخاب فقط داده های مورد نیاز استفاده شده است.

int pageSize = 50, startingPageIndex = 1;
var dataContext = new OrderProcessingDbContext();
var data = dataContext.Orders.Take(pageSize)
.Skip(startingPageIndex * pageSize)
.ToList();

زمینه داده بزرگ خود را به بسیاری از زمینه های داده کوچکتر تقسیم کنید

زمینه داده در برنامه شما نشان دهنده پایگاه داده شما است. از این رو، ممکن است تعجب کنید که آیا برنامه باید فقط یک یا چند زمینه داده داشته باشد. در Entity Framework Core، زمان راه‌اندازی یک زمینه داده بزرگ، یک محدودیت عملکرد قابل توجه را نشان می‌دهد. در نتیجه، به جای استفاده از یک زمینه داده گسترده، باید زمینه داده را به چندین زمینه داده کوچکتر تقسیم کنید.

در حالت ایده آل، شما باید فقط یک زمینه داده در هر ماژول یا واحد کار داشته باشید. برای استفاده از چندین زمینه داده، به سادگی یک کلاس جدید برای هر زمینه داده ایجاد کنید و آن را از کلاس DbContext گسترش دهید.

از به‌روزرسانی‌های دسته‌ای برای تعداد زیادی نهاد استفاده کنید

رفتار پیش‌فرض EF Core ارسال بیانیه‌های به‌روزرسانی فردی به پایگاه داده زمانی است که دسته‌ای از دستورات به‌روزرسانی برای اجرا وجود دارد. به طور طبیعی، بازدیدهای متعدد به پایگاه داده مستلزم سربار عملکرد قابل توجهی است. برای تغییر این رفتار و بهینه‌سازی به‌روزرسانی‌های دسته‌ای، می‌توانید از روش UpdateRange() همانطور که در قطعه کد زیر نشان داده شده است استفاده کنید.

public class DataContext : DbContext
  {
      public void BatchUpdateAuthors(List<Author> authors)
      {
          var students = this.Authors.Where(a => a.Id >10).ToList();
          this.UpdateRange(authors);
          SaveChanges();
      }
      protected override void OnConfiguring
      (DbContextOptionsBuilder options)
      {
          options.UseInMemoryDatabase("AuthorDb");
      }
      public DbSet<Author> Authors { get; set; }
      public DbSet<Book> Books { get; set; }
  }

اگر از EF Core 7 یا جدیدتر استفاده می‌کنید، می‌توانید از روش‌های ExecuteUpdate و ExecuteDelete برای انجام به‌روزرسانی‌های دسته‌ای و حذف بازدیدهای متعدد پایگاه داده استفاده کنید. به عنوان مثال:

_context.Authors.Where(a => a.Id > 10).ExecuteUpdate();

غیرفعال کردن ردیابی تغییرات برای جستارهای فقط خواندنی

رفتار پیش فرض EF Core ردیابی اشیاء بازیابی شده از پایگاه داده است. زمانی که می‌خواهید یک موجودیت را با داده‌های جدید به‌روزرسانی کنید، ردیابی لازم است، اما زمانی که با مجموعه‌های داده بزرگ سروکار دارید، این عملیات پرهزینه است. بنابراین، می‌توانید با غیرفعال کردن ردیابی در زمانی که موجودیت‌ها را تغییر نمی‌دهید، عملکرد را بهبود ببخشید.

برای جستارهای فقط خواندنی، یعنی زمانی که می‌خواهید موجودیت‌ها را بدون تغییر آن‌ها بازیابی کنید، باید از AsNoTracking برای بهبود عملکرد استفاده کنید. قطعه کد زیر نشان می دهد که چگونه AsNoTracking می تواند برای غیرفعال کردن ردیابی برای یک پرس و جو فردی در EF Core استفاده شود.

var dbModel = await this._context.Authors.AsNoTracking()
    .FirstOrDefaultAsync(e => e.Id == author.Id);

قطعه کد ارائه شده در زیر نشان می دهد که چگونه می توانید نهادها را مستقیماً از پایگاه داده برای اهداف فقط خواندنی، بدون ردیابی و بدون بارگیری آنها در حافظه بازیابی کنید.

public class DataContext : DbContext
{
    public IQueryable<Author> GetAuthors()
    {
        return Set<Author>().AsNoTracking();
    }
}

استفاده از DbContext pooling

یک برنامه معمولا دارای چندین زمینه داده است. از آنجایی که ایجاد و از بین بردن اشیاء DbContext ممکن است پرهزینه باشد، EF Core مکانیزمی برای ادغام آنها ارائه می دهد. با ادغام، اشیاء DbContext یک بار ایجاد می شوند، سپس در صورت نیاز مجددا استفاده می شوند.

استفاده از استخر DbContext در EF Core می‌تواند عملکرد را با کاهش هزینه‌های سربار مربوط به ساخت و دفع اشیاء DbContext بهبود بخشد. در نتیجه ممکن است برنامه شما از حافظه کمتری نیز استفاده کند.

قطعه کد زیر نشان می دهد که چگونه می توانید ادغام DbContext را در فایل Program.cs پیکربندی کنید.

builder.Services.AddDbContextPool<MyDbContext>(options => options.UseSqlServer(connection));

از IQueryable به جای IEnumerable استفاده کنید

هنگامی که در EF Core داده‌ها را درخواست می‌کنید، به جای IEnumerable از IQueryable استفاده کنید. هنگامی که از IQueryable استفاده می کنید، دستورات SQL در سمت سرور، جایی که داده ها ذخیره می شوند، اجرا می شوند، در حالی که IEnumerable به اجرای پرس و جو در سمت مشتری نیاز دارد. علاوه بر این، در حالی که IQueryable از بهینه سازی پرس و جو و بارگذاری تنبل پشتیبانی می کند، IEnumerable این کار را نمی کند. این توضیح می دهد که چرا IQueryable کوئری ها را سریعتر از IEnumerable اجرا می کند.

قطعه کد زیر نشان می دهد که چگونه می توانید از IQueryable برای جستجوی داده ها استفاده کنید.

IQueryable<Author> query = _context.Authors;
query = query.Where(e => e.Id == 5);
query = query.OrderBy(e => e.Id);
List<Author> entities = query.ToList();

از بارگیری مشتاق به جای بارگیری تنبل استفاده کنید

EF Core به طور پیش فرض از بارگیری تنبل استفاده می کند. با بارگذاری تنبل، موجودیت های مرتبط تنها زمانی در حافظه بارگذاری می شوند که به آنها دسترسی داشته باشید. مزیت این است که داده ها بارگذاری نمی شوند مگر اینکه به آنها نیاز باشد. با این حال، بارگذاری تنبل می تواند از نظر کارایی پرهزینه باشد زیرا ممکن است برای بارگیری داده ها به چندین پرس و جو در پایگاه داده نیاز باشد.

برای حل این مشکل برای سناریوهای خاص، می توانید از بارگذاری مشتاق در EF Core استفاده کنید. بارگیری مشتاق، موجودیت‌های شما و موجودیت‌های مرتبط را در یک جستار واکشی می‌کند و تعداد رفت‌وآمدها به پایگاه داده را کاهش می‌دهد. قطعه کد زیر نشان می دهد که چگونه می توان از بارگیری مشتاقانه استفاده کرد.

public class DataContext : DbContext
{
    public List<Author> GetEntitiesWithEagerLoading()
    {
        List<Author> entities = this.Set<Author>()
            .Include(e => e.Books)
            .ToList();
        return entities;
    }
}

غیرفعال کردن بارگیری تنبل

با حذف نیاز به بارگیری موجودیت‌های مرتبط غیرضروری (مانند بارگذاری صریح)، به نظر می‌رسد که بارگذاری تنبل توسعه‌دهنده را از برخورد کامل با نهادهای مرتبط رها می‌کند. از آنجایی که EF Core در بارگیری خودکار موجودیت‌های مرتبط از پایگاه داده در صورت دسترسی به کد شما مهارت دارد، بارگذاری تنبل ویژگی خوبی به نظر می‌رسد.

با این حال، بارگذاری تنبل به ویژه مستعد ایجاد سفرهای رفت و برگشت اضافی غیرضروری است که می تواند برنامه شما را کند کند. می‌توانید با مشخص کردن موارد زیر در زمینه داده‌های خود، بارگیری تنبل را خاموش کنید:

ChangeTracker.LazyLoadingEnabled = false;

استفاده از کد ناهمزمان به جای کد همزمان

برای بهبود عملکرد و پاسخگویی برنامه خود باید از کدهای همگام استفاده کنید. در زیر نمونه کدی را به اشتراک می‌گذارم که نشان می‌دهد چگونه می‌توانید کوئری‌ها را به صورت ناهمزمان در EF Core اجرا کنید. ابتدا دو کلاس مدل زیر را در نظر بگیرید.

public class Author
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<Book> Books { get; set; }
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
}

در قطعه کد زیر، با گسترش کلاس DbContext کتابخانه EF Core، یک کلاس زمینه داده سفارشی ایجاد خواهیم کرد.

public class DataContext : DbContext
{
    protected readonly IConfiguration Configuration;
    public DataContext(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    protected override void OnConfiguring
    (DbContextOptionsBuilder options)
    {
        options.UseInMemoryDatabase("AuthorDb");
    }
    public DbSet<Author> Authors { get; set; }
    public DbSet<Book> Books { get; set; }
}

توجه داشته باشید که ما در اینجا برای سادگی از یک پایگاه داده در حافظه استفاده می کنیم. قطعه کد زیر نشان می دهد که چگونه می توانید از کد async برای به روز رسانی یک موجودیت در پایگاه داده با استفاده از EF Core استفاده کنید.

public async Task<int> Update(Author author)
{
    var dbModel = await this._context.Authors
       .FirstOrDefaultAsync(e => e.Id == author.Id);
       dbModel.Id = author.Id;
       dbModel.FirstName = author.FirstName;
       dbModel.LastName = author.LastName;
       dbModel.Books = author.Books;
       return await this._context.SaveChangesAsync();
}

کاهش رفت و برگشت به پایگاه داده

با اجتناب از مشکل انتخاب N+1 می توانید تعداد رفت و برگشت به پایگاه داده را به میزان قابل توجهی کاهش دهید. مشکل انتخاب N+1 عملکرد پایگاه داده را از روزهای اولیه ORMها تحت تاثیر قرار داده است. این نام به مشکل ارسال پرس‌وجوهای کوچک N+1 به پایگاه داده برای بازیابی داده‌هایی اشاره دارد که می‌توان با یک جستار بزرگ بازیابی کرد.

در EF Core، مشکل N+1 زمانی رخ می‌دهد که می‌خواهید داده‌ها را از دو جدول بارگیری کنید که رابطه یک به چند یا چند به چند دارند. برای مثال، فرض کنید داده‌های نویسنده را از جدول نویسنده‌ها و همچنین داده‌های کتاب را از جدول Books بارگیری می‌کنید. کد زیر را در نظر بگیرید.

foreach (var author in this._context.Authors)
{
    author.Books.ForEach(b => b.Title.ToUpper());
}

توجه داشته باشید که حلقه بیرونی foreach همه نویسندگان را با استفاده از یک جستجو واکشی می کند. این “۱” در جستارهای N+1 شما است. Foreach داخلی که کتاب‌ها را واکشی می‌کند نشان‌دهنده “N” در مسئله N+1 شما است، زیرا foreach داخلی N بار اجرا می‌شود.

برای حل این مشکل، باید داده های مرتبط را از قبل (با استفاده از بارگیری مشتاقانه) به عنوان بخشی از پرس و جو “۱” واکشی کنید. به عبارت دیگر، همانطور که در قطعه کد ارائه شده در زیر نشان داده شده است، باید داده های کتاب را در جستجوی اولیه خود برای داده های نویسنده وارد کنید.

var entitiesQuery = this._context.Authors
    .Include(b => b.Books);
foreach (var entity in entitiesQuery)
{
   entity.Books.ForEach(b => b.Title.ToUpper());
}

با انجام این کار، تعداد سفرهای رفت و برگشت به پایگاه داده را از N+1 به تنها یک کاهش می دهید. این به این دلیل است که با استفاده از Include، بارگذاری مشتاق را فعال می کنیم. پرس و جو بیرونی، یعنی entitiesQuery، فقط یک بار اجرا می شود تا تمام رکوردهای نویسنده همراه با داده های کتاب مرتبط بارگیری شود. به جای انجام رفت و برگشت به پایگاه داده، دو حلقه foreach روی داده های موجود در حافظه کار می کنند.

اتفاقاً، EF Core 7 برخی از سفرهای رفت و برگشت به پایگاه داده را به صورت رایگان کاهش می دهد. مدیریت تراکنش برای بیانیه های درج منفرد از EF Core 7 حذف شد زیرا دیگر ضروری نیست. در نتیجه، EF Core 7 دو رفت و برگشت را که در نسخه های قبلی EF Core برای شروع و انجام تراکنش استفاده می شد حذف می کند. نتیجه این است که EF Core 7 هنگام درج داده ها در پایگاه داده با استفاده از یک دستور درج در مقایسه با نسخه های قبلی، عملکرد قابل توجهی را ارائه می دهد.

عملکرد باید یک ویژگی باشد

در این مقاله ما ۱۰ استراتژی کلیدی را بررسی کردیم که می توانید برای بهبود عملکرد دسترسی به داده ها در EF Core استفاده کنید. علاوه بر این، باید طراحی پایگاه داده، نمایه ها، پرس و جوها و رویه های ذخیره شده خود را به دقت تنظیم کنید تا از حداکثر مزایا بهره مند شوید. عملکرد باید یکی از ویژگی های برنامه شما باشد. هر زمان که در حال ساخت برنامه هایی هستید که از داده های زیادی استفاده می کنند، ضروری است که از همان ابتدا عملکرد را در ذهن داشته باشید.

در نهایت، هر برنامه کاربردی نیازمندی ها و ویژگی های دسترسی به داده های متفاوتی دارد. شما باید عملکرد EF Core خود را قبل و بعد از اعمال هر یک از تغییراتی که در اینجا مورد بحث قرار دادیم برای ارزیابی نتایج برای برنامه خاص خود معیار قرار دهید. یک ابزار عالی برای این کار BenchmarkDotNet است که می‌توانید در پست قبلی من در اینجا بخوانید.