کوئری‌ های کامپایل‌ شده در Entity Framework Core

سه شنبه 1 خرداد 1397

Entity Framework Core 2.0 کوئری‌های کامپایل‌شده را به طور صریح معرفی می‌کند. آن‌ها کوئری‌های LINQ هستند که از قبل کامپایل می‌شوند تا به محض اینکه برنامه درخواست داده‌ها را می‌دهد برای اجرا خوانده شوند. این مقاله نحوه کار کوئری‌های کامپایل‌شده و استفاده از آن‌ها را شرح می‌دهد.

کوئری‌ های کامپایل‌ شده در Entity Framework Core

چگونه کوئری‌ها اجرا می‌شوند؟

فرض کنید کلاس کانتکس دیتابیسی با متدهایی برای برگرداندن category با id داریم. در واقع این متدها category را به صورت eager load اجرا می‌کنند و در صورت نیاز ما می‌توانیم مفهوم eager loading مربوط به category را در جایی تغییر دهیم.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
     {
     }

     public DbSet<Category> Categories { get; set; }

     // ...

     public IList<Category> GetCategory(Guid id)
     {
         return Categories.Include(c => c.Translations)
                          .ThenInclude(c => c.Language)
                          .Include(c => c.Parent)
                          .Where(c => c.Id == id)
                          .FirstOrDefault();
     }

     // ...
}

جریان گام به گام آنچه متد بالا انجام می‌دهد:

1. ساخت کوئری LINQ برای به دست آوردن category با id مشخص

2. کامپایل کوئری

3. اجرای کوئری

4. تحقق نتایج

5. بازگشت category اگر پیدا شود یا null باشد

فرض کنید سایتی با دسته‌بندی‌های محصول داریم و بسیاری از درخواست‌ها باید از دسته (category) فعلی بارگیری شوند، آن وقت کوئری بالا در سایت ما بسیار محبوب است.

کامپایل شدن کوئری از قبل

بیایید ببینیم با این متد چه کاری می‌توانیم انجام دهیم. در سطح متد نمی‌توانیم کوئری که اجرا می‌شود و نتایجی که برگردانده می‌شود را زیاد بهینه کنیم اما می‌توانیم برخی چیزها را با استفاده از کوئری کامپایل‌شده ذخیره کنیم. همچنین به عنوان یک کوئری کامپایل‌شده شناخته می‌شود. ما کوئری کامپایل‌شده را به عنوان Funcای که می‌توانیم بعدا فراخوانی کنیم تعریف می‌کنیم.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    private static Func<ApplicationDbContext, Guid, IQueryable<Category>> _getCategory =
            EF.CompileQuery((ApplicationDbContext context, Guid id) =>
                context.Categories.Include(c => c.Translations)
                                  .ThenInclude(c => c.Language)
                                  .Include(c => c.Parent)
                                  .Where(c => c.Id == id)
                                  .FirstOrDefault());
 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }
 
    public DbSet<Category> Categories { get; set; }
 
    // ...
 
    public Category GetCategory(Guid id)
    {
        return _getCategory(this, id);
    }
 
    // ...
}

حالا ما کوئری را برای بارگذاری categoryها توسط idای که یک بار ساخته شده است و فراخوانی متد GetCategory() که لازم نیست کوئری را مجددا کامپایل کند، داریم. توجه داشته باشید که کامپایل کوئری در محدوده استاتیک رخ می‌دهد و به این معناست که کوئری که یک بار ساخته شده است توسط تمام نمونه‌های کلاس ApplicationDbContext استفاده می‌شود. هیچ موضوع threading بین درخواست‌های هر فراخوانی وجود ندارد، getCategory func نمونه‌ای از کانتکس دیتابیسی که ما با فراخوانی متد ارائه کردیم را مورد استفاده قرار می‌دهد.

نکته: نسخه فعلی Entity Framework Core (2.0.1) بازگشت آرایه‌ها و لیست‌ها را از کوئری‌هایی که به طور صریح کامپایل‌شده‌اند پشتیبانی نمی‌کند. همچنین IEnumerable<T> و IQueryable<T> کار نمی‌کنند و وقتی Func فراخوانی می‌شود خطا می‌دهند.

کوئری‌های کامپایل‌شده غیرهمزمان

برای ایجاد سرورهایی که از پردازنده‌های مؤثرتری استفاده کنند می‌توانیم کوئری‌های Entity Framework را به صورت غیرهمزمان اجرا کنیم. این کار برنامه ما را سریع‌تر نمی‌کند. در واقع یک میلیمتر سربار را اضافه می‌کند اما بهره‌وری بهتری را به دست می‌آوریم. اما بیایید محدودیت‌ها و نوشتن رونوشت‌های غیرهمزمان برای کوئری categoryمان را قرار دهیم.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    private static Func<ApplicationDbContext, Guid, Task<Category>> _getCategory =
            EF.CompileAsyncQuery((ApplicationDbContext context, Guid id) =>
                context.Categories.Include(c => c.Translations)
                        .ThenInclude(c => c.Language)
                        .Include(c => c.Parent)
                        .Where(c => c.Id == id)
                        .FirstOrDefault());
 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }
 
    public DbSet<Category> Categories { get; set; }
 
    // ...
 
    public async Task<Category> GetCategoryAsync(Guid id)
    {
        return await _getCategory(this, id);
    }
 
    // ...
}

این روش غیرهمزمان است، به طوری که چیزها در حال حاضر می‌توانند باشند. FirstOrDefaultAsync()، ToListAsync() و فراخوانی‌های غیرهمزمان دیگر که Entity Framework در حال حاضر ارائه می‌دهد، در کوئری‌های کامپایل‌شده پشتیبانی نمی‌شوند.

نتیجه‌گیری

پشتیبانی کوئری ‌های کامپایل‌شده ویژگی‌ای است که جزء قابلیت‌های با دسترسی‌ بالا در Entity Framework 2.0 طبقه‌بندی شده است. آن‌ها به ما کمک می‌کنند تا با یک بار کامپایل کردن کوئری‌ها و استفاده از کوئری‌های کامپایل‌شده در بعد، وقتی که برای داده‌های ساخته شده فراخوانی می‌شوند، کارایی برنامه را بالا ببریم. محدودیت‌هایی وجود دارند و هنوز همه چیز امکان‌پذیر نیست اما زمان شروع بررسی‌ این ویژگی‌ها و تلاش برای اعمال آن به محبوب‌ترین کوئری‌ها در برنامه وب است. امیدواریم که در آینده‌ای نزدیک شاهد پشتیبانی از متدهای LINQای که نتایج چندگانه را برمی‌گردانند باشیم.

ایمان مدائنی

نویسنده 1299 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید