הפעם אחדד נקודות על הממשקים IEnumerable ו- IQueryable ועל ההבדלים ביניהם.
נתחיל מהפשוט והמוכר יותר, IEnumerable.
ממשק זה הוא בעצם מימוש בשפת C# של ה-Design Pattern הקרוי Iterator, המאפשר לנוע בתוך Collection איבר אחרי איבר.
Collection שמממש את הממשק הזה (לדוגמה List), מאפשר שימוש במשפטי foreach, כאשר אחזור האובייקטים יתבצע בצורה עצלה (Lazy) בצורה איטרטיבית.
אותו foreach יבקש בכל איטרציה איבר אחד בלבד, שיחזור מאותו Collection ע"י משפט Yield (או מימוש IEnumerator על הנתונים ואחזור שלהם).
למה צריך דבר שכזה?
1. לפעמים הפעולה של להחזיר את כל האיברים היא כבדה ונרצה לתת למי שמשתמש בקוד שלנו את האופציה לפעול בצורה Lazy.
//If you will try to do ToList to this collection, it may take along time...
public class Top100PageCrawlerCollection : IEnumerable<object>
{
public IEnumerator<object> GetEnumerator()
{
for(int i=0;i<100;i++)
yield return DownloadPage();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public object DownloadPage()
{
System.Threading.Thread.Sleep(100);
return new Object();
}
}
public class Top100PageCrawlerCollection : IEnumerable<object>
{
public IEnumerator<object> GetEnumerator()
{
for(int i=0;i<100;i++)
yield return DownloadPage();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public object DownloadPage()
{
System.Threading.Thread.Sleep(100);
return new Object();
}
}
// If you will try to do toList to this collection, you will be stuck...
public class PositiveNumbersCollection : IEnumerable<int>
{
private int _innerseed = 0;
public IEnumerator<int> GetEnumerator()
{
while(true)
yield return _innerseed++;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class PositiveNumbersCollection : IEnumerable<int>
{
private int _innerseed = 0;
public IEnumerator<int> GetEnumerator()
{
while(true)
yield return _innerseed++;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
עכשיו נעבור ל-IQueryable: הבסיס מאחורי IQueryable הוא תמיכה בעצי ביטויים (Expression Trees). פעולות על Collection כגון פלטור לא יתבצעו על מאגר הנתונים, אלא ישמרו בטיפוס נתונים של ביטוי ורק ברגע ההרצה תתבצע פעולה הנקראת קמפול - הפיכה של אותו ביטוי לשאילתה הסופית שתרוץ אל מול הנתונים במקום המרוחק.
הדבר פחות רלוונטי כאשר הנתונים נמצאים בזכרון ומאד רלוונטי כאשר הנתונים נמצאים במקום מרוחק, הדוגמה הקלאסית לכך היא מסד נתונים.
מבולבלים? דוגמה:
DBCollection Db = new DBCollection();
var query1 = Db.AsEnumerable().Where(x => x.Age < 50).Run();
// SELECT * FROM Collection
var query2 = Db.AsQueryable().Where(x => x.Age < 50).Run();
// SELECT * FROM Collection WHERE age < 50
בדוגמה הראשונה, התבצעה שאילתה שהביאה את כל הנתונים והסינון התבצע בזיכרון.
בדוגמה השניה, התבצעה שאילתה שדאגה לסינון הנתונים כבר בדאטבייס והנתונים שהגיעו כבר היו מפולטרים.
לא אכנס לענייני דטאבייס כדי להשוות מבחינת ביצועים בין השניים - רק חשוב שתדעו - יכולות להיות השלכות ביצועים משמעותיות בין שתי השאילתות, בהתאם לגודל הטבלה, סיבוכיות הפלטור והארכיטקטורה הכללית של המערכת.
עידן