DCSIMG
Entity Framework Lazy Loading (בעברית - טעינה עצלנית של מסגרת ישויות) - Ido Flatow's Blog Veni Vidi Scripsi

Ido Flatow's Blog

Veni Vidi Scripsi

News

Have you heard me speak?
Powered
<style type='text/css' media='screen' id='sm_css'> #smix {overflow: visible;height: auto;border-radius: 10px;max-width: 250px;background-color: #323232;text-align: left;font-size: 12px;line-height: 16px;font-family:'Lucida Sans Unicode','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;-webkit-border-radius: 10px;-moz-border-radius: 10px;border-radius: 10px;} #smix a {color: #0056CC;text-decoration: none;} #smix .sm_head {color: #fff; line-height: 1em;font-size: 1.4em;padding: 10px;color: #fff;} #smix .sm_lanyard_wrapper {background-color: #fff;;clear: both;width: 97%;margin: 0 auto;margin-bottom: 0px;} #smix .sm_lanyard_content {padding: 7px;}#smix button.sm_rec, #smix a.sm_rec, #smix input[type=submit].sm_rec { padding: 6px 10px; -webkit-border-radius: 2px 2px;-moz-border-radius: 2px; border-radius: 2px; border: solid 1px rgb(153, 153, 153); background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 255, 255)), to(rgb(221, 221, 221))); color: #333; text-decoration: none; cursor: pointer; display: inline-block; text-align: center; text-shadow: 0px 1px 1px rgba(255,255,255,1); line-height: 1; }#smix .sm_rec:hover { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(248, 248, 248)), to(rgb(221, 221, 221))); }#smix .sm_rec:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(204, 204, 204)), to(rgb(221, 221, 221))); }#smix .sm_rec.medium { padding: 3px 7px; font-size: 13px; }#smix .sm_rec span.icon.thumbs_up {background-position: 0px 36px;vertical-align: text-top;display: inline-block;margin-right: 4px;height: 18px;width: 16px;background-image: url(http://speakermix.com/images/new/thumbsold.png);}#smix .sm_rec:hover span.icon.thumbs_up {background-position: 0px 18px;} #smix .sm_events {padding:2px 0px 4px 0px;} #smix .sm_section {font-size: 10px; border-bottom: 1px solid silver; margin-bottom: 6px;} #smix .sm_subline {font-size:120%;margin-top:4px;font-weight:bold} #smix .powered {text-align: right} #smix .powered img {margin: 7px} </style>
Sela Technology Center

Advertisement

Entity Framework Lazy Loading (בעברית - טעינה עצלנית של מסגרת ישויות)

כשהחלטתי בזמנו לכתוב את הפוסטים שלי בעברית, שיערתי שיגיע היום שבו לא אוכל לתרגם מושגים לעברית כך שזה ישמע טוב - היום הזה הגיע, ולכן אאלץ להשתמש במונחים לועזיים.

כשתיכננו את הארכיטקטורה של EF, הוחלט שטעינת הישויות תהיה תוך כדי ריצה (JIT) ע"י שימוש בטכניקת Lazy Load - רק כאשר פונים לאוסף ישוית מבוצעת פניה ל-DB עבור טעינתם.

יש איזשהו הגיון מאחורי התכנון הזה - הרצון לחסוך פעולות מיותרות ב-DB. עם זאת, ישנן כמה חסרונות לשיטה זו:

  1. על-מנת לבצע את הטעינה, יש להפעיל את מתודת Load של ה-EntityCollection או ה-EntityReference באופן יזום (Explicit), מה שאומר שצריך לזכור לשים פקודת Load לפני כל פניה ל-Navigation Property (אחרת עלולים לקבל NullReferenceException או לא פחות גרוע מזה - שגיאה בלוגיקה של התוכנית)
  2. הפעלה עוקבת של Load על אותו Navigation Property תגרום לפנייה חוזרת ל-DB, על כן צריך לשים לב טוב טוב בזמן Code Review אם המפתחים שלכם זכרו לעטוף כל פקודה כזו בבדיקה של IsLoaded (מאפיין שיש לכל EntityCollection ו-EntityReference).
  3. במידה ובנינו קוד שמבצע איטרציה על אוסף ישויות ומבצע פעולות על כל אחת, והישויות עליהן עוברים מכילות Navigation Properties, יש לבצע Load לפני כל פניה למאפיין, מה שאומר שכמות הפניות ל-DB תהיה מספר המאפיינים הנבדקים בישות X מספר הישויות - לא סימפטי מבחינת יעילות

 

הסעיף הראשון די מעצבן, בגלל שתי השורות שצריכות לחזור על עצמן לפני כל פניה ל-Navigation Property (בדיקת IsLoaded וביצוע Load). קיים כיום פתרון של Transparent Lazy Load אבל הוא לא חלק מה-EF עצמו עדין.

לגבי סעיף 3 - קיים פתרון ברמת ה-EF ע"י שימוש במתודה Include שניתן להפעיל על אובייקטי ObjectQuery (כל EntityType שניתן לתשאול). למתודה מעבירים פרמטר מחרוזתי שמציין את שם ה-Navigation Property אליו נרצה לגשת בהמשך, כאשר ניתן לשרשר מספר פקודות Include על-מנת לקבל תוצאת שאילתא המכילה נתונים של כמה ישויות.

מה בעצם קורה מאחורי הקלעים? כאשר מפעילים פעולת Include, השאילתא שמורצת ב-DB מכילה את השליפה של הישות המקורית וכן שליפה עבור כל אחת מישויות הבנים שהוספנו.

לדוגמה, נתון המודל הבא:

image 

ומבנה ה-DB הבא:

 image

בהנתן קוד שנראה כך:

TestModel.TestEntities model = new TestModel.TestEntities();

