- המדריך רלוונטי למפתחי NET.
כחלק משחרור הגרסה של Entity Framework 5 קיבלנו פיצ’ר חדש שמעניק יכולת לפצל שאילתא אחת למספר שאילתות SQL וכתוצאה מכך לשפר ביצועים.
בפוסט הקרוב אראה את ברירת המחדל בה עבדתי עד היום, איך נראה הקוד לאחר השינוי שהפיצ’ר מביא ומה הוא בעצם נותן לנו בחיים.
נגדיר תחילה Entity / יישות שמייצגת חנות וכמובן את הטבלאות במערכת:
public class Store
{
public long Id { get; set; }
public string Name { get; set; }
public List Employees { get; set; }
public List Products { get; set; }
public List Hours { get; set; }
public List Closures { get; set; }
public List Vendors { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public List Quantities { get; set; }
}
public class Quantity
{
public long Id { get; set; }
public long Amount { get; set; }
public Date OrderDate { get; set; }
}
שאילתת ברירת המחדל שהיינו כותבים כדי למשוך את כל החנויות וכל המוצרים שהן מכילות תיראה כך:
var store = await dbContext.Stores
.Include(s => s.Products)
.ThenInclude(q => q.Quantity)
.ToListAsync(s => s.Id == storeId);
בפועל ספריית ה- Entity Framework מתרגמת את השאילתא לSQL שנראה כך:
SELECT s.*, p.*, d.*
FROM Stores s
LEFT JOIN Products p ON p.storeId = s.Id
LEFT JOIN Quantity q ON q.productId = p.Id
WHERE s.Id = @storeId
ORDER BY s.Id, p.Id, d.Id;
ברוב המקרים, השאילתא תביא את מה שביקשנו, וזה יתבצע ללא כל בעיה.
עם שחרורו של EF Core 5.0 קיבלנו תכונה חדשה בשם Query Splitting. זה מאפשר לנו לציין שאנו רוצים שהשאילתת LINQ הנתונה תהיה מפוצלת למספר שאילתות SQL.
כך ייראה הקוד לאחר השינוי:
var store = await dbContext.Stores
.Include(s => s.Products)
.ThenInclude(q => q.Quantity)
.AsSplitQuery()
.ToListAsync(s => s.Id == storeId);
במקרה הזה, EF יפיק מאחורי הקלעים את שאילתות SQL הבאות:
SELECT s.*
FROM Stores s
WHERE s.Id = @storeId;
SELECT p.*
FROM Products p
JOIN Stores s ON p.StoreId = s.Id
WHERE o.Id = @orderId;
SELECT d.*
FROM Quantity q
JOIN Products p ON q.ProductId = p.Id
JOIN Stores s ON p.StoreId = s.Id
WHERE s.Id = @storeId;
שימו לב שעבור כל שימוש של Include, יש שאילתת SQL נפרדת. היתרון הגדול כאן הוא שאנחנו לא משכפלים נתונים בעת שליפה ממסד הנתונים, כפי שנעשה במקרה הקודם.
יש דרך להוסיף קונפיגורציה בתשתית שתגרום לכל השאילתות באופן אוטומטי להיות מפוצלות, זה ייראה כך:
services.AddDbContext(options =>
options.UseSqlServer(
"CONNECTION_STRING",
o => o.UseQuerySplittingBehavior(
QuerySplittingBehavior.SplitQuery)));
זה יגרום לכל השאילתות ש-EF מייצר להיות שאילתות מפוצלות.
בגישה הזו אם נרצה שאחת השאילתות בכל זאת תרוץ כשאילתה בודדת, עליך לקרוא לשיטת AsSingleQuery:
var store = await dbContext.Stores
.Include(s => s.Products)
.ThenInclude(q => q.Quantity)
.AsSingleQuery()
.ToListAsync(s => s.Id == storeId);
הערה חשובה שצריך להכיר לפני שמסיימים:
קחו בחשבון שיש מקרים מסויימים כמו שאילתא משולבת paging או שאילתות מסובכות אחרות בהן הפיצול יגרום לקוד להישבר, בגלל העובדה הזו אני פחות ממליץ לעשות שינוי רוחבי בתשתית עבור כל הפונקציות במידה ומדובר בקוד ותיק.
במידה ומדובר בקוד אפליקציה או סרביס חדש ללא ספק הייתי מוסיף את זה עבור כולם ובמקרים ספציפים מציין שאני מעוניין בהרצה בודדות כפי שראינו בדוגמא הקודמת.