Query Splitting چیست؟

👈 در این مقاله میخواهیم به یکی از ویژگیهای قدرتمند در EF Core به نام Query Splitting بپردازیم.


👁 بازدید : 41

1403/5/29 | 23:11 : تاریخ 📆


فرض کنید سه جدول Product وCustomer و Order داریم که اینها با هم رابطه یک به چند دارن.یعنی هر Product میتونه چندین Order داشته باشه و هر Customer میتونه چندین Product و Order داشته باشه. و حالا اگه بخواهیم از کلاس Context تمام رکوردهای Product  و Order رو بگیریم از متد Include در EF استفاده میکنیم و  داریم:

dbContext
    .Orders
    .Include(order => order.Products)
    .ThenInclude(product=> product.Products)
    .First(order => order.Id == orderId);

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

SELECT o.*, pr.*, c.*
FROM Orders o
LEFT JOIN Products pr ON pr.OrderId = o.OrderId
LEFT JOIN Customers c ON c.CustomerId = pr.CustomerId 
WHERE o.OrderId = @orderId
ORDER BY o.Id, pr.Id, c.Id;

در اغلب موارد این کوئری درست کار میکنه، اما در این کیس ما دچار مشکل Cartesian Explosion میشیم و دلیل اون هچ بخاطر join زدن روی جدول  Customers هست.

اما چطور مشکل رو حل کنیم؟

در معرفی EF Core 5 ما شاهد ویژگی جدیدی به نام Query Splitting بودیم به طوری که به ما اجازه میداد قسمت های مختلف یک کوئری چند تیکه رو به بخشهای مختلف تقسیم کنه.

برای استفاده از Query Splitting کافیه متد AsSplitQuery رو بعد از آخرین ThenInclude فراخوانی کنیم

dbContext
    .Orders
    .Include(order => order.Products)
    .ThenInclude(product=> product.Products)
    .AsSplitQuery()
    .First(order => order.Id == orderId);

حالا EF کدهای SQL زیر رو تولید میکنه :

SELECT o.*
FROM Orders o
WHERE o.Id = @orderId;

SELECT pr.*
FROM Products pr
JOIN Orders o ON pr.OrderId = o.OrderId
WHERE o.Id = @orderId;

SELECT c.*
FROM Customers c
JOIN Products pt ON c.CustomerId = pr.CustomerId
JOIN Orders o ON pr.OrderId = o.OrderId
WHERE o.Id = @orderId;

باید توجه کرد که هر متد Include یک کوئری جدا SQl داره.مزیت اینکار در جلوگیری از دریافت دیتاهای تکراریه.

 

فعال کردن Query Splitting برای تمام کوئری ها:

برای اینکار باید بریم سراغ کدهای معرفی سرویس ها

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        "CONNECTION_STRING",
        o => o.UseQuerySplittingBehavior(
            QuerySplittingBehavior.SplitQuery)));

 

نتیجه:

هرچند متد Query Splitting میتونه سرعت کار مارو زیاد کنه اما باید مراقب استفاده بجا و درست از اون باشیم، بعضا ممکنه تقسیم کوئری به نتایج اشتباهی منجرب بشه.مثلا به هیچ عنوان پیشنهاد نمیشه که روی تمام کوئری ها Query Splitting رو فراخوانی کنیم برای Include شاید منطقی بیاد اما برای Add یا Update چطور!؟

فرض کنید سه جدول Product وCustomer و Order داریم که اینها با هم رابطه یک به چند دارن.یعنی هر Product میتونه چندین Order داشته باشه و هر Customer میتونه چندین Product و Order داشته باشه. و حالا اگه بخواهیم از کلاس Context تمام رکوردهای Product  و Order رو بگیریم از متد Include در EF استفاده میکنیم و  داریم:

dbContext
    .Orders
    .Include(order => order.Products)
    .ThenInclude(product=> product.Products)
    .First(order => order.Id == orderId);

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

SELECT o.*, pr.*, c.*
FROM Orders o
LEFT JOIN Products pr ON pr.OrderId = o.OrderId
LEFT JOIN Customers c ON c.CustomerId = pr.CustomerId 
WHERE o.OrderId = @orderId
ORDER BY o.Id, pr.Id, c.Id;

در اغلب موارد این کوئری درست کار میکنه، اما در این کیس ما دچار مشکل Cartesian Explosion میشیم و دلیل اون هچ بخاطر join زدن روی جدول  Customers هست.

اما چطور مشکل رو حل کنیم؟

در معرفی EF Core 5 ما شاهد ویژگی جدیدی به نام Query Splitting بودیم به طوری که به ما اجازه میداد قسمت های مختلف یک کوئری چند تیکه رو به بخشهای مختلف تقسیم کنه.

برای استفاده از Query Splitting کافیه متد AsSplitQuery رو بعد از آخرین ThenInclude فراخوانی کنیم

dbContext
    .Orders
    .Include(order => order.Products)
    .ThenInclude(product=> product.Products)
    .AsSplitQuery()
    .First(order => order.Id == orderId);

حالا EF کدهای SQL زیر رو تولید میکنه :

SELECT o.*
FROM Orders o
WHERE o.Id = @orderId;

SELECT pr.*
FROM Products pr
JOIN Orders o ON pr.OrderId = o.OrderId
WHERE o.Id = @orderId;

SELECT c.*
FROM Customers c
JOIN Products pt ON c.CustomerId = pr.CustomerId
JOIN Orders o ON pr.OrderId = o.OrderId
WHERE o.Id = @orderId;

باید توجه کرد که هر متد Include یک کوئری جدا SQl داره.مزیت اینکار در جلوگیری از دریافت دیتاهای تکراریه.

 

فعال کردن Query Splitting برای تمام کوئری ها:

برای اینکار باید بریم سراغ کدهای معرفی سرویس ها

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        "CONNECTION_STRING",
        o => o.UseQuerySplittingBehavior(
            QuerySplittingBehavior.SplitQuery)));

 

نتیجه:

هرچند متد Query Splitting میتونه سرعت کار مارو زیاد کنه اما باید مراقب استفاده بجا و درست از اون باشیم، بعضا ممکنه تقسیم کوئری به نتایج اشتباهی منجرب بشه.مثلا به هیچ عنوان پیشنهاد نمیشه که روی تمام کوئری ها Query Splitting رو فراخوانی کنیم برای Include شاید منطقی بیاد اما برای Add یا Update چطور!؟

تمامی‌حقوق‌مادی‌ومعنوی‌این‌سایت‌برای‌یادمیگیریم‌محفوظ است.