var all = from a in model.Person.Include("Pets").Include("Address")
          select a;

foreach (var person in all.ToList())
{
    if (!person.Pets.IsLoaded)
        person.Pets.Load();
    if (!person.Address.IsLoaded)
        person.Address.Load();
    
    Console.WriteLine(
        string.Format ("{0} {1}\n{2}\n{3}",
            person.FirstName, 
            person.LastName,
            String.Join("\n", (from ad in person.Address
             select string.Format("{0} {1} {2}", ad.City, ad.Street, ad.House)).ToArray()),
             String.Join("\n", (from p in person.Pets
             select string.Format("{0} {1}", p.Name, p.Species)).ToArray())));
    Console.WriteLine();
}

כאשר תבוצע הפקודה ToList, תורץ השאילתא הבאה:

 

 

SELECT 
[UnionAll1].[Id] AS [C1], 
[UnionAll1].[FirstName] AS [C2], 
[UnionAll1].[LastName] AS [C3], 
[UnionAll1].[C2] AS [C4], 
[UnionAll1].[C1] AS [C5], 
[UnionAll1].[Id1] AS [C6], 
[UnionAll1].[Name] AS [C7], 
[UnionAll1].[Species] AS [C8], 
[UnionAll1].[PersonId] AS [C9], 
[UnionAll1].[C3] AS [C10], 
[UnionAll1].[C4] AS [C11], 
[UnionAll1].[C5] AS [C12], 
[UnionAll1].[C6] AS [C13], 
[UnionAll1].[C7] AS [C14]
FROM  (SELECT 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[LastName] AS [LastName], 
    1 AS [C2], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[Name] AS [Name], 
    [Extent2].[Species] AS [Species], 
    [Extent2].[PersonId] AS [PersonId], 
    CAST(NULL AS int) AS [C3], 
    CAST(NULL AS varchar(1)) AS [C4], 
    CAST(NULL AS varchar(1)) AS [C5], 
    CAST(NULL AS varchar(1)) AS [C6], 
    CAST(NULL AS int) AS [C7]
    FROM  [dbo].[Person] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Pets] AS [Extent2] ON [Extent1].[Id] = [Extent2].[PersonId]
UNION ALL
    SELECT 
    2 AS [C1], 
    [Extent3].[Id] AS [Id], 
    [Extent3].[FirstName] AS [FirstName], 
    [Extent3].[LastName] AS [LastName], 
    1 AS [C2], 
    CAST(NULL AS int) AS [C3], 
    CAST(NULL AS varchar(1)) AS [C4], 
    CAST(NULL AS varchar(1)) AS [C5], 
    CAST(NULL AS int) AS [C6], 
    [Extent4].[Id] AS [Id1], 
    [Extent4].[City] AS [City], 
    [Extent4].[Street] AS [Street], 
    [Extent4].[House] AS [House], 
    [Extent4].[PersonId] AS [PersonId]
    FROM  [dbo].[Person] AS [Extent3]
    INNER JOIN [dbo].[Address] AS [Extent4] ON [Extent3].[Id] = [Extent4].[PersonId]) AS [UnionAll1]

ארוך, לא? אבל עבור מבנה זה של טבלאות השליפה יחסית יעילה.

אזהרה: מומלץ מאוד להזהר בכמות ה-Include-ים שמבצעים ובמספר הרמות שנכנסים פנימה לתוך ה-Navigations - ככל שתנסו באמצעות Include לשלוף יותר Navigation Properties פנימיים של ישויות, כך השאילתא תגדל באופן מפלצתי ותהפוך לפחות יעילה (ניסיתם פעם לעשות Left Join על 4 טבלאות גדולות? לא ממש טוב בביצועים).

 

דבר נוסף שיש לשים לב אליו - הפקודה Include מנסה לזהות את ה-Navigation Property והמיפוי שלה (על-מנת לבנות את שאילתת ה-Union) עוד טרם טעינת אוסף הישויות הראשי, מה שאומר שאם אוסף הישויות הראשי שלכם הוא פולימורפי (נניח אם בנוסף ל-Person היתה מחלקה יורשת נוספת באותו EntitySet) תוכלו לבצע Include רק ל-Navigation Properties של ה-Base Class, כלומר אין לנו אפשרות אמיתית לבצע Eager Load (טעינה Implicit של כל מבנה הישויות).

אם כן תרצו לטעון רשימה פולימורפית, תצטרכו לבצע מספר טעינות, כל פעם עבור עץ ירושה בודד (עם ה-Include-ים עבורו).

אבל לכל פתרון יש את ה-catch - מה קורה אם מחלקת בן כלשהי מכילה Navigation Property שגם הוא ממחלקת Base כלשהי (נניח Person שמכיל מאפיין Cars שהוא רשימה של אוסף פולימורפי של Car וסוגי רכבים שיורשים ממנו)? אתם מבינים לאן זה הולך...

 

אז לסיכום, Lazy Loading קיים אך בעייתי, Include קיים אך בעייתי, Eager Load לא קיים אך לא פחות בעייתי - פשוט צריך למצוא את הפתרון המתאים בהתאם לסיטואציה בה אתם נמצאים.

Comments

ronjt said:

I wish I could read your comments.  Any chance of seeing it in English. Thank you.

# July 22, 2008 1:53 AM

Ido Flatow said:

Hi ronjt,

I didn't quite understand from your comment if you wish that the post will be translated into english or do you wish further comments to be written in english.

# July 22, 2008 12:54 PM

Ido Flatow said:

I&#39;ve received a couple of request to write some of my previous posts in English so that all other

# August 20, 2008 4:13 PM

Entity Framework and Lazy Loading « hirenppatel said:

Pingback from  Entity Framework and Lazy Loading &laquo; hirenppatel

# May 19, 2011 12:13 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: