Linq To Object - חלק ראשון
מאת: ארז קלר
בסופו של יום, כמפתחים חלק ניכר מזמננו מוקדש לביצוע מניפולציות על מידע.
מידע יכול להגיע ממגוון מקורות: מסדי נתונים, קבצי 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.
תחביר:
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 :
פלט:
בדוגמה הבאה (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 : }
פלט:
שאילתות 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 : }
פלט:
הדוגמה הבאה (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 של המערך.
פלט:
בשאילתות 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.
פלט:
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 : }
פלט:
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 : }
פלט:
שאילתות ממסד נתונים.
טבלאות יכולות להיות גדולות מאוד, מרובות שדות, לעיתים נשלוף את כל השדות של טבלה מסוימת ובמקרים אחרים נשלוף רק את השדות הרלבנטיים למשימה שעומדת בפנינו.
לדוגמה:
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 נייצר טיפוס אנונימי שיחזיר רק את השם פרטי ושם המשפחה של הסטונטים שעונים על הקריטריון של הסינון.
פלט:
מאמר זה על שפת Linq התמקד ב- linq to Object והוא הראשון מתוך סדרה של שלושה מאמרים בנושא.
המאמר הבא יסקור את האופרטורים השכיחים של Linq וזה שאחריו את השימוש במתודות linq המוגדרות כ- Extension Methods.
כל הזכויות שמורות למחבר ©