در این مقاله قصد دارم تا شما را با Language Integrated Query یا LINQ و کاربردهای آن در زبان سی شارپ آشنا کنم. این ویژگی که به شما قابلیت اجرای کوئری های مختلف داخل زبان سی شارپ را می دهد که با ارئه نسخه 3 از زبان #C معرفی شد.
به طور خلاصه در هر کدام از این بخش ها چه مطالبی مطرح خواهد شد؟
در طول مقاله فرض کردم که دوستان با مفاهیم زیر آشنایی دارند:
تمام کدهای ارائه شده در این مقاله با زبان #C نوشته می شوند. همچنین پس از اتمام مقاله نسخه PDF این مقاله به همراه کدهای #C در اختیار کاربران انجمن قرار خواهد گرفت.
در ادامه مقدمه ای بر LINQ خواهیم داشت و همچنین با قابلیت ها و توانایی های آن به صورت مختصر آشنا خواهیم شد. شاید اولین سوالی که در ذهن هر یک از شما پیش بیاد این باشه که چه تعریفی می توان برای LINQ ارائه داد؟ چه کارهایی می توان با LINQ انجام داد؟
LINQ چه قابلیت هایی را در اختیار یک برنامه نویس قرار می دهد؟ به این سوالات می شود به این صورت جواب داد که LINQ شامل یکسری عملگرهای استاندارد است که به شما امکان می دهد بر روی انواع منابع داده در داخل زبان های سازگار با NET. مانند VB.NET یا #C کوئری هایی را نوشته و اجرا کنید.
نام Language Integrated Query نیز به این دلیل انتخاب شده است که این کوئری ها داخل یک زبان برنامه نویسی مانند #C نوشته و اجرا می شوند. اما منابع داده ای که LINQ می تواند از آنها استفاده کند می تواند یک شئ ایجاد شده، یک فایل مستندات XML، یک بانک SQL Server و یا یک منبع دلخواه باشد.
LINQ این قابلیت را دارد که با تمامی این منابع داده کار کند. علاوه بر استخراج و اجرای کوئری بر روی منابع داده نیز، بوسیله LINQ شما امکان تغییر و دستکاری یک منبع داده مانند یک بانک SQL Server را خواهید داشت. برای شروع با یک مثال کوچک شروع میکنیم:
فرض کنید که یک آرایه از نوع رشته داریم که یکسری نام را داخل آن قرار دادیم، حالا می خواهیم نام هایی که با حروف Mo شروع می شوند را جستجو کرده و به کاربر نمایش دهیم. برای اینکار باید یک آرایه ایجاد کرده و با یک دستور foreach بر روی تک تک عناصر آرایه عملیات مقایسه را انجام داده و نتیجه را به کاربر نشان دهیم:
string[] names = { "Ali", "Hasan", "Mojtaba", "Hamid", "Morteza", "Reza", "Mohammad" }; foreach (string name in names) if(name.StartsWith("Mo")) Console.WriteLine(name);
با اجرای کد بالا تمامی نام هایی که با حروف Mo شروع می شوند در خروجی چاپ خواهند شد. اما اینکار را می توان با نوشتن یک کوئری بسیار ساده LINQ نیز انجام داد. قطعه کد زیر یک کوئری LINQ است که نام هایی که با Mo شروع می شوند را برای ما استخراج کرده و بر روی خروجی نمایش میدهد:
string[] names = { "Ali", "Hasan", "Mojtaba", "Hamid", "Morteza", "Reza", "Mohammad" }; IEnumerable query = from n in names where n.StartsWith("Mo") select n; foreach (string name in query) Console.WriteLine(name);
در کد بالا دستوراتی وجود دارد که شاید با آنها نا آشنا باشید، اما نگران نباشید، در ادامه این مقاله با تک تک دستورات بالا و کاربرد هر یک آشنا خواهید شد. اما مهمترین چیزی که در دستورات بالا توجه یک برنامه نویس را به خود جلب میکند، شباهت ساختار کوئری نوشته شده با کوئری های SQL است. وقتی این کوئری اجرا می شود یک شئ از نوع اینترفیس <IEnumerable برای مقایسه عملگرهای LINQ با دستوراتی که قبلا" برای گرفتن کوئری ها از اشیاء استفاده می شد، به سراغ کلاس <List
public class Person { public Person() { } public Person(string firstName, string lastName, int age) { this.FirstName = firstName; this.LastName = lastName; this.Age = age; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public override string ToString() { return string.Format("FirstName: {0}\n{LastName: {1}\nAge: {2}", this.FirstName, this.LastName, this.Age); } }
کلاس بالا شامل سه فیلد، دو متد سازنده و متد override شده ToString است. در قدم بعدی ما یک لیست از اشیاء Person ایجاد میکنیم. (<List
List persons = new List() { new Person() { FirstName = "Ali", LastName = "Jamali", Age = 42 }, new Person() { FirstName = "Hosein", LastName = "Ahmadi", Age = 23 }, new Person() { FirstName = "Mohamamd", LastName = "Sadeghi", Age = 17 }, new Person() { FirstName = "Reza", LastName = "Jamali", Age = 24 }, new Person() { FirstName = "Nima", LastName = "Karami", Age = 18 } };
حال می خواهیم از مجموعه بالا تمامی افرادی که سنشان زیر 20 سال است را انتخاب کرده و در خروجی نمایش دهیم. کلاس <List
List query = persons.FindAll(delegate(Person p) { return p.Age < 20; }); foreach (Person person in query) { Console.WriteLine(person.ToString()); }
با اجرای کد بالا، متد FindAll شئ جدیدی از نوع <List
IEnumerable query = from p in persons where p.Age < 20 select p; foreach (Person person in query) { Console.WriteLine(person.ToString()); }
حال اگر بخواهیم شرط را تغییر بدهیم، بطوریکه علاوه بر سن افراد، نام آنها نیز مورد مقایسه قرار بگیرد کافیست کوئری خود را به صورت زیر تغییر دهیم:
IEnumerable query = from p in persons where p.Age < 20 && p.FirstName.StartsWith("M") select p;
تا اینجای مقاله در کوئری هایی که نوشتیم تنها از عملگر where برای فیل تر سازی اطلاعات استفاده کردیم، در LINQ عملگرهای دیگری برای مرتب سازی نتایج، گروه بندی، ادغام و ... وجود دارد که در بخش های بعدی این مقاله به تفصیل در مورد آنها صحبت خواهیم کرد.
اما یکی از قابلیت های جالب در نوشتن کوئری های LINQ که در محیط Visual Studio به چشم می آید قابلیت IntelliSense در نوشتن Query ها است که نوشتن آن ها را بسیار آسان می کند. همانگونه که شما وقتی شئ ای از نوع string ایجاد می کنید
و می توانید به وسیله IntelliSense به تمامی متدهای آن دسترسی داشته باشید، در کوئری های LINQ نیز عملیات به همین گونه است. شما در هنگام نوشتن کوئری ها به تمامی متد هایی که برای یک شئ تعریف شده است دسترسی دارید مانند متد StartWith کلاس string که در کوئری های بالا از آن استفاده کردیم. اما در بالا گفتیم که LINQ قابلیت کار با چندین منبع داده را دارد که در زیر به طور خلاصه به بررسی هر یک از این منابع می پردازیم:
XElement x = new XElement( "Person", new XElement("FirstName", "Hosein"), new XElement("LastName", "Ahmadi"));
حاصل قطعه کد بالا تگ های XML ایجاد شده زیر است:
Hosein Ahmadi
حال اگر بخواهیم از XAttribute نیز در کلاس XElemet استفاده کنیم، کد بالا را به صورت زیر تغییر می دهیم:
XElement x = new XElement("Person", new XAttribute("PersonID","10"), new XElement("FirstName", "Hosein"), new XElement("LastName", "Ahmadi"));
حال المان ایجاد شده توسط کد بالا به صورت زیر خواهد بود:
Hosein Ahmadi
اما قدرت اصلی LINQ to XML در این قسمت معلوم می شود که میتوانیم پارامتر های ورودی جهت قرار گیری در المان XML تعریف شده را توسط یک Query انتخاب کنیم:
string[] names = { "Hosein", "Hamid", "Reza", "Mohammad", "Hadi" }; XElement x = new XElement("SelectedNames", from n in names where n.StartsWith("H") select new XElement("Name", n));
المان ایجاد شده توسط کد بالا به صورت زیر است:
Hosein Hamid Hadi
در بخش های بعدی مقالات به طور مفصل در باره LINQ to XML صحبت خواهیم کرد.
3. LINQ to ADO.NET: این بخش در رابطه با اجرای کوئری های LINQ بر روی پایگاه های داده مانند SQL Server و همچنین دستکاری داده های آنان است. خود این بخش را می توان به سه بخش LINQ to SQL، LINQ to DataSet و LINQ to Entities تقسیم بندی کرد که هر کدام مبحث کاملی را می طلبند.
به دلیل اینکه حجم مطالب مربوط به این بخش بسیار زیاد بوده و همچنین نیاز به آشنایی با مفاهیم پایه ای مانند ORM را دارد، توضیحات کامل در بخش مربوطه مطرح خواهند شد. تنها موردی که در اینجا می توان به آن اشاره کرد این است که در LINQ to SQL و LINQ to Entities برای موجودیت های بانک (جداول) در داخل برنامه اشیاء ای ایجاد می شوند که این اشیاء به واسطه یکسری Attribute ها توانایی تقابل با بانک و جداول مربوطه را دارند.
کوئری های که برای خواندن اطلاعات از جدول به صورت کوئری های LINQ نوشته می شوند، به کوئری های SQL تبدیل شده و به سرویس SQL فرستاده می شوند تا نتایج مورد نظر گرفته شده و به کاربر نمایش داده شود. برای مثال کلاس زیر نمایش معادل یک جدول از بانک SQL است که در زبان #C بصورت یک کلاس تعریف شده است:
[Table(Name = "Person.Contact")] public class Contact { [Column(DBType = "nvarchar(50) not null")] public string FirstName; [Column(DBType = "nvarchar(50) not null")] public string LastName; [Column(DBType = "nvarchar(50) not null")] public string EmailAddress; }
به استفاده از Attribute ها برای Map کردن جدول به صورت یک شئ توجه کنید. بوسیله LINQ to SQL می توان از روی این شئ بر روی جدول معادل در بانک اطلاعاتی کوئری هایی را اجرا کرد و یا رکورد هایی را دستکاری کرد. مطالبی که تا اینجا بیان شد، خلاصه ای از توانایی ها و ویژگی های LINQ بود که در ادامه این مقاله با تفصیل در مورد هر یک از این ویژگی ها بحث خواهیم کرد. در بخش بعد در مورد ویژگی های جدید 3.0 #C که کاربرد های فراوانی در LINQ دارند بحث خواهیم کرد. این ویژگی ها به شرح زیر هستند:
در ادامه مقالات آموزشی LINQ به بررسی ویژگی های زبان 3.0 #C خواهیم پرداخت که در نوشتن کوئری های LINQ پرکاربرد هستند و آشنایی با اونها پیش نیاز یادگیری LINQ هست و در بخش های بعدی با نحوه استفاده از این ویژگی ها در کوئری های LINQ آشنا خواهیم شد. ویژگی هایی که در این مقاله بررسی خواهیم کرد به شرح زیر است:
1. اولین ویژگی که میخواهیم در مورد آن بحث کنیم ویژگی Implicity Typed Variables است. این ویژگی به شما این امکان رو میده تا انتخاب نوع داده تعریفی داخل کد را به عهده کامپایلر بزارید. برای مثال قطعه کد زیر رو در نظر بگیرید:
int i = 25
یک متغییر int تعریف کردیم و مقدار آن را مساوی 25 قرار دادیم، در #C نسخه 3.0 با استفاده از کلمه کلیدی var انتخاب نوع متغییر را به عهده کامپایلر بزاریم. انتخاب نوع بر اساس مقداری که داخل متغییر در هنگام تعریف رخته می شود (Initialization) انتخاب می شود:
var i = 12; var s = "Welcome"; var d = 2.6;
در کد های بالا، متغیر i از نوع int، متغیر s از نوع string و متغیر d از نوع double در نظر گرفته می شود. فقط نکته ای که باید در نظر بگیرید این است که هنگام استفاده از کلمه var باید متغییر مقدار دهی اولیه شود. در غیر این صورت با پیغام خطا مواجه خواهید شد.
2. ویژگی بعدی Lamda Expressions هستند، این عبارات چیزی معادل Anonymous methods در نسخه های قبلی #C هستند. در بخش قبلی درباره متد FindAll کلاس جنریک <List
static void Main(string[] args) { List list = new List() { 7, 9, 3, 1, 5, 6, 7 }; List query = list.FindAll(Compare); foreach (int i in query) Console.WriteLine(i); } static bool Compare(int a) { return a > 5; }
همانطوری که در کد بالا مشاهده میکنید، متدی با عنوان Compare تعریف شده و به عنوان پارامتر به متد FindAll جهت مقایسه فرستاده می شود. اما روش بعدی استفاده از Anonymous Methods که با استفاده از کلمه کلیدی delegate می توان از آن استفاده کرد:
static void Main(string[] args) { List list = new List() { 7, 9, 3, 1, 5, 6, 7 }; List query = list.FindAll(delegate(int a) { return a > 5; }); foreach (int i in query) Console.WriteLine(i); }
کاربرد دیگه Anonymous Methods در استفاده از Event ها است که میتوان کد مربوط به یک Event را با استفاده از Anonymous Methods به صورت درجا تعریف کرد:
حالت اول:
public Form1() { InitializeComponent(); button1.Click += Button1_Click; } public void Button1_Click(object sender, EventArgs e) { MessageBox.Show("Welcome to C# world"); }
و با استفاده از Anonymous Methods:
public Form1() { InitializeComponent(); button1.Click += delegate(object sender, EventArgs e) { MessageBox.Show("Welcome To C#"); }; }
اما از Lambda Expressions چگونه می توان استفاده کرد. عبارات Lambda همان کار Anonymous Methods را انجام می دهند با این تفاوت که دیگر نیازی به تعیین نوع پارامترهای ورودی تابع مورد نظر نیست! برای روش تر شدن قضیه به همان مثال متد FindAll توجه کنید، در کد زیر ما همان کد را با عبارات Lambda پیاده سازی میکنیم:
List query = list.FindAll(delegate(int a) { return a > 5; }); // anonymous methods with delegate keyword List query = list.FindAll((n) => { return n > 5; }); // anonymous methods with Lambda Expressions
در کد بالا، در خط اول با استفاده از کلمه کلیدی delegate و در خط دوم با استفاده از Lambda Expressions، متد بی نام مورد نظر را پیاده سازی کردیم. تفاوت موجود در ساختار ها، عدم نیاز به تعیین نوع پارامتر های ورودی متد و استفاده از عملگر <= برای نشان دادن عبارات Lambda می باشد. مثال دیگر همان تعیین متد مورد نظر برای ایونت Click کنترل Button است که در کد زیر با استفاده از عبارات Lambda آن را پیاده سازی کردیم:
button1.Click += (sender, e) => { MessageBox.Show("Welcome To C#"); };
List query = list.FindAll(n => { return n > 5; }); button1.Click += (sender, e) => { MessageBox.Show("Welcome To C#"); };
در صورتی که تعداد دستورات داخل بدنه متد تنها یک دستور باشد، می توان از نوشتن {} خودداری کرد:
button1.Click += (sender, e) => MessageBox.Show("Welcome To C#");
در صورتی که متد مورد نظر، مقداری را برگرداند (مانند متد FindAll) و تعداد دستورات داخل بدنه متد تنها یک دستور باشد که حاصل را برمیگرداند، با حذف {} ها باید کلمه کلیدی return را نیز حذف کرد:
List query = list.FindAll(n => n > 5); // valid List query = list.FindAll(n => return n > 5); // wrong
یکی از کاربرد های مهم عبارات Lambda در نوشتن کوئری های LINQ است که در بخش های بعدی مقالات با آنها بیشتر آشنا خواهیم شد.
3. یکی دیگر از ویژگی های جدید نسخه 3 زبان #C، متدهای توسعه یا Extension Methods هستند. این قابلیت به شما این امکان را می دهد تا به کلاس های موجود، متد های مورد نیاز خودتان را اضافه کنید. برای مثال، نوع داده int را فرض کنید. می خواهیم به این نوع داده، متدی با نام Negative را اضافه کنیم تا منفی عددی که داخل آن ذخیره شده است را برای ما برگرداند. برای این کار باید یک کلاس static ایجاد کرده و داخل آن متد های توسعه مورد نظر خود را بنویسیم. توجه کنید که خود متد توسعه نیز باید از نوع int باشد. برای درک بهتر این موضوع به کد زیر توجه کنید:
class Program { static void Main(string[] args) { int myInt = 25; Console.WriteLine(myInt.Negative()); } } static class Extensions { public static int Negative(this int i) { return -i; } }
با قطعه کد بالا، در داخل کلاس Extensions، ما یک متد با نام Negative برای نوع داده int تعریف کردیم. ساختار نوشتن متدهای Extensions مانند نوشتن سایر متدها می باشد، با این تفاوت که اولین پارامتر ورودی این تابع، نشان دهنده یک Instance از نوعی است که می خواهیم متد مورد نظر به آن اضافه شود.
همچنین این متد ها همیشه باید به صورت Static نوشته شوند. در بخش بالای کد و داخل متد Main ما از این متد استفاده کردیم. توجه کنید که متدهای توسعه تنها از طریق Instance های ایجاد شده از روی یک نوع داده قابل دستری هستند. نحوه نمایش متد های توسعه با سایر متدها، در Intellisence محیط VS فرق دارد. در کنار این متد ها، یک فلش آبی رنگ به سمت پایین وجود دارد که نشان دهنده یک متد توسعه است.
4. ویژگی بعدی که در مورد آن صحبت خواهیم کرد نوع های بی نام یا Anonymous Types هستند. این نوع ها با کمک کلمه کلیدی var تعریف شده و می توانند دارای یکسری خصوصیات باشند. برای مثال، می توانیم یک نوع بدون نام که دارای خصوصیات FirstName و LastName باشد را تعریف کنیم، بدون اینکه نیازی به تعریف یک کلاس جدید باشد. به مثال زیر توجه کنید:
var myAnonymousType = new { FirstName = "Hosein", LastName = "Ahmadi" }; Console.WriteLine(myAnonymousType.FirstName + myAnonymousType.LastName);
با استفاده از کد بالا یک نوع بدون نام ایجاد شد. انتخاب نام این نوع به عهده کامپایلر است. اگه به کد IL ایجاد شده توسط کامپایلر نگاهی بندازیم می بینیم که خود کامپایلر نامی برای این نوع در نظر میگیره:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 3 .locals init ( [0] class <>f__AnonymousType0`2<string, string> myAnonymousType) L_0000: nop L_0001: ldstr "Hosein" L_0006: ldstr "Ahmadi" L_000b: newobj instance void <>f__AnonymousType0`2<string, string>::.ctor(!0, !1) L_0010: stloc.0 L_0011: ldloc.0 L_0012: callvirt instance !0 <>f__AnonymousType0`2<string, string>::get_FirstName() L_0017: ldloc.0 L_0018: callvirt instance !1 <>f__AnonymousType0`2<string, string>::get_LastName() L_001d: call string [mscorlib]System.String::Concat(string, string) L_0022: call void [mscorlib]System.Console::WriteLine(string) L_0027: nop L_0028: ret }
نکته قابل توجه دیگر اینجاست که اگه دو نوع بدون نام دارای خواص یکسان باشند، می توانند به صورت Implicit به یکدیگر Cast شوند، مثال:
var myAnonymousType1 = new { FirstName = "Hosein", LastName = "Ahmadi" }; var myAnonymousType2 = new { FirstName = string.Empty, LastName = string.Empty }; myAnonymousType2 = myAnonymousType1; Console.WriteLine(myAnonymousType2.FirstName + myAnonymousType2.LastName);
نوع های بدون نام کابردهای زیادی در کوئری های LINQ دارن که در بخش های بعدی با آنها آشنا خواهیم شد. این چهار ویژگی، مهمترین ویژگی هایی بودند که در کوئری های LINQ کاربرد دارند. در بخش بعدی مقالات آموزشی LINQ، در مورد ساختار کوئری های LINQ و همچنین نحوه اجرای کوئری ها صحبت خواهیم کرد.
در این بخش از سری مقالات آموزشی، درباره ساختار کوئری های LINQ و همچنین نحوه اجرای کوئری ها صحبت خواهیم گرد.مباحث این بخش به شرح زیر می باشند:
خوب، تو اولین مرحله می خواییم با ساختار و اجزای تشکیل دهنده یک کوئری آشنا بشیم. اگه بخواییم ساختار کلی یک کوئری LINQ رو نمایش بدیم، ساختار یک کوئری LINQ به صورت زیر می باشد:
from in [query standard operators] select
همانطور که در بالا مشاهده میکنید کوئری های LINQ از بخش های مختلفی تشکیل شده اند. (قسمت هایی که داخل <> قرار گرفته توسط کاربر تعیین شده و اجباری می باشند و قسمت هایی که داخل [] قرار گرفته اند اختیاری می باشند، باقی اجزاء که داخل نشانه ای قرار نگرفته اند بخش های پیش فرض کوئری های LINQ هستند)
برای درک بهتر هر یک از بخش های بالا با یک مثال جلو میریم:
int[] nums = { 4, 1, 7, 5, 9, 6, 2 }; var query = from n in nums where n > 4 select n.ToString(); foreach (string s in query) Console.Write(s);
همانطور که گفته شد، کوئری با کلمه کلیدی from آغاز می شو، n در کوئری بالا نشان دهنده یکی از عناصر موجود در منبع داده است که از آن برای عملیات های فیل تر کردن استفاده می کنیم، بعد از n، کلمه کلیدی in و بعد از آن منبع داده مورد نظر است که در این کوئری منبع داده مورد نظر ما آرایه nums است.
در بخش بعدی از اپراتور استاندارد where که برای فیل تر کردن اطلاعات استفاده می شود، استفاده کردیم، برای فیل تر سازی اطلاعات، از n که در خط اول کوئری تعریف کردیم استفاده کردیم، در خط بعدی هم بعد از کلمه کلیدی select نوع عناصر موجود در query را تعیین می کنیم.
در اینجا با استفاده از متد ToString تعیین کردیم که عناصر موجود در کوئری از نوع string باشند. اگر به جای ()n.ToString، تنها n را می نوشتیم، عناصر موجود در لیست برگردانده شده توسط کوئری از نوع int در نظر گرفته می شد. گرفتن کوئری از منابع داده به دو صورت امکان پذیر است.
در مورد روش اول در بخش های قبلی صحبت کردیم. در روش دوم از Extension Method هایی استفاده می کنیم که در net. نسخه 3.5 اضافه شده اند. استفاده از این Method ها برای نوشتن کوئری انعطاف پذیری بیشتری رو در نوشتن کوئری ها به ما میدن. نوشتن کوئری بالا با استفاده از متد های کوئری به صورت زیر است:
int[] nums = { 4, 1, 7, 5, 9, 6, 2 }; var query = nums.Where(n => n > 4).Select(n => n.ToString()); foreach (string s in query) Console.Write(s);
اما گفتیم که استفاده از متد ها در نوشتن کوئری انعطاف پذیری بیشتری دارند. یک نمونه از این انعطاف پذیری انتخاب ایندکس های دلخواه در کوئری گرفته شده است. برای مثال فرض کنیم بخواهیم در کوئری گرفته شده، فقط ایندکس های زوج را برگردانیم:
int[] nums = { 4, 1, 7, 5, 9, 6, 2 }; var query = nums.Where((n,index) => n > 4 && index % 2 == 0).Select(n => n.ToString()); foreach (string s in query) Console.Write(s);
پارامتر دومی که داخل متد where با نام index تعریف شده، نشان دهنده ایندکس مربوط به هر یک از عناصر نتیجه برگردانده شده از شرط قبل از شر index است. در ایجاد تمامی ایندکس های زوج برگردانده می شوند. متدهایی که در نوشتن کوئری استفاده می شوند پارامتر های ورودی دیگری نیز دارند که برای آشنایی با آنها می توانید از MSDN استفاده کنید. در بخش های بعدی مقاله با استفاده از این متدها بیشتر آشنا خواهیم شد.
اما امکان بعدی که در مورد آن می خواهیم بحث کنیم قابلیت استفاده از Anonymous Types در نوشتن کوئری ها است. همانطور که در بخش قبلی گفتیم Anonymous Types نوع هایی بی نام هستند که می توانند دارای یکیسری خصوصیات باشند.
در کوئری های LINQ میتوانیم تعیین کنیم که خروجی ما از نوع Anonymous Types باشند. به مثال زیر توجه کنید، می خواهیم خروجی کوئری نوعی که تنها حاوی خصوصیت FullName است باشد، FullName از ترکیب خصوصیات FirstName و LastName در شئ Person تشکیل شده است (در کد زیر از کلاس Person که در بخش اول ایجاد کردیم استفاده می کنیم):
List persons = new List() { new Person() { FirstName = "Ali", LastName = "Jamali", Age = 42 }, new Person() { FirstName = "Hosein", LastName = "Ahmadi", Age = 23 }, new Person() { FirstName = "Mohamamd", LastName = "Sadeghi", Age = 17 }, new Person() { FirstName = "Reza", LastName = "Jamali", Age = 24 }, new Person() { FirstName = "Nima", LastName = "Karami", Age = 18 } }; var query = from p in persons where p.Age > 20 select new { FullName = p.FirstName + " " + p.LastName }; foreach (var at in query) { Console.WriteLine(at.FullName); }
نوشتن کوئری بالا با استفاده از متدهای کوئری نیز امکان پذیر است:
.... var query = persons.Where(p => p.Age > 20) .Select(n => new { FullName = n.FirstName + " " + n.LastName }); ....
اما نحوه اجرای کوئری ها در LINQ به چه صورت است. وقتی شما داخل کد برنامه ای که نوشتید از کوئری LINQ استفاده کردید، هنگام اجرا، وقتی برنامه به کوئری LINQ می رسد، در اصل کوئری اجرا نمی شود، بلکه هنگامی کوئری اجرا می شود که از نتایج کوئری استفاده شود.
در مثال های بالا کوئری های نوشته شده زمانی اجرا می شوند که برنامه به دستور foreach می رسد. به همین دلیل به اجرای کوئری های LINQ در زبان انگلیسی Deferred-Query-Execution یا اجرای کوئری با تاخیر نیز می گویند. البته می توان کوئری ها را در همان خطی که کوئری نوشته شده است اجرا کرد.
این کار به استفاده از متد های ToList، ToArray، ToDictionary و ToLookup امکان پذیر است. این متد ها نتیجه کوئری را به یکی از مجموعه های List، Array، Dictionary و یا LookUp تبدیل می کنند. برای درک بهتر به مثال زیر دقت کنید:
List query = persons.Where(p => p.Age > 20).ToList(); foreach (var p in query) { Console.WriteLine(p.ToString()); }
اگر بخواهیم کوئری بالا را با استفاده از دستورات LINQ بنویسیم باید به صورت زیر عمل کنیم:
List query = (from p in persons where p.Age > 20 select p).ToList();
همانطور که مشاهده میکنید در کوئری بالا، بلافاصله نتیجه کوئری با استفاده از متد ToList داخل یک مجموعه List ریخته می شود و سپس با دستور foreach این لیست مورد پیمایش قرار می گیرد. دستور ToDictionary برای ایجاد یک Dictionary که حاوی یک کلید است استفاده می شود:
Dictionary<string, person=""> query = persons.Where(p => p.Age > 20).ToDictionary(n => n.FirstName); Console.WriteLine(query["Hosein"].ToString()); </string,>
هر یک از مباحثی که در بالا عنوان شد، در نوشتن کوئری های قدرتمند LINQ نقش دارند. در بخش های بعدی مقالات، با استفاده از مثال های گوناگون به صورت جزئی تر با هر یک از این مباحث آشنا خواهید شد. در بخش بعدی در مورد اپراتورهای استاندارد LINQ صحبت خواهیم کرد.
در این بخش درباره عملگرهای استانداردی که در LINQ برای ایجاد کوئری ها می تونیم از اونها استفاده کنیم صحبت خواهیم کرد. در بخش های قبلی با یکی از این عملگر ها به نام where آشنا شدیم که وظیفه فیل تر سازی اطلاعات در کوئری های LINQ رو داره. در این بخش به طور مفصل در باره تک تک این عملگرها بحث خواهیم کرد. کلا" دو دسته از عملگرهای استاندارد در LINQ وجود دارند، اپراتورهای استانداری که بر روش اشیاء ای از نوع اینترفیس <><> کد زیر مثالی در باره استفاده از عملگرهای استاندارد LINQ است:
string myStr = "This text is only for test"; string[] words = myStr.Split(' '); var query = from word in words group word.ToLower() by word.Length into gr orderby gr.Key select new { Length = gr.Key, Words = gr }; foreach (var obj in query) { Console.WriteLine(obj.Length); foreach (string word in obj.Words) { Console.WriteLine(word); } }
در کد بالا، ابتدا رشته ای تعریف شده و کلمات رشته داخل یک آرایه قرار میگیرند. سپس با یک کوئری LINQ کلمات به ترتیب طول مرتب شده و دسته بندی می شن، بعد از دسته بندی حروف با یک دستور foreach مقادیر خروجی نمایش داده می شن. عملگرهایی که در بالا استفاده شده است، یکی عملگر group by برای دسته بندی خروجی و یکی هم عملگر orderby برای مرتب سازی خروجی می باشد. خروجی کد بالا به صورت زیر خواهد بود:
is 3 for 4 this text only test
در ادامه به بررسی هر یک از عملگرهای LINQ خواهیم پرداخت. در کدهای نوشته شده، به هر دو روش نوشتن کوئری های LINQ و استفاده از Extention Method ها مثال ها را خواهیم آورد.
اولین دسته از عملگرهایی که بررسی خواهیم کردیم مربوط به مرتب سازی خروجی کوئری می باشد. این عملگرها عبارتند از:
در ادامه با ارائه یک مثال با هر یک از عملگر های بالا بیشتر آشنا خواهیم شد. (مثال هم به صورت کوئری های LINQ و هم با استفاده از متدهای موجود زده خواهند شد)
عملگر OrderBy :
int[] numbers = { 4, 1, 6, 5, 2, 8, 3, 7, 9 }; IEnumerable query = from n in numbers orderby n select n; IEnumerable query2 = numbers.OrderBy(n => n); // query using OrderBy extention method foreach (int number in query) Console.WriteLine(number);
خروجی کد بالا:
1 2 3 4 5 6 7 8 9
int[] numbers = { 4, 1, 6, 5, 2, 8, 3, 7, 9 }; IEnumerable query = from n in numbers orderby n descending select n; IEnumerable query2 = numbers.OrderByDescending(n => n); foreach (int number in query) Console.WriteLine(number);
خروجی کد بالا:
9 8 7 6 5 4 3 2 1
List persons = new List() { new Person() { FirstName = "Hosein", LastName = "Ahmadi", Age = 23 }, new Person() { FirstName = "Hamid", LastName = "Asgari", Age = 27 }, new Person() { FirstName = "Reza", LastName = "Karimi", Age = 42 }, new Person() { FirstName = "Mohammad", LastName = "Zamani", Age = 31 }, new Person() { FirstName = "Reza", LastName = "Ahmadi", Age = 26 }, new Person() { FirstName = "Ali", LastName = "Kiani", Age = 17 }, new Person() { FirstName = "Saman", LastName = "Skandari", Age = 25 }, new Person() { FirstName = "Karim", LastName = "Heydari", Age = 19 } }; var query = from p in persons orderby p.FirstName, p.Age select p; var query2 = persons.OrderBy(n => n.FirstName).ThenBy(n => n.Age); foreach (Person p in query2) { Console.WriteLine(p.ToString()); Console.WriteLine("--------------------"); }
خروجی کد بالا:
FirstName: Ali LastName: Kiani Age: 17 -------------------- FirstName: Hamid LastName: Asgari Age: 27 -------------------- FirstName: Hosein LastName: Ahmadi Age: 23 -------------------- FirstName: Karim LastName: Heydari Age: 19 -------------------- FirstName: Mohammad LastName: Zamani Age: 31 -------------------- FirstName: Reza LastName: Ahmadi Age: 26 -------------------- FirstName: Reza LastName: Karimi Age: 42 -------------------- FirstName: Saman LastName: Skandari Age: 25 --------------------
List persons = new List() { new Person() { FirstName = "Hosein", LastName = "Ahmadi", Age = 23 }, new Person() { FirstName = "Hamid", LastName = "Asgari", Age = 27 }, new Person() { FirstName = "Reza", LastName = "Karimi", Age = 42 }, new Person() { FirstName = "Mohammad", LastName = "Zamani", Age = 31 }, new Person() { FirstName = "Reza", LastName = "Ahmadi", Age = 26 }, new Person() { FirstName = "Ali", LastName = "Kiani", Age = 17 }, new Person() { FirstName = "Saman", LastName = "Skandari", Age = 25 }, new Person() { FirstName = "Karim", LastName = "Heydari", Age = 19 } }; var query = from p in persons orderby p.FirstName ascending, p.Age descending select p; var query2 = persons.OrderBy(n => n.FirstName).ThenByDescending(n => n.Age); foreach (Person p in query) { Console.WriteLine(p.ToString()); Console.WriteLine("--------------------"); }
int[] numbers = { 4, 1, 7, 5, 3, 8, 2, 9 }; List query = numbers.Reverse().ToList(); query.ForEach(n => Console.WriteLine(n));
خروجی کد بالا:
9 2 8 3 5 7 1 4
عملگرهایی که در بالا در موردشان صحبت کردیم برای مرتب سازی نتایج کوئری ها استفاده می شوند. در بخش بعدی در مورد عملگر های مجموعه ای (Set Operators) صحبت خواهیم کرد.
این عملگر ها جهت انجام عملیات هایی مانند اشتراک، اجتماع و ... بر روی مجموعه ها استفاده می شوند. عملگر های این بخش عبارتند از:
در زیر با ارئه مثال های گوناگون با کاربرد هر یک از عملگرها آشنا خواهیم شد.
int[] set1 = { 4, 1, 7, 5, 2, 8, 6, 4, 5, 1, 8, 9 }; var query = set1.Distinct(); foreach (int i in query) Console.WriteLine(i);
خروجی کد بالا:
4 1 7 5 2 8 6 9
int[] set1 = { 3, 6, 1, 2, 7, 8 }; int[] set2 = { 5, 2, 3, 7, 6 }; var query = set1.Except(set2); foreach (int i in query) Console.WriteLine(i);
خروجی کد بالا:
1 8
int[] set1 = { 3, 6, 1, 2, 7, 8 }; int[] set2 = { 5, 2, 3, 7, 6 }; var query = set1.Intersect(set2); foreach (int i in query) Console.WriteLine(i);
خروجی کد بالا:
3 6 2 7
int[] set1 = { 3, 6, 1, 2, 7, 8 }; int[] set2 = { 5, 2, 3, 7, 6 }; var query = set1.Union(set2); foreach (int i in query) Console.WriteLine(i);
خروجی کد بالا:
3 6 1 2 7 8 5
عملگرهای بعدی که در مورد آنها صحبت خواهیم کرد برای فیل تر کردن اطلاعات به کار می روند. دو نوع عملگر برای اینکار وجود دارد:
برای آشنایی بیشتر با این متد مثالی با استفاده از یک ArrayList خواهیم زد، ArrayList شئ ایست که می تواند هر نوع داده ای را در خود ذخیره کند. حال اگر ما بخواهیم از داخل یک ArrayList که حاوی چندین نوع می باشد، یک نوع خاص را استخراج کنیم می توانیم از این عملگر استفاده کنیم:
ArrayList list = new ArrayList() { 2, 7, "A", 'C', 2.7, 4, "D" }; IEnumerable query1 = list.OfType(); IEnumerable query2 = list.OfType(); foreach (int i in query1) Console.WriteLine(i); foreach (string s in query2) Console.WriteLine(s);
خروجی کد بالا:
2 7 4 A D
این عملگر به چند صورت قابل استفاده است که در این بخش تنها روش ساده آن را توضیح داده و در قسمت های بعدی روش های پیشرفته تر استفاده از این متد را توضیح خواهیم داد.
string[] s = { "Hello LINQ!", "Hello C#!", "LINQ is very flexible","Hello Generics!"}; IEnumerable query = from w in s where w.ToLower().Contains("linq") select w; IEnumerable query2 = s.Where(str => str.ToLower().Contains("linq")); foreach (string linq in query) Console.WriteLine(linq);
خروجی کد بالا:
Hello LINQ! LINQ is very flexible
در این مقاله در مورد سه گروه از عملگر های استاندارد صحبت کردیم، عملگرهای مرتب سازی، عملگرهای مجموعه ای و عملگرهای فیل تر سازی. به دلیل حجم زیاد این عملگرها، در طول چند بخش در مورد عملگرها صحبت خواهم کرد. در بخش بعدی در مورد عملگرهای Quantifier، عملگرهای Projection و عملگره
در بخش چهارم درباره برخی از این عملگرها صحبت کردیم. عملگرهایی که در بخش پنجم مورد بررسی قرار خواهند گرفت به شرح زیر می باشند:
زمانی که تعدادی یا کل المان های موجود در مجموعه، در شرایط تعیین شده صدق کنند این عملگرها مقدار Boolean را بر می گردانند. عملگر های این بخش عبارتند از:
این عملگر زمانی مقدار true بر می گرداند که تمامی اعضای مجموعه در شرایط تعیین شده صدق کنند. به مثال زیر توجه کنید:
string[] strArray1 = { "a", "b", "d", "t", "l", "o" }; string[] strArray2 = { "a", "a", "a", "a", "a", "a" }; bool flag1 = strArray1.All(n => n.Equals("a")); bool flag2 = strArray1.All(n => n.Equals("a")); Console.WriteLine(flag1); Console.WriteLine(flag2);
کد بالا مقادیر false و true را به ترتیب چاپ می کند. زیرا لیست اول همه عناصر مساوی کاراکتر a نیستند، ولی لیست دوم همه عناصر برابر کاراکتر a هستند.
این عملگر زمانی مقدار true بر می گرداند که حداقل یکی از المان های مجموعه دارای شرایط تعیین شده باشد. این عملگر به دو صورت به کار گرفته می شود:
string[] strArray = { "a", "b", "d", "t", "l", "o" }; bool flag1 = strArray.Any(n=>n.Equals("p")); bool flag2 = strArray.Any(n=>n.Equals("l")); bool flag3 = strArray.Any(); Console.WriteLine(flag1); Console.WriteLine(flag2);
در بالا خروجی ابتدا false، سپس true و در آخر true می باشد.
این عملگر تعیین می کند که لیست دارای عضوی برابر با عضو تعیین شده می باشد یا خیر. این عملگر به جای یک شرط برای پارامتر ورودی، یک مقدار از نوع المان های لیست می گیرد. به مثال زیر توجه کنید:
string[] strArray = { "a", "b", "d", "t", "l", "o" }; bool flag1 = strArray.Contains("b"); Console.WriteLine(flag1);
کد بالا مقدار True را برای خروجی چاپ می کند. این عملگر یک یک پارامتر از نوع IEqualityComarer نیز می گیرد که جهت مقایسه المان ها مورد استفاده قرار می گیرد.
عملگر های این بخش برای تعیین نوع خروجی و تبدیل آن به نوع مورد نظر استفاده می شوند. از عملگر Select تا این لحظه چندین بار استفاده کردیم و با آن آشنایی داریم، اما عملگر SelectMany! از این عملگر برای استفاده چندین from در یک query استفاده می شود. به مثال زیر دقت کنید (به کلاس Person یک آرایه با نام Friends از نوع string اضافه شده است)
List persons = new List() { new Person() { FirstName = "Ali", LastName = "Jamali", Age = 42, Friends = new string[] { "Hosein","Reza"} }, new Person() { FirstName = "Hosein", LastName = "Ahmadi", Age = 23,Friends = new string[] { "Mohammad","Nima"} }, new Person() { FirstName = "Mohamamd", LastName = "Sadeghi", Age = 17,Friends = new string[] { "Reza","Nima"} }, new Person() { FirstName = "Reza", LastName = "Jamali", Age = 24,Friends = new string[] { "Hosein","Mohammad"} }, new Person() { FirstName = "Nima", LastName = "Karami", Age = 18,Friends = new string[] { "Reza","Hosein"} } }; var query1 = from p1 in persons from p2 in p1.Friends where p1.Age > 20 select p2; var query2 = persons.Where(n => n.Age > 20).SelectMany(friend => friend.Friends); query2.ToList().ForEach(n => Console.WriteLine(n));
هر دو کوئری نوشته شده در بالا، یک کار را انجام می دهد. در کوئری بالا، ابتدا تمام اشخاصی که سنشان بالای 20 سال است انتخاب شده و سپس لیست دوستان آنها به صورت یک مجموعه آرایه قرار داده شده است. یکی از مهمترین کاربرد های SelectMany زمانی است که شما می خواهید لیستی را که داخل هر یک از المان های نتیجه کوئری قرار دارد با یکدیگر Merge کنید.
از این عملگر ها برای تقسیم بندی خروجی کوئری استفاده می شود. عملگر های این دسته عبارتند از:
با استفاده از این عملگر می توان تعداد رکورد دلخواه در ابتدای نتیجه کوئری را در نظر نگرفت. مثال:
int[] numbers = { 4, 6, 9, 3, 1, 2, 7, 5, 10, 8, 21, 7 }; var query = numbers.Skip(2); query.ToList().ForEach(n => Console.WriteLine(n));
در خروجی بالا، دو المان ابتدایی کوئری در نظر گرفته نشده و المان سوم خروجی کوئری بر روی خروجی ظاهر می گردد.
این عملگر المان ها را تا زمانی که شرط تعیین شده بر قرار باشد در خروجی در نظر نمی گیرد:
int[] numbers = { 4, 6, 9, 3, 1, 2, 7, 5, 10, 8, 21, 7 }; var query = numbers.OrderBy(n => n).SkipWhile(n => n < 10); query.ToList().ForEach(n => Console.WriteLine(n));
در کد بالا ابتدا لیست به ترتیب صعودی مرتب شده و سپس اعداد تا زمانی که کوچکتر از 10 باشند در خروجی در نظر گرفته نمی شوند و اعداد 10، 8، 21 و 7 در خروجی چاپ خواهند شد.
این عملگر ها عکس عملگر های Skip و SkipWhile عمل میکنند. یعنی المان های تا محل تعیین شده را بر می گردانند. مثال:
int[] numbers = { 4, 6, 9, 3, 1, 2, 7, 5, 10, 8, 21, 7 }; var query = numbers.Take(2); query.ToList().ForEach(n => Console.WriteLine(n));
کد بالا 2 المان ابتدای لیست را بر می گرداند.
nt[] numbers = { 4, 6, 9, 3, 1, 2, 7, 5, 10, 8, 21, 7 }; var query = numbers.OrderBy(n => n).TakeWhile(n => n < 10); query.ToList().ForEach(n => Console.WriteLine(n));
کد بالا ابتدا لیست را مرتب کرده و تا زمانی که اعداد کوچکتر از 10 باشند در خروجی نمایش داده می شوند.
در ادامه به بررسی عملگر های زیر خواهیم پرداخت:
افرادی که با کوئری های SQL کار کرده باشند با مفهوم Join آشنایی دارند. عملگر های Join جهت ادغام دو لیست مبتنی بر یک کلید خاص استفاده می شود که شبیه به عملیات inner join در SQL می باشد. برای مثال، فرض کنیم دو لیست داریم، داخل یکی از لیست ها، مشتریان و داخل لیست دیگر سفارشات مربوط به هر یک از مشتریان را نگهداری می کنیم.
حال اگر بخواهیم این دولیست با یکدیگر ادغام شده و هر سفارش به همراه نام سفارش دهنده نمایش داده شود از Join استفاده می کنیم. برای آشنایی بیشتر به یک مثال توجه کنید، فرض کنیم می خواهیم اطلاعات مربوط به سفارشات مشتریان را ذخیره کنیم، برای اینکار دو کلاس با نام های زیر در برنامه تعریف می کنیم:
class Customer { public int CustomerID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } class Order { public int CustomerID { get; set; } public int OrderID { get; set; } public string OrderDescription { get; set; } }
کلاس Customer اطلاعات مشتری را نگهداری کرده و کلاس Order اطلاعات سفارشرا نگهداری می کند، کلاس سفارشات یک خصوصیت با نام CustomerID دارد که برای ارتباط بین مشتری و سفارش استفاده می شود که همان فیلد کلید است (طراحی انجام شده برای این مثال با توجه به اصول شئ گرایی صحیح نمی باشد
اما برای نزدیکی ساختار لیست ها به جداول بانک اطلاعاتی طراحی به این صورت انجام شده است)، در ادامه می خواهیم یک کوئری اجرا کنیم که اطلاعات را به صورت نام مشتری، کد سفارش و توضیحات سفارش برای ما برگرداند، در این قسمت ما می توانیم از عملگر های Join استفاده کنیم:
List customers = new List { new Customer() { CustomerID = 1, FirstName = "Mohammad", LastName = "Sadeghi" }, new Customer() { CustomerID = 2, FirstName = "Ali", LastName = "Rezaee" } }; List orders = new List { new Order() { CustomerID = 1, OrderID = 1, OrderDescription = "Order1" }, new Order() { CustomerID = 1, OrderID = 2, OrderDescription = "Order2" }, new Order() { CustomerID = 1, OrderID = 3, OrderDescription = "Order3" }, new Order() { CustomerID = 2, OrderID = 1, OrderDescription = "Order1" }, new Order() { CustomerID = 2, OrderID = 2, OrderDescription = "Order2" }, }; var joinQuery = from c in customers join j in orders on c.CustomerID equals j.CustomerID into orderDetails from d in orderDetails select new { CustomerName = c.FirstName + " " + c.LastName, OrderID = d.OrderID, OrderDesc = d.OrderDescription }; foreach (var item in joinQuery) { Console.WriteLine(string.Format("Name:{0} OrderID:{1} OrderDesc:{2}", item.CustomerName, item.OrderID, item.OrderDesc)); } Console.ReadKey();
همانطور که در کد بالا مشاهده می کنید در کوئری از عملگر join برای ادغام سفارشات با اطلاعات مشتری استفاده شده است، ساختاری عملگر های Join به صورت زیر است:
from in join in on equals from r in selelct
در بخش بالا خط سوم تعیین کننده شرط ما برای ادغام دو لیست می باشد که این مقایسه باید بر روی کلید مورد نظر انجام شود. امکان انجام مقایسه بین دو کلید و یا بیشتر نیز وجود دارد، برای مقایسه بین تعداد فیلد های بیشتر باید به صورت زیر عمل کرد:
from in join in on new {,, ...} equals {,,...} from r in select
با استفاده از الگوی بالا می توان از چندین فیلد کلید برای نوشتن کوئری استفاده کرد. برای روشن تر شدن یک مثال دیگر را با هم بررسی می کنیم، فرض کنیم داخل کلاس سفارش می خواهیم اطلاعات کالای سفارش داده شده را نیز نگهداری کنیم، برای این منظور کلاسی با نام Product تعریف می کنیم و کلاس Order را به صورت زیر تغییر می دهیم:
class Order { public int CustomerID { get; set; } public int OrderID { get; set; } public int ProductID { get; set; } public string OrderDescription { get; set; } } class Product { public int ProductID { get; set; } public string ProductName { get; set; } public decimal Price { get; set; } }
همانطور که مشاهده می کنید، در کلاس Order خصوصیتی با نام ProductID تعریف شده که تعیین کننده کد کالای سفارش داده شده می باشد. حالا می خواهیم از لیست سفارشات مشتریان کوئری بنویسیم که نام مشتری، به همراه کد سفارش، نام کالا و قیمت کالا را را نمایش دهد، در این قسمت باید از عملگر join استفاده کنیم که از چندین فیلد برای کلید استفاده می کند:
List customers = new List { new Customer() { CustomerID = 1, FirstName = "Mohammad", LastName = "Sadeghi" }, new Customer() { CustomerID = 2, FirstName = "Ali", LastName = "Rezaee" } }; List products = new List { new Product() { ProductID = 1, ProductName = "Product1", Price = 1000 }, new Product() { ProductID = 2, ProductName = "Product2", Price = 2500 }, new Product() { ProductID = 3, ProductName = "Product3", Price = 4000 }, new Product() { ProductID = 4, ProductName = "Product4", Price = 3700 }, }; List orders = new List { new Order() { CustomerID = 1, OrderID = 1, ProductID = 1, OrderDescription = "Order1" }, new Order() { CustomerID = 1, OrderID = 2, ProductID = 3, OrderDescription = "Order2" }, new Order() { CustomerID = 1, OrderID = 3, ProductID = 2, OrderDescription = "Order3" }, new Order() { CustomerID = 2, OrderID = 1, ProductID = 1, OrderDescription = "Order1" }, new Order() { CustomerID = 2, OrderID = 2, ProductID = 4, OrderDescription = "Order2" }, }; var joinQuery = from c in customers from p in products oin j in orders on new { c.CustomerID, p.ProductID } equals new { j.CustomerID, j.ProductID } into orderDetails from d in orderDetails select new { PersonName = c.FirstName + " " + c.LastName, OrderID = d.OrderID, ProductName = p.ProductName, Price = p.Price, OrderDesc = d.OrderDescription };
همانطور که در کوئری بالا مشاهده می کنید از ترکیب دو کلید CustomerID و ProductID برای انجام عملیات Join استفاده شده که این قابلیت به ما این امکان رو میده تا کوئری های پیچیده ای را بنویسیم. از عملگر های Join در Method Based Query هم می توان استفاده کرد. برای مثال، اولین کوئری نوشته شده در این بخش را می توان به صورت زیر نیز نوشت:
var query = customers.Join(orders, n => n.CustomerID, n => n.CustomerID, (l1, l2) => new { PersonName = l1.FirstName + " " + l1.LastName, OrderID = l2.OrderID, OrderDesc = l2.OrderDescription });
نوع دیگری از عملگر Join، استفاده از GroupJoin است، این عملگر برای ادغام یک لیست با مقادیر متناظر در لیست دیگر استفاده می شود، این عملگر برای کار با ساختاری های Hierarchical استفاده می شود. برای آشنایی بیشتر، فرض کنیم لیستی داریم شامل نام کارخانه های خودرو سازی و لیستی دیگر که نام خودرو به همراه شرکت تولید کننده را نگهداری می کند، حال می خواهیم یک کوئری بنویسیم که نتیجه آن نام شرکت و لیستی از خودرو های آن شرکت می باشد، به کد زیر توجه کنید:
List companies = new List() { new Company() { Name = "IranKhodro" }, new Company() { Name = "Siapa" }, new Company() { Name = "Hyundai" } }; List cars = new List() { new Car() { CompanyName = "IranKhodro", Name = "Peikan" }, new Car() { CompanyName = "IranKhodro", Name = "P 405" }, new Car() { CompanyName = "IranKhodro", Name = "P Pars" }, new Car() { CompanyName = "Siapa" , Name = "Pride" }, new Car() { CompanyName = "Siapa" , Name = "Xantia" }, new Car() { CompanyName = "Hyundai" , Name = "Azera" }, new Car() { CompanyName = "Hyundai" , Name = "Sonata" }, new Car() { CompanyName = "Hyundai" , Name = "Santafeh" }, new Car() { CompanyName = "Hyundai" , Name = "Genesis" } }; var query = companies.GroupJoin(cars, company => company.Name, car => car.CompanyName, (company, car) => new { company.Name, Cars = car.Select(n => n.Name) }); foreach (var co in query) { Console.WriteLine(co.Name); foreach (var car in co.Cars) Console.WriteLine(" {0}", car); } Console.ReadKey(); class Company { public string Name { get; set; } } class Car { public string CompanyName { get; set; } public string Name { get; set; } }
با اجرای کد بالا، خروجی زیر بر روی صفحه Console ظاهر خواهد شد:
IranKhodro Peikan P 405 P Pars Siapa Pride Xantia Hyundai Azera Sonata Santafeh Genesis
بیشترین کاربرد عملیات های Join در نوشتن کوئری ها بر روی بانک های اطلاعاتی می باشد.
List orderHistory = new List { new OrderHistory() { OrderDate = "1388/01/25", OrderPrice = 125000 }, new OrderHistory() { OrderDate = "1388/01/25", OrderPrice = 18500 }, new OrderHistory() { OrderDate = "1388/01/12", OrderPrice = 2500000 }, new OrderHistory() { OrderDate = "1388/02/12", OrderPrice = 17500 }, new OrderHistory() { OrderDate = "1388/01/17", OrderPrice = 41000 }, new OrderHistory() { OrderDate = "1388/01/28", OrderPrice = 250000 }, new OrderHistory() { OrderDate = "1388/02/06", OrderPrice = 60000 }, new OrderHistory() { OrderDate = "1388/02/08", OrderPrice = 281000 }, new OrderHistory() { OrderDate = "1388/03/02", OrderPrice = 236000 }, new OrderHistory() { OrderDate = "1388/03/02", OrderPrice = 782400 }, }; var query = from e in orderHistory group e by e.OrderDate into dateGrouped orderby dateGrouped.Key select new { Date = dateGrouped.Key, TotalPrice = dateGrouped.Sum(n => n.OrderPrice) }; foreach (var item in query) { Console.WriteLine("{0} {1}", item.Date, item.TotalPrice); } Console.ReadKey();
با اجرای این دستور خروجی زیر چاپ خواهد شد:
1388/01/12 2500000 1388/01/17 41000 1388/01/25 143500 1388/01/28 250000 1388/02/06 60000 1388/02/08 281000 1388/02/12 17500 1388/03/02 1018400
List names = new List { "Ali","Mohammad","Mahdi","Abbas","Behzad","Behrooz ","Naser" }; /* 1 */ Console.WriteLine(names.Where(n => n.StartsWith("D")).First()); /* 2 */ Console.WriteLine(names.Where(n => n.StartsWith("D")).DefaultIfEmpty().First());
با اچرای دستور شماره 1، به دلیل اینکه لیست مورد نظر خالی می باشد، یک Exception ایجاد خواهد شد، زیرا لیست خالی بوده و متد First هیچ عنصری را در لیست نخواهد یافت، ولی با اجرای دستور شماره 2، به دلیل اینکه از متد DefaultIfEmpty استفاده شده است، به دلیل اینکه نتیجه کوئری یک لیست خالی می باشد، یک لیست که تنها دارای یک مقدار و آنهم مقدار پیش فرض نوع string می باشد، بر خواهد گرداند که متد First آن مقدار را بر می گرداند. از این متد در مواقعی که نتیجه کوئری قابل پیش بینی نیست می توان استفاده کرد.
IEnumerable result = Enumerable.Empty();
IEnumerable numbers = Enumerable.Range(1, 10);
IEnumerable list = Enumerable.Repeat("Hi", 10);
این متد لیستی حاوی 10 عنصر با مقدار "Hi" بر می گرداند.
string[] list1 = { "A", "D", "B", "H", "K" }; string[] list2 = { "D", "Q", "H", "A", "B" }; string[] list3 = { "A", "D", "B", "H", "K" }; Console.WriteLine(list1.SequenceEqual(list2)); Console.WriteLine(list1.SequenceEqual(list3));
اجرای قطعه کد بالا، ابتدا مقدار False و سپس مقدار True را بر می گرداند.
string[] list1 = { "A", "D", "B", "H", "K" }; Console.WriteLine(list1.ElementAt(2)); // out B
string[] list1 = { "A", "D", "B", "H", "K" }; string element = list1.Single(n => n.Equals("B"));
در این بخش از مقاله به بررسی تعدادی دیگر از عملگر های LINQ پرداختیم، در بخش بعدی سایر عملگر های Converting، Concatenation و Aggregation را بررسی خواهیم کرد که مباحث مربوط به عملگر های LINQ به پایان برسد.
در این بخش، به بررسی آخرین دسته از عملگر های استاندارد LINQ خواهیم پرداخت که با اتمام این بخش صحبت بر روی عملگر های LINQ به پایان خواهد رسید. عملگر هایی که در این بخش در مورد آنها صحبت خواهیم کرد عبارتند از:
از این عملگر ها برای تبدیل اشیاء به نوع های دیگر مورد استفاده قرار می گیرند. این قسمت شامل هشت عملگر می باشد که در زیر به بررسی یکایک این عملگر ها خواهیم پرداخت:
System.Collections.ArrayList list = new System.Collections.ArrayList { 1, 8, 3, 6, 4, 2, 3 }; var query = list.Cast().Select(n => n); foreach (int i in query) Console.WriteLine(i);
در مثال بالا یک ArrayList تعریف شده است که با عملگر Case عناصر آن را به نوع int تبدیل کردیم.
System.Collections.ArrayList list = new System.Collections.ArrayList { 1, "test", "A", 6, 'c', "D", 3 }; var numbers = list.OfType(); var characters = list.OfType(); var strings = list.OfType();
List numbers = new List { 4, 8, 9, 6, 3, 1, 2, 8 }; int[] array = numbers.ToArray();
List customers = new List { new Customer() { CustomerID = 1, FirstName = "Mohammad", LastName = "Sadeghi" }, new Customer() { CustomerID = 2, FirstName = "Ali", LastName = "Rezaee" }, new Customer() { CustomerID = 3, FirstName = "Hosein", LastName = "Mohammad" } }; var dictionary = customers.ToDictionary(n => n.CustomerID); Console.WriteLine(dictionary[2].FirstName);
int[] numbers = { 7, 9, 8, 2, 3, 1, 6, 5 }; numbers.ToList().ForEach(n => Console.WriteLine(n));
List citites = new List { new CityInfo() { Province = "Tehran", CitiyName = "Karaj" }, new CityInfo() { Province = "Tehran", CitiyName = "Ray" }, new CityInfo() { Province = "Tehran", CitiyName = "Hashtgerd" }, new CityInfo() { Province = "Mazandaran", CitiyName = "Babolsar" }, new CityInfo() { Province = "Tehran", CitiyName = "Noor" }, new CityInfo() { Province = "Tehran", CitiyName = "Nowshahr" } }; var query = citites.ToLookup(n => n.Province); foreach (CityInfo info in query["Tehran"]) Console.WriteLine(info.CitiyName);
این بخش دارای یک عملگر با نام Concate می باشد که دو لیست را با یکدیگر ادغام می کند:
int[] list1 = { 1, 2, 3, 4, 5 }; int[] list2 = { 5, 6, 7, 8, 9, 10 }; IEnumerable concated = list1.Concat(list2); foreach (int i in concated) Console.WriteLine(i);
از این عملگر ها برای استخراج یک مقدار از مجموعه داده شده استفاده می شود، عملگر های این مجموعه به شرح زیر می باشند:
عملگر Average : میانگین مقادیر یک لیست را به دست می آورد:
int[] list1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Console.WriteLine(list1.Average());
int[] list1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Console.WriteLine(list1.Count());
int[] list1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Console.WriteLine(list1.Max());
int[] list1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Console.WriteLine(list1.Min());
int[] list1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Console.WriteLine(list1.Sum());
نکته :: برای متد های ذکر شده می توان یک تابع نیز به عنوان ورودی تعیین کرد که انتخاب کننده فیلد مورد نظر برای محاسبه می باشد، مثال:
List orderHistory = new List { new OrdersHistory() { OrderDate = "1388/01/25", OrderPrice = 125000 }, new OrdersHistory() { OrderDate = "1388/01/25", OrderPrice = 18500 }, new OrdersHistory() { OrderDate = "1388/01/12", OrderPrice = 2500000 }, new OrdersHistory() { OrderDate = "1388/02/12", OrderPrice = 17500 }, new OrdersHistory() { OrderDate = "1388/01/17", OrderPrice = 41000 }, new OrdersHistory() { OrderDate = "1388/01/28", OrderPrice = 250000 }, new OrdersHistory() { OrderDate = "1388/02/06", OrderPrice = 60000 }, new OrdersHistory() { OrderDate = "1388/02/08", OrderPrice = 281000 }, new OrdersHistory() { OrderDate = "1388/03/02", OrderPrice = 236000 }, new OrdersHistory() { OrderDate = "1388/03/02", OrderPrice = 782400 }, }; Console.WriteLine(orderHistory.Sum(n=> n.OrderPrice)); Console.ReadKey();
int[] list1 = { 2, 3, 4, 5, 6, 7 }; Console.WriteLine(list1.Aggregate((current, next) => { return current + next; }));
در سری مقالات LINQ سعی کردم که مفاهیم ابتدایی رو خدمت دوستان عرض کنم. با آشنایی با این موارد میتونیم LINQ رو مباحث دیگری مانند LINQ 2 XML و LINQ 2 Entities بسط بدیم. برای آشنایی بیشتر با مفاهیم مربوط به LINQ 2 Entities و Entity Framework میتونید به سری آموزشی که توسط بنده در سایت قرار داده شده مراجعه کنید. به امید خدا در قسمت های بعدی سعی می کنم در مورد LINQ 2 XML مطالب بیشتری رو خدمت شما تقدیم کنم.
بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب
حسین احمدی ، بنیانگذار TOSINSO ، توسعه دهنده وب و برنامه نویس ، بیش از 12 سال سابقه فعالیت حرفه ای در سطح کلان ، مشاور ، مدیر پروژه و مدرس نهادهای مالی و اعتباری ، تخصص در پلتفرم دات نت و زبان سی شارپ ، طراحی و توسعه وب ، امنیت نرم افزار ، تحلیل سیستم های اطلاعاتی و داده کاوی ...
زمان پاسخ گویی روز های شنبه الی چهارشنبه ساعت 9 الی 18
فقط به موضوعات مربوط به محصولات آموزشی و فروش پاسخ داده می شود