102
0

הכירו את הפונקציה AsSplitQuery שתשפר לכם את הביצועים ב-Entity Framework

102
זמן קריאה: 3 דקות
  • המדריך רלוונטי למפתחי NET. 

כחלק משחרור הגרסה של Entity Framework 5 קיבלנו פיצ’ר חדש שמעניק יכולת לפצל שאילתא אחת למספר שאילתות SQL וכתוצאה מכך לשפר ביצועים.

בפוסט הקרוב אראה את ברירת המחדל בה עבדתי עד היום, איך נראה הקוד לאחר השינוי שהפיצ’ר מביא ומה הוא בעצם נותן לנו בחיים.

נגדיר תחילה Entity / יישות שמייצגת חנות וכמובן את הטבלאות במערכת:

				
					public class Store
{
    public long Id { get; set; }
    public string Name { get; set; }

    public List<Employee> Employees { get; set; }
    public List<Product> Products { get; set; }
    public List<StoreHour> Hours { get; set; }
    public List<StoreClosures> Closures { get; set; }
    public List<Vendor> Vendors { get; set; }
}

public class Product
{
    public long Id { get; set; }
    public string Name { get; set; }
    public List<Quantity> 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<ApplicationDbContext>(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 או שאילתות מסובכות אחרות בהן הפיצול יגרום לקוד להישבר, בגלל העובדה הזו אני פחות ממליץ לעשות שינוי רוחבי בתשתית עבור כל הפונקציות במידה ומדובר בקוד ותיק.

במידה ומדובר בקוד אפליקציה או סרביס חדש ללא ספק הייתי מוסיף את זה עבור כולם ובמקרים ספציפים מציין שאני מעוניין בהרצה בודדות כפי שראינו בדוגמא הקודמת.

אמיר שטיימן
WRITEN BY

אמיר שטיימן

Backend Engineer @Cynet
ביום יום מפתח Backend בסביבת SaaS, מיקרו סרביסים בשילוב של מערכות מבוזרות. בזמני הפנוי - לומד ומשתדרג בעולם התוכנה, אוהד מכבי חיפה, פלייסטיישן ובירה עם חברים :)
Linkedin | Twitter

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *