Linq To Object - חלק ראשון

סגור באמצעות טופס זה תוכלו לספר ולהמליץ לחבריכם..
שם השולח:
כתובת דוא"ל של השולח:
שם המקבל:
שלח לכתובת דוא"ל:
הוסף הערה:
Linq זה כינוי לשפת שאילתות המובנית כחלק אינטדרלי בשפת #C. Linq היא שפה שלא רק שהתחביר שלה מאוד דומה לשפת SQL אלא בעיקר המשמעות שלה. Linq היא שפת שאילתות המאפשרת לבצע שאילתות על אוספים. ב-Linq ניתן להשתמש בשתי דרכים: הראשונה בתחביר ייעודי שהפך להיות חלק מתחביר השפה והשניה היא אוסף מתודות ייעודיות העושות שימוש בביטויי למבדה תוך שימוש ב- Func<T> delegate ומסתירות את התחביר הבסיסי ובכך להפחית את כמות הקוד הנדרש למספר שורות בודדות.

Linq To Object  - חלק ראשון Linq


 


מאת: ארז קלר



הורדת קבצי דוגמההורדת דוגמאות קוד



בסופו של יום, כמפתחים חלק ניכר מזמננו מוקדש לביצוע מניפולציות על מידע.
מידע יכול להגיע ממגוון מקורות: מסדי נתונים, קבצי
XML, קבצים וכו'.
לעיתים קרובות, באפליקציה אחת נדרש מידע המגיע ממספר סוגים של מקורות נתונים, עובדה זו מקשה עלינו המפתחים בשל הצורך ללמוד ולהכיר "שפות" שונות וטכניקות שונות,
הקידוד הנדרש לביצוע פעולה על נתונים המגיעים ממסד נתונים שונה מצורת הקידוד כאשר המידע מגיע מקובץ
XML, ושונה מצורת הכתיבה הנדרשת לביצוע מניפולציה על נתונים הנמצאים באוסף.
בביצוע פעולה על מסד נתונים נשתמש בשפת
SQL, בביצוע פעולה על XML נעזר במחלקות הנמצאות במרחב השמות System.XML , לביצוע פעולה על מידע הנמצא באוספים נכתוב לולאות, ניגש למחלקת הבסיס, ניגש למחלקות הקשורות ביחסי הכלה ביניהן וכו'.
סידור מבנה המידע שונה ממקור נתונים אחד למשנהו:
במסד נתונים המידע מאורגן באוסף טבלאות עם קשרים ביניהן,
בקבצי
XML המידע מאורגן בצורה היררכית,
באוסף אובייקטים המידע מאורגן בישויות המקיימות סוגים שונים של קשרים ביניהן (
Inheritance, Aggregation, Composition, Association).

Linq - ראשי תיבות של 
Language Integrated Query, מספקת
 לנו מודל ותחביר אחידים לטיפול במידע ללא קשר למקור הנתונים ממנו הוא מגיע, היא מאפשרת להגדיר שאילתות כחלק אינטגראלי משפת התכנות בה אנו משתמשים.
Linq זה כינוי לשפת שאילתות המובנית כחלק אינטגרלי משפת #C.
Linq היא שפה שלא רק שהתחביר שלה מאוד דומה לשפת SQL אלא בעיקר המשמעות שלה,
היא שפת שאילתות המאפשרת לבצע שאילתות על אוספים.
ב-Linq ניתן להשתמש בשתי דרכים: הראשונה בתחביר ייעודי שהפך להיות חלק מתחביר השפה והשניה היא אוסף מתודות ייעודיות העושות שימוש בביטויי למבדה תוך שימוש ב- Func<T> delegate ומסתירות את התחביר הבסיסי ובכך להפחית את כמות הקוד הנדרש למספר שורות בודדות.

Linq מאפשרת ביצוע שאילתות חיפוש, סינון, סידור, צירוף, איחוד לקבוצות של נתונים וכו'.

Linq מחולקת למספר תחומים:

Linq to Object – יכולת לתשאל מבני נתונים הנמצאים בזיכרון.
Linq to SQL – ביצוע שאילתות על מידע המגיע מ-SQL.
Linq to Dataset – ביצוע שאילתות על מידע המגיע מ- Datasets או Datatables .
Linq to Entities – בצוע שאילתות על מידע הנמצא בישויות מידע, טכניקה שנתמכת ב- .NET Framework 3.5.
Linq to XML – בצוע שאילתות הנמצאות בקבצי XML.

מאמר זה מתמקד ב- Linq to Object משום שזו הדרך הפשוטה להכיר את Linq .

מיקרוסופט רואה ב-
Linq חשיבות רבה מאוד, חלק מהתכונות שהתווספו  לשפת #C במהלך השנים  כגון: var, Anonymous Type, Object Initialization, Lambda וכו'' נוצרו על מנת לתמוך ב- Linq.

 
ההיכרות עם התכונות הללו של השפה היא חיונית על מנת להשתמש בשפת Linq 

מבנה תחבירי בסיסי

 
תחביר:

from num in arr
                     where (num % 2) == 0
select num;
var result = 


1 – שאילתה חייבת להתחיל במילה השמורה from.
2 –
num, או כל שם חוקי אחר של משתנה ב-C#, הוא משתנה המייצג כל אחד מהאלמנטים המוגדרים במקור המידע (arr בדוגמה זו).משתנה זה מוגדר על ידי var או בציון הטיפוס בצורה מפורשת , המשתנה הוא Strongly type ומכיוון שכך שגיאה בשאילתה תתגלה בזמן קומפילציה (לעומת שאילתת SQL רגילה הטעות תתגלה רק בזמן ריצה) וההשלמה האוטומטית של ה- IntelliSense זמינה גם בשאילתות.
לדוגמה, אם האוסף arr  מכיל int אזי num ה
מוגדר על ידי var יהיה מטיפוס int גם הוא.
למרות זאת ניתן, אם נרצה בכך, להגדיר אותו בצורה מפורשת: from int num in arr.
בספרות המקצועית num מכונה identifier או range variable.
3 – מקור המידע עליו מתבצעת השאילתה מוגדר באמצעות המילה השמורה
in,
בדוגמה מקור המידע נקרא 
arr .

4 - מקור המידע חייב להיות מטיפוס המממש את IEnumerable או מממש את הגרסה הגנרית שלו IEnumerable<T> או מממש ממשק היורש ממשקים אלו.
5 – כמו בשאילתותSQL  הרגילות והמוכרות ניעזר ב- where כאשר יוצר הצורך להגדיר תנאי לשאילתה (או פילטר אם נרצה), התנאי יכול להיות תנאי פשוט או תנאי מורכב תוך שימוש באופרטור AND ( && ) או באופרטור OR ( || ), where הוא אופציונאלי.
השאילתה תחזיר רק את אותם אלמנטים שעומדים בתנאי שהוגדר באמצעות where.
6 – select מגדיר את הטיפוס של האלמנטים שיוצרו כתוצאת השאילתה, הטיפוס לא חייב להיות זהה לטיפוס של האלמנטים במקור המידע.

דוגמת קוד (LinqSample01): 

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         // Data source.
 6  :         int[] arr = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 
 7  :         // Query creation.
 8  :         var result = 
 9  :             from num in arr 
 10 :             where (num % 2) == 0 
 11 :             select num; 
 12 :         // Query execution.
 13 :         foreach (int num in result) 
 14 :         { 
 15 :             Console.Write("{0}, ", num); 
 16 :         } 
 17 :         Console.WriteLine(); 
 18 :     } 
 19 : } 

בשלב ראשון נגדיר מבנה הנתונים (שורה 6), מבנה הנתונים חייב להיות סידרתי ומטיפוס המממש את IEnumerable.

בשלב שני (שורה 8) נגדיר את השאילתה תוך שימוש בתחביר Linq , בשלב זה השאילתה מוגדרת אולם עדיין לא מתבצעת.
בשלב השלישי (שורה 13), השאילתה מתבצעת  וערכיה המוחזרים מודפסים גם
result  חייב להיות מטיפוס המממש את הממשק IEnumerable.
השאילתה מתבצעת רק כאשר יש צורך בתוצאתה.

לחילופין ניתן להגדיר את result בצורה יותר מפורשת:

 1  : IEnumerable<int> another_result = 
 2  :     from num in arr 
 3  :     where (num % 2) == 0 
 4  :     select num; 
 5  :          

פלט:

פלט תוכנית הדוגמה LinqSample01

בדוגמה הבאה (LinqSample02) המזהה  nameהוא מטיפוס string ולכן ניתן להיעזר במתודות שלו לצורך בנית השאילתה, זה אחד היתרונות של ניצול ה- Strong Type.
במידה וב-
string לא הייתה מוגדרת המתודה StartsWith  או לא הייה קיים מאפיין בשם Length  הייתה מתקבלת שגיאת קומפילציה :

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         string[] names_arr = { "Shoshana", "Elimelech", "Zrubavel", "Hasya", "Fruma",  
 6  :                                    "Zelda", "Metushelach","Zalman","Shira", "Ben Zion", 
 7  :                                    "Zecharia", "Hiskiyah", "Joshafat", "Yerocham", "Tuvia", 
 8  :                                    "Elisheva", "Bracha", "Shaul", "Bruriya", "Zmira", "Ziona",  
 9  :                                    "Zafrira","Ruchama", "Shimshon","Zevulun",}; 
 10 :         var result = from name in names_arr 
 11 :                      where name.StartsWith("Sh") && name.Length > 5 
 12 :                      select name; 
 13 :         foreach (var name in result) 
 14 :             Console.WriteLine(name); 
 15 :     } 
 16 : } 

פלט:

פלט תוכנית הדוגמה LinqSample02

שאילתות Linq ניתן להריץ גם על אוסף אובייקטים, אמנם זה כבר לא צריך להפתיע אתכם בשלב זה משום שהשאילתות בדוגמאות הקודמות התבצעו על אוסף של int ועל אוסף של string שהם אובייקטים לכל דבר ועניין, אולם הדוגמה הבאה תבצע שאילתה על אוסף אובייקטים ממחלקה המוגדרת בתוכנית (LinqSample03):

תחילה הוגדרו שני טיפוסים חדשים: enum Department ו- class Employee:

 1  : enum Department { Production, Marketing, Manpower, Administration } 
 2  : class Employee 
 3  : { 
 4  :     public string Name { get; set; } 
 5  :     public Department Department { get; set; } 
 6  :     public override string ToString() 
 7  :     { 
 8  :         return string.Format("Name: {0,-10}, Department: {1}", Name, Department); 
 9  :     } 
 10 : } 

 Main:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         List<Employee> list = new List<Employee> 
 6  :             { 
 7  :                 new Employee { Name="Shoshana", Department=Department.Administration} , 
 8  :                 new Employee { Name="Elimelech", Department=Department.Marketing} , 
 9  :                 new Employee { Name="Zrubavel", Department=Department.Manpower} , 
 10 :                 new Employee { Name="Hasya", Department=Department.Administration} , 
 11 :                 new Employee { Name="Fruma", Department=Department.Production} , 
 12 :                 new Employee { Name="Zelda", Department=Department.Production} , 
 13 :                 new Employee { Name="Metushelach", Department=Department.Marketing} , 
 14 :                 new Employee { Name="Zalman", Department=Department.Production} , 
 15 :                 new Employee { Name="Zevulun", Department=Department.Administration} , 
 16 :                 new Employee { Name="Patachkamaz", Department=Department.Production} , 
 17 :             }; 
 18 :         var result = from e in list 
 19 :                      where e.Department == Department.Production 
 20 :                      select e; 
 21 :         foreach (Employee e in result) 
 22 :         { 
 23 :             Console.WriteLine(e.ToString()); 
 24 :         } 
 25 :     } 
 26 : } 

פלט:

פלט תוכנית הדוגמה LinqSample03 

הדוגמה הבאה (LinqSample04) מציגה ביצוע פעולות מתמטיות על הערכים שנבחרו בשאילתה:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         int[] arr = { 4, 81, 64, 16, 9, 25, 49 }; 
 6  :         var result = 
 7  :             from num in arr 
 8  :             select Math.Sqrt(num); 
 9  :         foreach (var num in result) 
 10 :         { 
 11 :             Console.WriteLine(num); 
 12 :         } 
 13 :     } 
 14 : } 

בשורה 8 מתבצעת המניפולציה של הוצאת שורש מערכי num של המערך.
פלט:

פלט דוגמת הקוד LinqSample04

בשאילתות Linq ניתן להשתמש לשלב מחלקות, מתודות ומאפיינים המוגדרים ב- .NET Framework כמו השימוש שנעשה בדוגמה במתודה  Math.Qsrt.

ולא רק זאת, לא בכל המקרים האוסף שהוא תוצאת השאילתה הוא מאותו הטיפוס של האוסף שתושאל בשאילתה,  Linq יודעת לבצע המרה (טרנספורמאציה) אוטומטית מטיפוס לטיפוס במקרה הצורך כפי שמציגה הדוגמה הבאה (LinqSample05):  

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         int[] arr = { 4, 81, 64, 16, 9, 25, 49 }; 
 6  :         var result = 
 7  :             from num in arr 
 8  :             select Math.Sqrt(num); 
 9  :         foreach (var num in result) 
 10 :         { 
 11 :             Console.Write(num.GetType() + ", "); 
 12 :             Console.WriteLine(num); 
 13 :         } 
 14 :     } 
 15 : } 

נוסיף לדוגמה הקודמת את הקוד המופיע בשורה 11 המדפיס את הטיפוס המוחזר.
מכיוון שהערך המוחזר של ()Math.Sqrt אינו מספר שלם אלא מטיפוס double האוסף שיווצר הוא של אלמנטים מטיפוס double.

פלט:

פלט תוכנית הדוגמה LinqSample05

Linq מאפשרת לתשאל ערכים ממבנה נתונים אחד ולהחזיר ערכים מתאימים ממבנה נתונים אחר (LinqSample06):

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         int[] digits_arr = { 7, 1, 2, 6, 8 }; 
 6  :         string[] strings_arr = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }; 
 7  :         var result = 
 8  :             from num in digits_arr 
 9  :             select strings_arr[num]; 
 10 :         foreach (var str in result) 
 11 :         { 
 12 :             Console.WriteLine(str); 
 13 :         } 
 14 :     } 
 15 : } 

פלט:

פלט תוכנית הדוגמה LinqSample06

Linq ו- Anonymous Types

Anonymous Types זו אחת התכונות החדשות שלבטח התווספו ל- C#  גם מתוך מחשבה על הצרכים והיכולות של Linq.
בלא מעט מקרים, נגדיר טיפוס חדש אשר ידע להכיל את ערכי תוצאות השאילתה, ומכיוון שאין משמעות לטיפוס מעבר לצרכי השאילתה נגדירו כ-
Anonymous Type, כפי שמציגה הדוגמה LinqSample07 אשר מאחדת ל- Anonymous Typeערכים המגיעים משני מערכים:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         int[] digits_arr = { 7, 1, 2, 6, 8 }; 
 6  :         string[] strings_arr = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }; 
 7  :         var result = 
 8  :             from num in digits_arr 
 9  :             select new { Digit = num, Text = strings_arr[num] }; 
 10 :         foreach (var obj in result) 
 11 :         { 
 12 :             Console.WriteLine("Digit = {0}, Text = {1}", obj.Digit, obj.Text); 
 13 :         } 
 14 :     } 
 15 : } 

פלט:

פלט תוכנית הדוגמה LinqSample07



שאילתות ממסד נתונים.
טבלאות יכולות להיות גדולות מאוד, מרובות שדות, 
לעיתים נשלוף את כל השדות של טבלה מסוימת ובמקרים אחרים נשלוף רק את השדות הרלבנטיים למשימה שעומדת בפנינו.
לדוגמה:

SELECT ArticleID, Title, Link ,Author
FROM Article

הטבלה Article מכילה כ-45 שדות, לטובת דו"ח מסויים אנו זקוקים רק למזהה הייחודי של הרשומה, לכותרת, לקישור ולשם המחבר.

Linq מאפשר לבצע זאת על מחלקות.
במחלקות מוגדרים מספר תכונות, לעיתים נהיה זקוקים לכולם אולם בלא מעט מקרים אנו נשתמש רק בחלקן לצורך פעילות מסוימות.
באמצעות השימוש ב-
Anonymous Types נוכל לברור רק את התכונות אותן אנו צריכים על מנת להתמודד עם הבעיה העומדת מולנו.

דוגמת הקוד LinqSample08 מדגימה יכולת זו:
תחילה נגדיר מחלקה בשם  Student:

 1  : class Student 
 2  : { 
 3  :     public int StudentID { get; set; } 
 4  :     public string FirstName { get; set; } 
 5  :     public string LastName { get; set; } 
 6  :     public string Email { get; set; } 
 7  :     public string PhoneNum { get; set; } 
 8  :     public DateTime Birthdate { get; set; } 
 9  : } 

דו"ח שאנו נדרשים לחבר צריך להדפיס את השמות הפרטיים ושמות המשפחה של כל הסטודנטים במאגר שנולדו בשנת 1995 (Main):

 

 

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         List<Student> list = Student.CreateStudentList(); 
 6  :         var result = from student in list 
 7  :                      where student.Birthdate.Year == 1995 
 8  :                      select new { LastName = student.LastName, FirstName = student.FirstName }; 
 9  :         foreach (var student in result) 
 10 :         { 
 11 :             Console.WriteLine("{0} {1}", student.FirstName, student.LastName); 
 12 :         } 
 13 :     } 
 14 : } 

בשורה 7 נסנן את הסטודנטים שנולדו ב-1995, בשורה 8 נייצר טיפוס אנונימי שיחזיר רק את השם פרטי ושם המשפחה של הסטונטים שעונים על הקריטריון של הסינון.

פלט:

 

פלט תוכנית הדוגמה LinqSample08


מאמר זה על שפת Linq התמקד ב- linq to Object והוא הראשון מתוך סדרה של שלושה מאמרים בנושא.
המאמר הבא יסקור את האופרטורים השכיחים של
Linq וזה שאחריו את השימוש במתודות linq המוגדרות כ- Extension Methods.




כל הזכויות שמורות למחבר ©