عملگرهای استاندارد پرس و جو
در یک طبقه بندی کلی، عملگرهای پرس و جو بر اساس ورودی و خروجی آنها به سه دسته تقسیم میشوند:
1- نتیجهی توالی ورودی، بصورت یک توالی، به خروجی ارسال میشود.
2- نتیجهی توالی ورودی، بصورت یک عنصر یکتا و واحد به خروجی ارسال میشود.
3- اثری از ورودی در توالی خروجی وجود ندارد (این عملگرها عناصر خودشان را تولید میکنند).
دستهی آخر شاید کمی عجیب به نطر برسد. این عملگرها هیچ توالی ورودی را دریافت نمیکنند. مثلا میتوان از طریق این عملگرها، یک توالی از اعداد صحیح را تولید کرد.
تقسیم بندی عملگرهای پرس و جو بر اساس عملکرد به صورت زیر میباشد :
در این مطلب عملگرهای محدود کننده، بازتابی و جداکننده، بررسی خواهند شد. بعد از معرفی هر عملگر، معادل عبارتهای پرس و جوی آنها نیز معرفی خواهند شد.
عملگرهای محدود کننده (Restriction Operators)
این عملگر، عناصری را به خروجی ارسال میکند که با گزارهی (Predicate) تعریف شده مطابقت داشته باشند.
نکته : گزاره (Predicate) تابعی است که اگر شرط آن تامین شود، مقدار true و در غیر اینصورت مقدار false را باز میگرداند.
مثال :
در کد فوق از عملگر where استفاده شده است. گزارهی (x=>x.Calories>=200) به ازای هر غذایی که کالری آن مساوی یا بزرگتر از 200 باشد، مقدار true را باز میگرداند.
خروجی کد بالا:
عملگر where امضای دیگری دارد که اندیس عنصر ورودی توالی را نیز میپذیرد. در مثال قبل، اندیس Sugar برابر 0 و اندیس Butter برابر 4 است. پرس و جوی زیر خروجی مشابه مثال قبل را تولید میکند.
گزاره نوشته شده در این پرس و جو از نوع <Func<Ingredient,int,bool خواهد بود و پارامتر int، اندیس عنصر در توالی ورودی میباشد.
پیاده سازی توسط عبارتهای پرس و جو
در روش عبارتهای پرس و جو، کلمهی کلیدی where بههمراه یک عبارت منطقی در پرس و جو ظاهر میشود:
عملگرهای بازتاب (Projection Operators)
عملگرهای پرس و جوی بازتابی، یک توالی ورودی را دریافت و با تبدیل عناصر آنها، یک توالی خروجی را تولید میکنند.
Select
عملگر پرس و جوی select هر عنصر توالی ورودی را به یک عنصر در توالی خروجی تبدیل میکند. تعداد عناصر ورودی و خروجی در این حالت یکسان میباشند.
پرس و جوی زیر عناصر توالی ورودی Ingredient را به عناصر رشتهای در توالی خروجی بازتاب میکند. عبارت Lambda تعریف شده، نحوهی بازتاب عناصر را مشخص میکند (هر عنصر ingredient به یک عنصر رشتهای بازتاب میشود):
میتوان توالی خروجی با عناصر صحیح را نیز تولید کرد:
در عملیات بازتاب میتوان یک شیء جدید را در توالی خروجی ایجاد کرد. در کد زیر عناصر Ingredient به یک عنصر جدید از نوع IngredientNameAndLenght بازتاب شده است.
پرس و جوی بالا را میتوان به شکل نوعهای بی نام نیز بازنویسی کرد. باید دقت شود که نوع بازگشتی این پرس و جو باید از نوع var باشد.
خروجی کد بالا به شکل زیر است :
پیاده سازی توسط عبارتهای پرس و جو
کلمهی کلیدی select در عبارتهای پرس و جو، به شکل زیر استفاده میشود:
SelectMany
برعکس دستور select که به ازای هر عنصر در توالی ورودی، یک عنصر را در توالی خروجی بازتاب میکرد، دستور SelectMany ممکن است تعداد عناصر کمتر و یا بیشتری را در توالی خروجی بازتاب کند (انتخاب مقادیر یک مجموعه از مجموعهی دیگر).
عبارت Lambda نوشته شده در عملگر Select، یک مقدار را باز میگرداند. اما عبارت Lambda نوشته شده در عملگر SelectMany، یک توالی فرزند (Child Sequence) را ایجاد میکند. توالی فرزند ممکن است حاوی تعداد مختلفی از عناصر به ازای هر عنصر در توالی ورودی باشد.
در مثال زیر عبارت Lambda یک توالی فرزند از کاراکترها ایجاد میکند (یک کاراکتر به ازای هر حرف از هر عنصر توالی ورودی). بهطور مثال عنصر ورودی Sugar، پس از پردازش توسط عبارت Lambda، یک توالی فرزند با 5 عنصر 's','u','g','e','r' فراهم میکند. هر رشتهی در توالی Ingredient میتواند تعداد حروف متفاوتی داشته باشد. در نتیجه عبارت Lambda، توالیهای فرزندی با طولهای مختلف ایجاد میکند.
مثال:
خروجی مثال بالا :
پیاده سازی توسط عبارتهای پرس و جو
در روش عبارتهای پرس و جو یک عبارت (clause) اضافی from برای تولید یک توالی فرزند به کار برده میشود. خروجی کد زیر مشابه کد قبلی است:
عملگرهای جداکننده (Partitioning Operators)
عملگرهای جداکننده، یک توالی ورودی را دریافت و آنها را از هم جدا میکنند.
Take
عملگر Takeیک توالی ورودی را دریافت کرده و تعداد مشخصی از توالی را باز میگرداند.
مثال: عملگر Take، سه عضو اول توالی Ingredient را باز میگرداند:
خروجی کد بالا :
همچون سایر عملگرهای پرس و جو، عملگر Take هم میتواند بصورت زنجیروار استفاده شود. در مثال زیر ابتدا عملگر Where برای محدود کردن عناصر با شرطی خاص و سپس عملگر Take برای جدا کردن عناصر حاصل از نتیجهی مرحله قبل مورد استفاده قرار گرفته است:
خروجی کد بالا :
پیاده سازی توسط عبارتهای پرس و جو
کلمهی کلیدی (Key word) جایگزینی برای عملگر Take وجود ندارد، ولی میتوان با ترکیب دو روش نوشتن پرس و جو، خروجی مورد نظر را تولید کرد:
عملگر TakeWhile بر عکس عملگر Take تعداد مشخصی را باز میگرداند . این عملگر تا زمانی که گزاره با عناصر مطابقت داشته باشد، اجرا میشود و در غیر اینصورت خاتمه پیدا میکند.
کد زیر تا زمانی که خصوصیت Calorie توالی ورودی بزرگتر و مساوی 100 باشد، عناصر را جدا میکند.
خروجی کد بالا :
همانطور که مشاهده میکنید، وقتی عملگر TakeWhile به عنصری میرسد که گزارهی آن را نقض میکند، متوقف میشود (در اینجا Flour). در حالی که ممکن است عناصری بعد از Flour وجود داشته باشند که با گزارهی TakeWhile تطابق داشته باشند.
پیاده سازی توسط عبارتهای پرس و جو
برای این عملگر هم کلمهی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو نتیجهی دلخواه حاصل میشود.
Skip
این عملگر تعداد مشخصی از عناصر را از ابتدای توالی نادیده گرفته و باقی عناصر را باز میگرداند.
کد زیر سه عضو اول توالی را نادیده گرفته و مابقی را باز میگرداند:
خروجی کد بالا :
پیاده سازی توسط عبارتهای پرس و جو
برای این عملگر هم کلمهی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو، نتیجهی دلخواه حاصل میشود.
با ترکیب عملگر Take و Skip میتوان اطلاعات را بهصورت صفحه بندی به کاربر ارائه کرد. مثال زیر این حالت را نشان میدهد.
خروجی کد بالا :
عملگر SkipWhile، مثل عملگر TakeWhile، از یک گزاره برای ارزیابی عناصر توالی استفاده میکند. این عملگر تا زمانیکه عناصر توالی، گزاره را نقض نکنند، عناصر را نادیده میگیرد.
مثال:
خروجی کد بالا:
در یک طبقه بندی کلی، عملگرهای پرس و جو بر اساس ورودی و خروجی آنها به سه دسته تقسیم میشوند:
1- نتیجهی توالی ورودی، بصورت یک توالی، به خروجی ارسال میشود.
2- نتیجهی توالی ورودی، بصورت یک عنصر یکتا و واحد به خروجی ارسال میشود.
3- اثری از ورودی در توالی خروجی وجود ندارد (این عملگرها عناصر خودشان را تولید میکنند).
دستهی آخر شاید کمی عجیب به نطر برسد. این عملگرها هیچ توالی ورودی را دریافت نمیکنند. مثلا میتوان از طریق این عملگرها، یک توالی از اعداد صحیح را تولید کرد.
تقسیم بندی عملگرهای پرس و جو بر اساس عملکرد به صورت زیر میباشد :
- محدود کننده (Restriction)
where
- بازتابی (Projection)
Select,SelectMany
- جداکننده (Partitioning)
Take,Skip,TakeWhile,SkipWhile
- مرتب سازی (Ordering)
OrderBy,OrderByDescending,ThenBy,ThenByDescending,Reverse
- گروه بندی (Grouping)
GroupBy
- مجموعه (Set)
Concat,Union,Intersect,Except
- تبدیل (Conversion)
ToArray,ToList,ToDictionary,ToLookup,OfType,Cast
- عنصر(Element)
First,FirstOrDefault,Last,LastOrDefalt,Single,SingleOrDefault
- عنصر در (ElementAt)
ElementAtOrDefault,DefaultIfEmpty
- تولید (Generation)
Empty,Range,Report
- کمی (Quantifier)
Any,All,Contains,SequenceEqual
- مجموعه (Aggregate)
Count,LongCount,Sum,Min,Max,Average,Aggregate
- اتصال (Join)
Join,GroupJoin,Zip
در این مطلب عملگرهای محدود کننده، بازتابی و جداکننده، بررسی خواهند شد. بعد از معرفی هر عملگر، معادل عبارتهای پرس و جوی آنها نیز معرفی خواهند شد.
این عملگرها یک توالی ورودی را دریافت و یک توالی محدود شده یا به بیان دیگر فیلتر شده را تولید میکنند. عناصر توالی خروجی، عناصری هستند که با فیلتر اعمال شده مطابقت دارند.
Where این عملگر، عناصری را به خروجی ارسال میکند که با گزارهی (Predicate) تعریف شده مطابقت داشته باشند.
نکته : گزاره (Predicate) تابعی است که اگر شرط آن تامین شود، مقدار true و در غیر اینصورت مقدار false را باز میگرداند.
مثال :
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.Where(x => x.Calories >= 200); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
خروجی کد بالا:
Sugar Butter
IEnumerable<Ingredient> query = ingredients.Where((ingredient, index) => ingredient.Name == "Sugar" || index == 4);
پیاده سازی توسط عبارتهای پرس و جو
در روش عبارتهای پرس و جو، کلمهی کلیدی where بههمراه یک عبارت منطقی در پرس و جو ظاهر میشود:
IEnumerable<Ingredient> gueryExpression = from i in ingredients where i.Calories >= 200 select i;
عملگرهای بازتاب (Projection Operators)
عملگرهای پرس و جوی بازتابی، یک توالی ورودی را دریافت و با تبدیل عناصر آنها، یک توالی خروجی را تولید میکنند.
Select
عملگر پرس و جوی select هر عنصر توالی ورودی را به یک عنصر در توالی خروجی تبدیل میکند. تعداد عناصر ورودی و خروجی در این حالت یکسان میباشند.
پرس و جوی زیر عناصر توالی ورودی Ingredient را به عناصر رشتهای در توالی خروجی بازتاب میکند. عبارت Lambda تعریف شده، نحوهی بازتاب عناصر را مشخص میکند (هر عنصر ingredient به یک عنصر رشتهای بازتاب میشود):
IEnumerable<string> query = ingredients.Select(x => x.Name);
IEnumerable<int> query = ingredients.Select(x => x.Name.Length);
در عملیات بازتاب میتوان یک شیء جدید را در توالی خروجی ایجاد کرد. در کد زیر عناصر Ingredient به یک عنصر جدید از نوع IngredientNameAndLenght بازتاب شده است.
class IngredientNameAndLength { public string Name { get; set; } public int Length { get; set; } public override string ToString() { return Name + " " + Length; } } IEnumerable<IngredientNameAndLength> query = ingredients.Select(x => new IngredientNameAndLength { Name = x.Name, Length = x.Name.Length });
var query = ingredients.Select(x => new { Name = x.Name, Length = x.Name.Length });
{ Name = Sugar, Length = 5 } { Name = Egg, Length = 3 } { Name = Milk, Length = 4 } { Name = Flour, Length = 5 } { Name = Butter, Length = 6 }
پیاده سازی توسط عبارتهای پرس و جو
کلمهی کلیدی select در عبارتهای پرس و جو، به شکل زیر استفاده میشود:
var query = from i in ingredients select new { Name=i.Name, Length=i.Name.Length };
برعکس دستور select که به ازای هر عنصر در توالی ورودی، یک عنصر را در توالی خروجی بازتاب میکرد، دستور SelectMany ممکن است تعداد عناصر کمتر و یا بیشتری را در توالی خروجی بازتاب کند (انتخاب مقادیر یک مجموعه از مجموعهی دیگر).
عبارت Lambda نوشته شده در عملگر Select، یک مقدار را باز میگرداند. اما عبارت Lambda نوشته شده در عملگر SelectMany، یک توالی فرزند (Child Sequence) را ایجاد میکند. توالی فرزند ممکن است حاوی تعداد مختلفی از عناصر به ازای هر عنصر در توالی ورودی باشد.
در مثال زیر عبارت Lambda یک توالی فرزند از کاراکترها ایجاد میکند (یک کاراکتر به ازای هر حرف از هر عنصر توالی ورودی). بهطور مثال عنصر ورودی Sugar، پس از پردازش توسط عبارت Lambda، یک توالی فرزند با 5 عنصر 's','u','g','e','r' فراهم میکند. هر رشتهی در توالی Ingredient میتواند تعداد حروف متفاوتی داشته باشد. در نتیجه عبارت Lambda، توالیهای فرزندی با طولهای مختلف ایجاد میکند.
مثال:
string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"}; IEnumerable<char> query = ingredients.SelectMany(x => x.ToCharArray()); foreach (var item in query) { Console.WriteLine(item); }
S u g a r E g g M i l k F l o u r B u t t e r
پیاده سازی توسط عبارتهای پرس و جو
در روش عبارتهای پرس و جو یک عبارت (clause) اضافی from برای تولید یک توالی فرزند به کار برده میشود. خروجی کد زیر مشابه کد قبلی است:
string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"}; IEnumerable<char> query2 = from i in ingredients from c in i.ToCharArray() select c; foreach (var item in query2) { Console.WriteLine(item); }
عملگرهای جداکننده، یک توالی ورودی را دریافت و آنها را از هم جدا میکنند.
Take
عملگر Takeیک توالی ورودی را دریافت کرده و تعداد مشخصی از توالی را باز میگرداند.
مثال: عملگر Take، سه عضو اول توالی Ingredient را باز میگرداند:
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.Take(3); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
Sugar Egg Milk
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.Where(x=>x.Calories>100).Take(2); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
Sugar Milk
پیاده سازی توسط عبارتهای پرس و جو
کلمهی کلیدی (Key word) جایگزینی برای عملگر Take وجود ندارد، ولی میتوان با ترکیب دو روش نوشتن پرس و جو، خروجی مورد نظر را تولید کرد:
IEnumerable<Ingredient> query = (from i in ingredients where i.Calories > 100 select i).Take(2); TakeWhile
کد زیر تا زمانی که خصوصیت Calorie توالی ورودی بزرگتر و مساوی 100 باشد، عناصر را جدا میکند.
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.TakeWhile(x => x.Calories >= 100); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
Sugar Egg Milk
پیاده سازی توسط عبارتهای پرس و جو
برای این عملگر هم کلمهی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو نتیجهی دلخواه حاصل میشود.
Skip
این عملگر تعداد مشخصی از عناصر را از ابتدای توالی نادیده گرفته و باقی عناصر را باز میگرداند.
کد زیر سه عضو اول توالی را نادیده گرفته و مابقی را باز میگرداند:
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.Skip(3); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
Flour Butter
پیاده سازی توسط عبارتهای پرس و جو
برای این عملگر هم کلمهی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو، نتیجهی دلخواه حاصل میشود.
با ترکیب عملگر Take و Skip میتوان اطلاعات را بهصورت صفحه بندی به کاربر ارائه کرد. مثال زیر این حالت را نشان میدهد.
IEnumerable<Ingredient> firstPage = ingredients.Take(2); IEnumerable<Ingredient> secondPage = ingredients.Skip(2).Take(2); IEnumerable<Ingredient> thirdPage = ingredients.Skip(4).Take(2); Console.WriteLine("First Page : "); foreach (var ingredient in firstPage) { Console.WriteLine(" - " + ingredient.Name); } Console.WriteLine("Second Page : "); foreach (var ingredient in secondPage) { Console.WriteLine(" - " + ingredient.Name); } Console.WriteLine("Third Page : "); foreach (var ingredient in thirdPage) { Console.WriteLine(" - " + ingredient.Name); }
First Page : - Sugar - Egg Second Page : - Milk - Flour Third Page : - Butter SkipWhile
مثال:
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=150}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Butter", Calories=200}, }; IEnumerable<Ingredient> query = ingredients.SkipWhile(x => x.Name != "Milk"); foreach (var ingredient in query) { Console.WriteLine(ingredient.Name); }
Milk Flour Butter