با سلام به کاربران عزیز با یکی دیگر از سری آموزش های LINQ در C# در خدمت شما هستیم. در این جلسه قصد داریم با اپراتور های joining آشنا شویم.
اگر با زبان SQL آشنایی داشته باشید می دانید که اپراتور Join
برای پیوند دادن دو یا چند جدول بر اساس فیلدهایی مشخص و مشترک استفاده می شود. در LINQ نیز کاربرد آن دقیقا مانند زبان SQL است فقط روش اعمال آن متفاوت است که در ادامه با آن آشنا خواهید شد.
توجه: ذکر این نکته ضروری است که پیاده سازی اپراتور های joining با استفاده از کلمه ی Join است. فقط به کاربردن آن همراه با دیگر اپراتور ها باعث تغییر کارایی Join خواهد شد.
عملکرد joining در LINQ به چهار بخش:
INNER JOIN
LEFT OUTER JOIN
CROSS JOIN
GROUP JOIN
تقسیم بندی می شوند که در جدول زیر کاربرد هر یک بررسی شده است:
کاربرد | نام اپراتور |
عناصری را از مجموعه سمت چپ و راست بر اساس یک فیلد مشترک باز می گرداند | INNER JOIN |
عناصر را از مجموعه سمت چپ و عناصر مربوطه از مجموعه سمت راست باز می گرداند | LEFT OUTER JOIN |
هر عنصر از مجموعه ی سمت چپ را به تمامی عناصر مجموعه ی سمت راست اضافه می کند (ربط می دهد) | CROSS JOIN |
عناصر را بر اساس مجموعه ی چپ و راست، به ترتیب گروه مرتب سازی می کند | GROUP JOIN |
با استفاده از این متد می توان دو یا چند مجموعه را بر اساس فیلدهای مشخصی با هم ارتباط داد. توضیح این نوع اپراتورها شاید کمی گیج کننده به نظر برسد. اجازه دهید با بررسی یک مثال آن را توضیح دهیم:
الگوی استفاده از join
به صورت زیر است:
var result = from d in objDept join e in objEmp on d.DepId equals e.DeptId select new { EmployeeName = e.Name, DepartmentName = d.DepName };
دو شی objEmp
و objDept
نماینده ی دو مجموعه ی مختلف هستند که دارای فیلدهای مشخصی می باشند که یک فیلد بین دو مجموعه مشترک است در واقع خط ارتباطی این دو مجموعه (یا جدول) بر اساس فیلد مشترک است که در این الگو، فیلد مشترک DepId
و DeptId
است.
حالا مثال زیر را در نظر بگیرید:
using System; using System.Collections.Generic; using System.Linq; namespace Linqtutorials { class Program { static void Main(string[] args) { List<Department> objDept = new List<Department>(){ new Department{DepId=1,DepName="Software"}, new Department{DepId=2,DepName="Finance"}, new Department{DepId=3,DepName="Health"} }; List<Employee> objEmp = new List<Employee>() { new Employee { EmpId=1,Name = "Suresh Dasari", DeptId=1 }, new Employee { EmpId=2,Name = "Rohini Alavala", DeptId=1 }, new Employee { EmpId=3,Name = "Praveen Alavala", DeptId=2 }, new Employee { EmpId=4,Name = "Sateesh Alavala", DeptId =2}, new Employee { EmpId=5,Name = "Madhav Sai"} }; var result = from d in objDept join e in objEmp on d.DepId equals e.DeptId select new { EmployeeName = e.Name, DepartmentName = d.DepName }; foreach (var item in result) { Console.WriteLine(item.EmployeeName + "\t | " + item.DepartmentName); } Console.ReadLine(); } } class Department { public int DepId { get; set; } public string DepName { get; set; } } class Employee { public int EmpId { get; set; } public string Name { get; set; } public int DeptId { get; set; } } }
به توضیحات این مثال با دقت توجه کنید:
به کلاس Department
دقت کنید این کلاس دارای دو فیلد DepId
و DepName
است که در Main
، اشیایی از آن در یک لیست با نام objDept
قرار گرفته اند.
حالا کلاس Employee
را در نظر بگیرید این کلاس دارای سه فید EmpId
و Name
و DeptId
می باشد که در Main
اشیایی از آن در یک لیست با نام objEmp
قرار گرفته اند. فیلد DeptId
برای ما اهمیت دارد چون در واقع خط ارتباط دو کلاس Department
و Employee
است. به مقادیر فیلد ها در دو لیست موجود دقت کنید.
در این مثال در اشیای کلاس Department
چند گروه شغلی با ID
هایی مشخص تعریف شده اند مثلا گروه شغلی Health
با ID=3
مشخص شده است. در اشیای کلاس Employee
کارمندان یک سازمان که هر کدام یک ID
مشخص دارند، ثبت شده و در فیلد DeptId
در واقع ID
شغل آن ها ثبت گردیده که اشاره به کلاس Department
دارد.
مثلا رکورد EmpId=1,Name = "Suresh Dasari", DeptId=1
شخصی را با ID=1
و نام Suresh Dasari
و شغل Software
مشخص می کند.
زمانی را در نظر بگیرید که تعداد کارمندان و ردیف های شغلی بسیار زیاد است و قصد داریم افراد را بر اساس ردیف های شغلی دسته بندی کنیم، در اینجا نیاز به استفاده از متد Join داریم که در این مثال از آن استفاده شده است.
در این مثال ابتدا روش Query را بررسی می کنیم:
d
را می توان اشاره گری به objDept
و e
را اشاره گری به objEmp
در نظر گرفت. با استفاده از متد Join
اعلام می کنیم که دو شی به هم متصل (ربط داده) شوند. در خط بعدی فیلد مشترک را بعد از کلمه ی on مشخص می کنیم و شرط تساوی آن را با کلمه ی equals
اعلام می کنیم. خب به عبارت بعد از Select
دقت کنید در انتها ما باید اعلام کنیم که اطلاعاتی که از دو جدول با هم Join
شدند بر چه اساس و فیلد هایی باید به عنوان خروجی در نظر گرفته شوند.
در این مورد e.Name
از کلاس Employee
با نام EmployeeName(نام دلخواه)
و d.DepName
از کلاس Department
با نام DepartmentName(نام دلخواه)
خروجی مورد نظر ما است. مشاهده می کنید که در حلقه ی foreach
این دو توسط item
قابل دستیابی است.
تذکر:اگر قسمت EmployeeName = e.Name
و DepartmentName = d.DepName
را متوجه نشدید عبارت anonymous type را در اینترنت جست و جو کنید.
همان طور که در خروجی مشخص است فقط افرادی که دارای DeptId
هستند، نشان داده شده اند.
برای نوشتن کد به روش Extension به صورت زیر عمل می کنیم:
var result = objDept.Join(objEmp,d =>d.DepId, e => e.DeptId,(dep,emp) =>new {EmployeeName=emp.Name,DepartmentName=dep.DepName });
خروجی این کد کاملا مشابه با حالت قبل است و فقط نوشتن آن کمی پیچیده تر است.
این پیاده سازی تمامی عناصر موجود در مجموعه ی چپ را بر اساس عناصر موجود در مجموعه ی سمت راست بر می گرداند. به مثال قبلی دقت کنید فقط اسم کارمندانی در خروجی قابل مشاهده است که فیلد DeptId
آن ها دارای مقدار است. در واقع می توان این چنین تصور کرد که اولویت با نشان دادن اطلاعات مجموعه ی سمت چپ است. عکس زیر مفهوم کلی Left Outer Join را نشان می دهد.
الگوی استفاده از آن تا حدودی مانند Inner Join
است اما دارای تفاوت های کوچکی است که می توانید در الگوی زیر مشاهده کنید:
var result = from e in objEmp join d in objDept on e.DeptId equals d.DepId into empDept from ed in empDept.DefaultIfEmpty() select new { EmployeeName = e.Name, DepartmentName = ed == null ? "No Department" : ed.DepName }
ابتدا به متد DefaultIfEmpty
دقت کنید. در اینجا بر روی شی empDept
اعمال شده این شی حاوی اطلاعات دو مجموعه بر اساس دو فیلد مشترک است. در چند خط بعدی بر روی ed
که حاوی اطلاعات empDept.DefaultIfEmpty
است یک شرط قرار داده شده که اگر ed.DepName
دارای مقدار نبود عبارت No Department نمایش داده شود در غیر این صورت DepName
مربوطه چاپ می شود. به طور کلی خروجی متد DefaultIfEmpty
برای فیلد هایی که مقدار ندارند 0 و برای باقی فیلدهایی که داری مقدار هستند، برابر با مقدار خود فیلد است.
نکته: منظور از مجموعه ی سمت چپ مجموعه ای است که ابتدا نوشته می شود.
برای درک بهتر به مثال زیر توجه کنید:
using System; using System.Collections.Generic; using System.Linq; namespace Linqtutorials { class Program { static void Main(string[] args) { List<Department> objDept = new List<Department>(){ new Department{DepId=1,DepName="Software"}, new Department{DepId=2,DepName="Finance"}, new Department{DepId=3,DepName="Health"} }; List<Employee> objEmp = new List<Employee>() { new Employee { EmpId=1,Name = "Suresh Dasari", DeptId=1 }, new Employee { EmpId=2,Name = "Rohini Alavala", DeptId=1 }, new Employee { EmpId=3,Name = "Praveen Alavala", DeptId=2 }, new Employee { EmpId=4,Name = "Sateesh Alavala", DeptId =2}, new Employee { EmpId=5,Name = "Madhav Sai"} }; var result = from e in objEmp join d in objDept on e.DeptId equals d.DepId into empDept from ed in empDept.DefaultIfEmpty() select new { EmployeeName = e.Name, DepartmentName = ed == null ? "No Department" : ed.DepName }; foreach (var item in result) { Console.WriteLine(item.EmployeeName + "\t | " + item.DepartmentName); } Console.ReadLine(); } } class Department { public int DepId { get; set; } public string DepName { get; set; } } class Employee { public int EmpId { get; set; } public string Name { get; set; } public int DeptId { get; set; } } }
کد بالا دقیقا کد استفاده شده برای مثال اول است فقط تغییراتی کوچک در آن انجام شده تا عملیات Left Outer Join را پیاده سازی کند. کد را در IDE خود اجرا کنید و خروجی را مشاهده کنید می بینید که اسم تمامی کارمندان در خروجی نشان داده شده (مجموعه ی چپ) و مواردی که DepName
آن ها مقدار ندارد با No Department مشخص شده است. به یاد دارید که در خروجی مثال اول فقط اسامی کارکنانی که دارای DepName
بودند در خروجی قابل مشاهده بود.
نوشتن کد به صورت Query برای Left Outer Join کمی پیچیده و مشکل است و نیازمند به کارگیری متدهایی است که تا الان بررسی نشده اند به همین دلیل آن را در جایی مناسب بررسی خواهیم کرد.
در این روش از Join
تمامی عناصر مجموعه ی سمت چپ به تمامی عناصر مجموعه ی سمت راست نسبت داده می شوند. به شکل زیر که نمایی کلی از Cross Join است دقت کنید:
الگوی استفاده از آن به صورت زیر است:
var result = from e in obj1 from d in obj2 select new { name1 = e.property, name2 = d.property };
به مثال زیر توجه کنید:
using System; using System.Collections.Generic; using System.Linq; namespace Linqtutorials { class Program { static void Main(string[] args) { List<Department> objDept = new List<Department>(){ new Department{DepId=1,DepName="Software"}, new Department{DepId=2,DepName="Finance"}, new Department{DepId=3,DepName="Health"} }; List<Employee> objEmp = new List<Employee>() { new Employee { EmpId=1,Name = "Suresh Dasari", DeptId=1 }, new Employee { EmpId=2,Name = "Rohini Alavala", DeptId=1 }, new Employee { EmpId=3,Name = "Praveen Kumar", DeptId=2 }, new Employee { EmpId=4,Name = "Sateesh Chandra", DeptId =2}, new Employee { EmpId=5,Name = "Madhav Sai"} }; var result = from e in objEmp from d in objDept select new { EmployeeName = e.Name, DepartmentName = d.DepName }; foreach (var item in result) { Console.WriteLine(item.EmployeeName + "\t | " + item.DepartmentName); } Console.ReadLine(); } } class Department { public int DepId { get; set; } public string DepName { get; set; } } class Employee { public int EmpId { get; set; } public string Name { get; set; } public int DeptId { get; set; } } }
همانطور که مشاهده می کنید در پیاده سازی Cross Join
اصلا واژه ی Join
به کار نرفته است.
اگر کد را اجرا کنید و خروجی را مشاهده کنید در نگاه اول شاید عجیب به نظر برسد ولی این روش پیاده سازی Join برای تولید داده های سطری و ستونی مرتب به کار می رود ولی کاربرد چندانی ندارد. به همین دلیل از توضیح بیشتر آن پرهیز می کنیم اما برای درک کلی کاربرد آن مثال جالب زیر را در نظر بگیرید:
char[] letters = "ABCDEFGH".ToCharArray(); char[] digits = "12345678".ToCharArray(); var coords = from l in letters from d in digits select l.ToString() + d; foreach (var coord in coords) { Console.Write("{0} ", coord); if (coord.EndsWith("8")) { Console.WriteLine(); } }
یکی از مهم ترین و پر کاربردترین پیاده سازی های Join
استفاده از Group Join است. برای فهم بهتر این کاربرد توضیح آن را در قالب مثال بررسی می کنیم.
مثال:
using System; using System.Collections.Generic; using System.Linq; namespace Linqtutorials { class Program { static void Main(string[] args) { List<Department> objDept = new List<Department>(){ new Department{DepId=1,DepName="Software"}, new Department{DepId=2,DepName="Finance"}, new Department{DepId=3,DepName="Health"} }; List<Employee> objEmp = new List<Employee>() { new Employee { EmpId=1,Name = "Suresh Dasari", DeptId=1 }, new Employee { EmpId=2,Name = "Rohini Alavala", DeptId=1 }, new Employee { EmpId=3,Name = "Praveen Alavala", DeptId=2 }, new Employee { EmpId=4,Name = "Sateesh Alavala", DeptId =2}, new Employee { EmpId=5,Name = "Madhav Sai"} }; var result = from d in objDept join e in objEmp on d.DepId equals e.DeptId into empDept select new { DepartmentName = d.DepName, Employees = from emp2 in empDept orderby emp2.Name select emp2 }; foreach (var empGroup in result) { Console.WriteLine(empGroup.DepartmentName); foreach (var item in empGroup.Employees) { Console.WriteLine(" {0}", item.Name); } } Console.ReadLine(); } } class Department { public int DepId { get; set; } public string DepName { get; set; } } class Employee { public int EmpId { get; set; } public string Name { get; set; } public int DeptId { get; set; } } }
این مثال دقیقا مانند مثال بررسی شده در قسمت Left Outer Join است، فقط نوع نمایش خروجی آن منظم تر است. توجه داشته باشید تا خط کد select new
کاملا مشابه قبل پیاده سازی شده اما مهم ترین بخشی که باعث می شود داده ها به صورت منظم در خروجی نمایان شوند Query دومی است که حاصل آن در Employees
ذخیره می شود. لازم به ذکر است نام Employees کاملا اختیاری بوده و قابل تغییر است. در این مثال از empDept
یک Query گرفتیم و با استفاده از متد orderby
نام کارکنان را مرتب سازی کرده ایم که در نتیجه ی آن خروجی منظم خواهیم داشت. به شکل زیر که خروجی این مثال است توجه کنید و با خروجی حاصل شده از مثال مربوط به Left Outer Join مقایسه کنید.
همان طور که مشاهده می کنید هر ردیف شغلی با کارمندان بخش خود به صورت مرتب در خروجی نشان داده شده اند. حالا به قسمتی از کد که foreach
در آن به کار رفته، دقت کنید. وظیفه ی این بخش چاپ صحیح و مرتب اطلاعاتی است که در result
ذخیره شده اند. حلقه ی اول وظیفه ی چاپ DepartmentName
را دارد.
به پیمایش کننده ی empGroup
دقت کنید که به دو شی DepartmentName
و Employees
دسترسی دارد. DepartmentName
از نوع string
است که توسط حلقه ی اول پیمایش و چاپ می شود اما شی Employees
از نوع Query
بوده پس برای چاپ آن نیاز به یک حلقه ی داخلی است تا اطلاعات آن را پیمایش و چاپ کند.
این قسمت از آموزش هم به پایان رسید. توجه داشته باشید مبحث Join
کمی مشکل بوده و توضیح کاربرد آن ها مشکل تر و نیاز به حل مثال هایی بیشتر دارد پس بهترین راه ممکن برای یادگیری عمیق آن حل تمرین بسیار است.
موفق باشید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.