آموزش LinQ در سی شارپ – با مثالهای کاربردی

بخش اول – عملگرهای مرتبسازی (Sorting Operators)
معرفی LinQ:
Linq و یا بطور صریح Language Integrated Query (زبان پرس و جوی یکپارچه) تکنولوژی نسبتا جدیدی می باشد که مایکروسافت از دات نت ورژن 3 به بعد ارائه داد .
در این سری آموزشی می خواهیم عملگرهای Linq را با هم بررسی کنیم و برای هریک از operator ها چند مثال بزنیم . یکی از پرکاربردترین عملگرها ، ordering و یا مرتب سازی می باشد . قبل از اینکه سراغ نوشتن اولین مثال برویم ، با نحوه ایجاد پروژه بصورت زیر آشنا شوید :
ایجاد پروژه جدید :
در این سری آموزشی Linq ، به نرم افزار Visual Studio نیاز دارید . پس از باز کردن برنامه ویژوال استودیو ، از منوی فایل ، New Project را انتخاب کنید و سپس در پنجره باز شده ، در قسمت چپ ، Visual c# را انتخاب کرده و از بخش راست (میانی) Console Application را بر میگزینیم . مانند شکل زیر :
» مثال اول
مرتبسازی آرایه بر اساس حروف الفبا:
به مثال زیر توجه کنید :
string[] words = { "cherry", "apple", "blueberry" }; var sortedWords = from word in words orderby word select word;
توضیح : ابتدا آرایه ای از رشته تعریف کردیم که سه کلمه در آن قرار دادیم .سپس در دستور بعد ، توسط orderby ، این آرایه را بر اساس حروف الفبا و بصورت صعودی (پیش فرض : ascending) مرتب کرده ایم و در متغیر sordtedWords قرار داده ایم .
نکته : دستور orderby را نیز می توان بصورت Extension Method بنویسیم . بصورت زیر :
words.OrderBy(item => item);
برای نمایش این لیست مرتب سازی شده ، باید در یک حلقه foreach بصورت زیر عمل کنیم :
Console.WriteLine("The sorted list of words:\n—————–"); foreach (var word in sortedWords) { Console.WriteLine(word); }
نکته : اگر بطور صریح نوع مرتب سازی را قید نکنیم ، پیش فرض بصورت صعودی می باشد ولی اگر بخواهیم در مثال فوق ، آرایه رشته ها را بطور نزولی مرتب کنیم ، باید بصورت orderby word descending بنویسیم .
بنابراین کد مثال اول بطور کامل بصورت زیر می شود :
public static void OrderBy_Sample1() { string[] words = { "cherry", "apple", "blueberry" }; var sortedWords = from word in words orderby word select word; //words.OrderBy(item => item); Console.WriteLine("The sorted list of words:\n—————–"); foreach (var word in sortedWords) { Console.WriteLine(word); } }
خروجی بصورت زیر است :
blueberry , cherry
» مثال دوم
مرتبسازی آرایه بر اساس طول رشته:
در این مثال می خواهیم آرایه رشته ها را بر اساس طول هر یک از رشته ها مرتب کنیم :
var sortedWords = from word in words orderby word.Length word;
خروجی بصورت زیر است :
cherry , blueberry
نحوه نمایش آن نیز مانند مثال اول می باشد . کد مثال دوم بصورت زیر است :
public static void OrderBy_Sample2() { string[] words = { "cherry", "apple", "blueberry" }; var sortedWords = from word in words orderby word.Length select word; Console.WriteLine("The sorted list of words (by length):\n————————–"); foreach (var word in sortedWords) { Console.WriteLine(word); }
» مثال سوم
دریافت مشخصات محصولات از انبار و مرتبسازی آنها:
برای رفتن سراغ مثال سوم ، نیاز به دو کلاس جدید بنام Product و ProductRepository می باشد . بنابراین ابتدا این دو کلاس را مطابق زیر ایجاد می کنیم و سپس مثال سوم را مطرح می کنیم .
تعریف کلاس Product :
کلاسی بنام Product به پروژه خود اضافه کنید و property های زیر را در آن تعریف کنید :
public int ProductID { get; set; } public string ProductName { get; set; } public string Category { get; set; } public decimal UnitPrice { get; set; } int UnitsInStock { get; set; }
تعریف کلاس ProductRepository :
بدین منظور ، کلاسی بنام ProductRepository تعریف کنید و عبارت زیر را برای تعریف لیستی از محصولات در آن بنویسید :
private List<Product> _products;
سازنده این کلاس را بصورت زیر تعریف کنید :
public ProductRepository() { LoadProducts(); }
اکنون متد LoadProducts را بصورت زیر تعریف کنید (مقداردهی به کلاس Product) :
private void LoadProducts(){ _products = new List { new Product { ProductID = 1, ProductName = "Chai", Category = "Beverages", UnitPrice = 18.0000M, UnitsInStock = 39 }, new Product { ProductID = 2, ProductName = "Chang", Category = "Beverages", UnitPrice = 19.0000M, UnitsInStock = 17 }, new Product { ProductID = 3, ProductName = "Aniseed Syrup", Category = "Condiments", UnitPrice = 10.0000M, UnitsInStock = 13 }, new Product { ProductID = 4, ProductName = "Chef Anton's Cajun Seasoning", Category = "Condiments", UnitPrice = 22.0000M, UnitsInStock = 53 }, new Product { ProductID = 5, ProductName = "Chef Anton's Gumbo Mix", Category = "Condiments", UnitPrice = 21.3500M, UnitsInStock = 0 }, new Product { ProductID = 6, ProductName = "Grandma's Boysenberry Spread", Category = "Condiments", UnitPrice = 25.0000M, UnitsInStock = 120 }, new Product { ProductID = 7, ProductName = "Uncle Bob's Organic Dried Pears", Category = "Produce", UnitPrice = 30.0000M, UnitsInStock = 15 }, new Product { ProductID = 8, ProductName = "Northwoods Cranberry Sauce", Category = "Condiments", UnitPrice = 40.0000M, UnitsInStock = 6 }, new Product { ProductID = 9, ProductName = "Mishi Kobe Niku", Category = "Meat/Poultry", UnitPrice = 97.0000M, UnitsInStock = 29 }, new Product { ProductID = 10, ProductName = "Ikura", Category = "Seafood", UnitPrice = 31.0000M, UnitsInStock = 31 }, new Product { ProductID = 11, ProductName = "Queso Cabrales", Category = "Dairy Products", UnitPrice = 21.0000M, UnitsInStock = 22 }, new Product { ProductID = 12, ProductName = "Queso Manchego La Pastora", Category = "Dairy Products", UnitPrice = 38.0000M, UnitsInStock = 86 }, new Product { ProductID = 13, ProductName = "Konbu", Category = "Seafood", UnitPrice = 6.0000M, UnitsInStock = 24 }, new Product { ProductID = 14, ProductName = "Tofu", Category = "Produce", UnitPrice = 23.2500M, UnitsInStock = 35 }, new Product { ProductID = 15, ProductName = "Genen Shouyu", Category = "Condiments", UnitPrice = 15.5000M, UnitsInStock = 39 }, new Product { ProductID = 16, ProductName = "Pavlova", Category = "Confections", UnitPrice = 17.4500M, UnitsInStock = 29 }, new Product { ProductID = 17, ProductName = "Alice Mutton", Category = "Meat/Poultry", UnitPrice = 39.0000M, UnitsInStock = 0 }, new Product { ProductID = 18, ProductName = "Carnarvon Tigers", Category = "Seafood", UnitPrice = 62.5000M, UnitsInStock = 42 }, new Product { ProductID = 19, ProductName = "Teatime Chocolate Biscuits", Category = "Confections", UnitPrice = 9.2000M, UnitsInStock = 25 }, new Product { ProductID = 20, ProductName = "Sir Rodney's Marmalade", Category = "Confections", UnitPrice = 81.0000M, UnitsInStock = 40 }, new Product { ProductID = 21, ProductName = "Sir Rodney's Scones", Category = "Confections", UnitPrice = 10.0000M, UnitsInStock = 3 }, new Product { ProductID = 22, ProductName = "Gustaf's Knäckebröd", Category = "Grains/Cereals", UnitPrice = 21.0000M, UnitsInStock = 104 }, new Product { ProductID = 23, ProductName = "Tunnbröd", Category = "Grains/Cereals", UnitPrice = 9.0000M, UnitsInStock = 61 }, new Product { ProductID = 24, ProductName = "Guaraná Fantástica", Category = "Beverages", UnitPrice = 4.5000M, UnitsInStock = 20 }, new Product { ProductID = 25, ProductName = "NuNuCa Nuß-Nougat-Creme", Category = "Confections", UnitPrice = 14.0000M, UnitsInStock = 76 }, new Product { ProductID = 26, ProductName = "Gumbär Gummibärchen", Category = "Confections", UnitPrice = 31.2300M, UnitsInStock = 15 }, new Product { ProductID = 27, ProductName = "Schoggi Schokolade", Category = "Confections", UnitPrice = 43.9000M, UnitsInStock = 49 }, new Product { ProductID = 28, ProductName = "Rössle Sauerkraut", Category = "Produce", UnitPrice = 45.6000M, UnitsInStock = 26 }, new Product { ProductID = 29, ProductName = "Thüringer Rostbratwurst", Category = "Meat/Poultry", UnitPrice = 123.7900M, UnitsInStock = 0 }, new Product { ProductID = 30, ProductName = "Nord-Ost Matjeshering", Category = "Seafood", UnitPrice = 25.8900M, UnitsInStock = 10 }, new Product { ProductID = 31, ProductName = "Gorgonzola Telino", Category = "Dairy Products", UnitPrice = 12.5000M, UnitsInStock = 0 }, new Product { ProductID = 32, ProductName = "Mascarpone Fabioli", Category = "Dairy Products", UnitPrice = 32.0000M, UnitsInStock = 9 }, new Product { ProductID = 33, ProductName = "Geitost", Category = "Dairy Products", UnitPrice = 2.5000M, UnitsInStock = 112 }, new Product { ProductID = 34, ProductName = "Sasquatch Ale", Category = "Beverages", UnitPrice = 14.0000M, UnitsInStock = 111 }, new Product { ProductID = 35, ProductName = "Steeleye Stout", Category = "Beverages", UnitPrice = 18.0000M, UnitsInStock = 20 }, new Product { ProductID = 36, ProductName = "Inlagd Sill", Category = "Seafood", UnitPrice = 19.0000M, UnitsInStock = 112 }, new Product { ProductID = 37, ProductName = "Gravad lax", Category = "Seafood", UnitPrice = 26.0000M, UnitsInStock = 11 }, new Product { ProductID = 38, ProductName = "Côte de Blaye", Category = "Beverages", UnitPrice = 263.5000M, UnitsInStock = 17 }, new Product { ProductID = 39, ProductName = "Chartreuse verte", Category = "Beverages", UnitPrice = 18.0000M, UnitsInStock = 69 }, new Product { ProductID = 40, ProductName = "Boston Crab Meat", Category = "Seafood", UnitPrice = 18.4000M, UnitsInStock = 123 }, new Product { ProductID = 41, ProductName = "Jack's New England Clam Chowder", Category = "Seafood", UnitPrice = 9.6500M, UnitsInStock = 85 }, new Product { ProductID = 42, ProductName = "Singaporean Hokkien Fried Mee", Category = "Grains/Cereals", UnitPrice = 14.0000M, UnitsInStock = 26 }, new Product { ProductID = 43, ProductName = "Ipoh Coffee", Category = "Beverages", UnitPrice = 46.0000M, UnitsInStock = 17 }, new Product { ProductID = 44, ProductName = "Gula Malacca", Category = "Condiments", UnitPrice = 19.4500M, UnitsInStock = 27 }, new Product { ProductID = 45, ProductName = "Rogede sild", Category = "Seafood", UnitPrice = 9.5000M, UnitsInStock = 5 }, new Product { ProductID = 46, ProductName = "Spegesild", Category = "Seafood", UnitPrice = 12.0000M, UnitsInStock = 95 }, new Product { ProductID = 47, ProductName = "Zaanse koeken", Category = "Confections", UnitPrice = 9.5000M, UnitsInStock = 36 }, new Product { ProductID = 48, ProductName = "Chocolade", Category = "Confections", UnitPrice = 12.7500M, UnitsInStock = 15 }, new Product { ProductID = 49, ProductName = "Maxilaku", Category = "Confections", UnitPrice = 20.0000M, UnitsInStock = 10 }, new Product { ProductID = 50, ProductName = "Valkoinen suklaa", Category = "Confections", UnitPrice = 16.2500M, UnitsInStock = 65 }, new Product { ProductID = 51, ProductName = "Manjimup Dried Apples", Category = "Produce", UnitPrice = 53.0000M, UnitsInStock = 20 }, new Product { ProductID = 52, ProductName = "Filo Mix", Category = "Grains/Cereals", UnitPrice = 7.0000M, UnitsInStock = 38 }, new Product { ProductID = 53, ProductName = "Perth Pasties", Category = "Meat/Poultry", UnitPrice = 32.8000M, UnitsInStock = 0 }, new Product { ProductID = 54, ProductName = "Tourtière", Category = "Meat/Poultry", UnitPrice = 7.4500M, UnitsInStock = 21 }, new Product { ProductID = 55, ProductName = "Pâté chinois", Category = "Meat/Poultry", UnitPrice = 24.0000M, UnitsInStock = 115 }, new Product { ProductID = 56, ProductName = "Gnocchi di nonna Alice", Category = "Grains/Cereals", UnitPrice = 38.0000M, UnitsInStock = 21 } }; }
و در نهایت ، متدی برای برگرداندن تمامی محصولات تعریف کنید :
public IEnumerable<Product> GetAll(){ return _products; }
بنابراین بطور کلی کلاس ProductRepository دارای یک سازنده ، یک property و دو متد می باشد .
میخواهیم تمام محصولات را از انبار گرفته و آنها را بر اساس نامشان مرتب کنیم :
public static void OrderBy_Sample3() { var products = new ProductRepository().GetAll(); var sortedProducts = from product in products orderby product.ProductName select product; foreach (var product in sortedProducts) { Console.WriteLine("ID: {0}\tName: {1}", product.ProductID, product.ProductName); } }
توضیح : ابتدا با فراخوانی متد GetAll ، تمام محصولات موجود در انبار محصولات را بیرون کشیدیم و سپس توسط دستور orderby ، آنها را براساس نامشان مرتب کرده و در متغیر sortedProducts ریخته و در نهایت در یک حلقه foreach ، تک تک آنها را به خروجی می دهیم .
مدل مقایسه ای دلخواه در دستور OrderBy :
برای اینکه روش دلخواه خود را برای مقایسه و sorting به دستور orderby اعمال کنیم ، باید اولا از دستور orderby بصورت Extension Method استفاده کنیم ، دوما مدل مقایسه ای دلخواه خود را تعریف کنیم .
» مثال چهارم
در این مثال ما میخواهیم کلمات واقع در یک آرایه بصورت CaseInsensitive (یعنی غیر حساس به حروف بزرگ و کوچک انگلیسی) با هم مقایسه و سورت شوند .
نکته : پیش فرض مقایسه کلمات در زبانهای برنامه نویسی ، CaseSensitive می باشد .
آرایه ای از رشته ها بصورت زیر داریم :
string[] words = { "aPPLE", "AbAcUs", "bRaNcH", "BlUeBeRrY", "ClOvEr", "cHeRry" };
نحوه استفاده از دستور orderby بصورت extensionMethod برای مقایسه دلخواه بصورت زیر است :
var sortedWords = words.OrderBy(word => word, new CaseInsensitiveComparer());
حال باید کلاس CaseInsensitiveComparer را پیاده سازی کنیم :
بدین منظور کلاسی به همین نام در پروژه خود ایجاد کنید و کدهای زیر را در آن تعریف کنید :
public class CaseInsensitiveComparer : IComparer { public int Compare(string x, string y) { return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); } }
متد Compare یکی از سه خروجی -1 , 0 , 1 را برمیگرداند :
- اگر اولی از دومی بزرگتر باشد 1 برمیگرداند .
- اگر اولی با دومی برابر باشد 0 برمیگرداند .
- و اگر دومی از اولی بزرگتر باشد 1- برمیگرداند .
بنابراین کد مثال 4 بطور کلی بصورت زیر شده است :
public static void OrderByComparer() { string[] words = { "aPPLE", "AbAcUs", "bRaNcH", "BlUeBeRrY", "ClOvEr", "cHeRry" }; var sortedWords = words.OrderBy(word => word, new CaseInsensitiveComparer()); foreach (var word in sortedWords) { Console.WriteLine(word); } }
» مثال پنجم
برای سورت بصورت نزولی مثال زیر را داریم :
public static void OrderByDescendingSimple1() { double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; var sortedDoubles = from number in doubles orderby number descending select number; Console.WriteLine("The doubles from highest to lowest:\n————————-"); foreach (var number in sortedDoubles) { Console.WriteLine(number); } }
همانطور که مشاهده می کنید ، تنها تفاوت سورت نزولی با صعودی (پیش فرض) استفاده از عبارت descending می باشد .
» مثال ششم
مرتبسازی نزولی در LinQ:
در این مثال می خواهیم مرتب سازی نزولی را روی لیست محصولات موجود در انبار و بر اساس تعداد محصول موجود در انبار انجام دهیم .
public static void OrderByDescendingSimple2() { var products = new ProductRepository().GetAll(); var sortedProducts = from product in products orderby product.UnitsInStock descending select product; foreach (var product in sortedProducts) { Console.WriteLine("ID: {0}\tName: {1}\tUnists In Stock: {2}", product.ProductID, product.ProductName, product.UnitsInStock); } }
بنابراین عملیات مرتب سازی را علاوه بر اینکه می توان روی primitive type ها انجام دهیم ، میتوان روی انواع تعریف شده توسط کاربر نیز اعمال کنیم .
مرتب سازی بر اساس دو یا چند فاکتور در LinQ:
می توان بیشتر از یک معیار برای مقایسه و مرتب سازی در نظر بگیریم . باید آنها را توسط کاما (,) از هم جدا کنیم . به مثال هفتم توجه کنید :
» مثال هفتم
مرتبسازی آرایه بر اساس دو آیتم در LinQ:
در این مثال ، ما آرایه ای از کلمات را داریم که می خواهیم آنها را ابتدا بر اساس طول کلمه و سپس بر اساس حروف الفبای کلمه (بصورت صعودی) مرتب کنیم :
public static void ThenBySimple() { string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var sortedDigits = from digit in digits orderby digit.Length, digit select digit; Console.WriteLine("Sorted digits:\n——————-"); foreach (var digit in sortedDigits) { Console.WriteLine(digit); } }
» مثال هشتم
مرتبسازی تو در تو در LinQ:
برای مرتب سازی تو در تو بصورت ExtensionMethod ، باید دستور دوم به بعد را بصورت thenby بنویسیم :
public static void ThenByComparer() { string[] words = { "aPPLE", "AbAcUs", "bRaNcH", "BlUeBeRrY", "ClOvEr", "cHeRry" }; var sortedWords = words.OrderBy(a => a.Length) .ThenBy(a => a, new CaseInsensitiveComparer()); foreach (var word in sortedWords) { Console.WriteLine(word); } }
توضیح : آرایه ای از رشته ها را داریم که می خواهیم ابتدا بر اساس طول رشته و سپس بر اساس مدل مقایسه ای دلخواه ما (که در اینجا غیرحساس بودن به حروف بزرگ و کوچک است) مرتب سازی انجام شود .
تمرین : برای مشاهده مثالی دیگر در این زمینه به وب سایت CodeProject مراجعه کنید .
نکته : می توان جهت مرتب سازی (sorting direction) را برای هرکدام از thenby ها بصورت جداگانه معرفی کنیم که در بخش بعد بحث خواهیم کرد .
تا بدین جای مقاله، 8 مثال از مبحث sorting در linq را بررسی کردیم . در ادامه، 3 مثال دیگر در این رابطه خواهیم دید . که نحوه پیاده سازی چند orderby پشت سرهم را بررسی می کنیم . برای اعمال چند orderBy ، باید از دستور thenBy استفاده شود یعنی در اولین عبارت مانند قبل از orderBy استفاده میکنیم و از ordering دوم به بعد از دستور ThenBy استفاده میکنیم . به مثالهای زیر دقت کنید :
» مثال نهم
درج دو آیتم در دستور orderBy در LinQ
در این مثال میخواهیم لیست محصولات را ابتدا براساس حروف الفبای دسته بندی (category) محصول و سپس بر اساس قیمت و بصورت نزولی مرتب سازی انجام شود :
public static void ThenByDescendingSimple() { var products = new ProductRepository().GetAll(); var sortedProducts = from product in products orderby product.Category, product.UnitPrice descending select product; foreach (var product in sortedProducts) { Console.WriteLine("Name: {0}\tCategory: {1}\tUnit Price: {2}", product.ProductName, product.Category, product.UnitPrice); } }
» مثال دهم
دستورات thenby و thenbydescending در LinQ:
برای اعمال مرتب سازی نزولی برای thenby ها (ordering دوم به بعد) باید از عبارت thenbydescending استفاده کنیم :
public static void ThenByDescendingComparer() { string[] words = { "aPPLE", "AbAcUs", "bRaNcH", "BlUeBeRrY", "ClOvEr", "cHeRry" }; var sortedWords = words.OrderBy(word => word.Length) .ThenByDescending(a => a, new CaseInsensitiveComparer()); foreach (var word in sortedWords) { Console.WriteLine(word); } }
» مثال یازدهم
دستور Reverse در LinQ:
برای برعکس کردن نتایج بدست آمده از پرس و جوی Linq بکار میرود . بعنوان مثال اگر خروجی بصورت 1و4و6 باشد با اعمال Reverse ، خروجی بصورت 6و4و1 می شود . به کد زیر دقت کنید :
public static void Reverse() { string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var reversedIDigits = ( from digit in digits where digit[1] == 'i' select digit ).Reverse(); Console.WriteLine("A backwards list of the digits with a second character of 'i':\n———————————————-"); foreach (var digit in reversedIDigits) { Console.WriteLine(digit); } }
توضیح : در این مثال می خواهیم از آرایه digits ، عناصری که حرف دوم آنها i می باشد را برداریم و در نهایت آنها را Reverse کنیم . خروجی مثال فوق nine,eight,six می شود.
بخش دوم – عملگرهای مجموعهای (Set Operators)
در این بخش قصد داریم شما را با عملگرهای مجموعه ای مانند اجتماع-اشتراک-تفاضل در زبان LINQ آشنا کنیم . اینگونه عملگرها روی مجموعه ها کار می کنند مانند اجتماع (union) و اشتراک(Intersect) و …
عملگر Distinct :
این عملگر تکراری های مجموعه را حذف می کند و باقی عناصر را برمیگرداند .
» مثال اول
به مثال اول توجه کنید :
public static void Distinct1() { int[] factorsOf300 = { 2, 2, 3, 5, 5 }; var uniqueFactors = factorsOf300.Distinct(); Console.WriteLine("Prime factors of 300:\n—————"); foreach (var factor in uniqueFactors) { Console.WriteLine(factor); } }
پس خروجی این مثال 2و3و5 می باشند .
» مثال دوم
در مثال بعدی می خواهیم دسته بندی لیست محصولات را استخراج کرده و آنها را بصورت یکتا به خروجی بدهد یعنی تکراری ها را حذف کند :
public static void Distinct2() { var products = new ProductRepository().GetAll(); var categoryNames = ( from product in products select product.Category ); Console.WriteLine("Category names:\n—————-"); foreach (var category in categoryNames) { Console.WriteLine(category); } }
عملگر اجتماع (Union) :
برای اجتماع گیری از دو و یا چند مجموعه بکار میرود . در مثال اول union را روی دو مجموعه ساده اعداد اعمال میکنیم.
» مثال اول
public static void Union1() { int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; int[] numbersB = { 1, 3, 5, 7, 8 }; var uniqueNumbers = numbersA.Union(numbersB); Console.WriteLine("Unique numbers from both arrays:\n———————-"); foreach (var number in uniqueNumbers) { Console.WriteLine(number); } }
پس خروجی بصورت 0,1,2,3,4,5,6,7,8,9 می شود .
به منظور یادگیری بیشتر در زمینه عملگرهای مجموعه ای ، به وب سایت مایکروسافت مراجعه کنید .
» مثال دوم
در مثال دوم میخواهیم عمل اجتماع را روی لیست محصولات و لیست مشتریان اعمال کنیم . بنابراین نیاز داریم ابتدا لیست مشتریان را تعریف کنیم .
تعریف Customer :
کلاسی بسازید و نام آنرا customer قرار دهید و property های زیر را در آن تعریف کنید :
public class Customer { #region Properties… public string CustomerID { get; set; } public string CompanyName { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Fax { get; set; } public List Orders { get; set; } #endregion }
اکنون کلاسی بنام CustomerRepository تعریف کنید و کدهای زیر را در آن بنویسید :
private List _customers; public CustomerRepository() { LoadCustomers(); } private void LoadCustomers() { _customers = ( from customerElement in XDocument.Load(@"Datasouce\Customers.xml").Root.Elements("customer") select new Customer { CustomerID = (string)customerElement.Element("id"), CompanyName = (string)customerElement.Element("name"), Address = (string)customerElement.Element("address"), City = (string)customerElement.Element("city"), Region = (string)customerElement.Element("region"), PostalCode = (string)customerElement.Element("postalcode"), Country = (string)customerElement.Element("country"), Phone = (string)customerElement.Element("phone"), Fax = (string)customerElement.Element("fax"), Orders = ( from orderElement in customerElement.Elements("orders").Elements("order") select new Order { OrderID = (int)orderElement.Element("id"), OrderDate = (DateTime)orderElement.Element("orderdate"), Total = (decimal)orderElement.Element("total") }).ToList() }).ToList(); } public IEnumerable GetAll() { return _customers; }
توضیح متد LoadCustomer :
همانطور که در ابتدای این متد مشاهده می کنید ، لیست مشتریان را از یک فایل xml که در پوشه Datasource می باشد خواندیم . ساختار این فایل بصورت زیر است :
ALFKI Alfreds Futterkiste Obere Str. 57 Berlin 12209 Germany 030-0074321 030-0076545 10643 1997-08-25T00:00:00 814.50 10692 1997-10-03T00:00:00 878.00 10702 1997-10-13T00:00:00 330.00 10835 1998-01-15T00:00:00 845.80 10952 1998-03-16T00:00:00 471.20 11011 1998-04-09T00:00:00 933.50
مشاهده می کنید که ریشه اصلی این ساختار customers است و دارای زیرشاخه هایی بنام customer می باشد . هر customer دارای مشخصاتی مانند شماره تلفن ، ایمیل و غیره می باشد و در نهایت زیرشاخه ای بنام orders دارد که تعیین کننده سفارشات هر مشتری است . هر orders شامل چندین order است که بیانگر جزئیات هر سفارش است (مانند آیدی – تاریخ – قیمت کل)
در متد LoadCustomer ، میخواهیم اطلاعات مشتریان را از این ساختار سلسله مرتبی بیرون بکشیم و به لیست تبدیل کنیم (ToList) .
بعد از تعریف کلاس های Customer , CustomerRepository اکنون تابع Union2 را بصورت زیر تعریف کنید :
public static void Union2() { var products = new ProductRepository().GetAll(); var customers = new CustomerRepository().GetAll(); var productFirstChars = from product in products select product.ProductName[0]; var customerFirstChars = from customer in customers select customer.CompanyName[0]; var uniqueFirstChars = productFirstChars.Union(customerFirstChars); Console.WriteLine("Unique first letters from Product names and Customer names:\n———————————"); foreach (var character in uniqueFirstChars) { Console.WriteLine(character); } }
توضیح : ابتدا لیست کامل محصولات و نیز مشتریان را بدست آورده ایم و سپس اولین کاراکتر نام محصول را در متغیر productFirstChars و اولین حرف company را داخل customerFirsthars ریختیم .
در نهایت لیستی از کاراکترهای غیرتکراری را برمیگرداند که یا در لیست اول است یا دوم و یا هردو .
نکته 1) : نوع هر دو لیست برای union کردن باید یکی باشد مثلا هر دو int باشند .
نکته 2) : عملگر union تکراری ها را حذف می کند .
عملگر اشتراک (Intersect) :
برای اشتراک گیری بین دو مجموعه بکار میرود . یعنی عناصری که در هر دو مجموعه وجود دارند را برمیگرداند .
» مثال اول
به مثال ساده زیر توجه کنید :
public static void Intersect1() { int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; int[] numbersB = { 1, 3, 5, 7, 8 }; var commonNumbers = numbersA.Intersect(numbersB); Console.WriteLine("Common numbers shared by both arrays:\n——————————-"); foreach (var number in commonNumbers) { Console.WriteLine(number); } }
خروجی برابر : 5,8 می باشد .
در مثال دوم می خواهیم اولین کاراکتر لیست محصولات و مشتریان را که در هر دو مشترک هستند را برگردانیم :
public static void Intersect2() { var products = new ProductRepository().GetAll(); var customers = new CustomerRepository().GetAll(); var productFirstChars = from product in products select product.ProductName[0]; var customerFirstChars = from customer in customers select customer.CompanyName[0]; var commonFirstChars = productFirstChars.Intersect(customerFirstChars); Console.WriteLine("Common first letters from Product names and Customer names:\n————————————–"); foreach (var character in commonFirstChars) { Console.WriteLine(character); } }
عملگر تفاضل (Except) :
عمل تفاضل دو مجموعه را انجام می دهد یعنی عناصری که در مجموعه اول است و در مجموعه دوم نیست .
» مثال اول
در مثال اول ، دو مجموعه عددی داریم که بصورت زیر تعریف شده اند :
public static void Except1() { int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; int[] numbersB = { 1, 3, 5, 7, 8 }; IEnumerable aOnlyNumbers = numbersA.Except(numbersB); Console.WriteLine("Numbers in first array but not second array:\n———————————–"); foreach (var number in aOnlyNumbers) { Console.WriteLine(number); } }
» مثال دوم
مثال دوم را مطابق معمول با دو لیست محصول و مشتری انجام می دهیم :
public static void Except2() { var products = new ProductRepository().GetAll(); var customers = new CustomerRepository().GetAll(); var productFirstChars = from product in products select product.ProductName[0]; var customerFirstChars = from customer in customers select customer.CompanyName[0]; var productOnlyFirstChars = productFirstChars.Except(customerFirstChars); Console.WriteLine("First letters from Product names, but not from Customer names:\n—————————————————"); foreach (var character in productOnlyFirstChars) { Console.WriteLine(character); } }
در تمامی این عملگرهای مجموعه ای ، دو مجموعه باید هم نوع باشند.
تا اینجا عملگرهای مجموعه ای (Set) و مرتب سازی (Ordering) را بررسی کردیم . در ادامه می خواهیم عملگرهای زیر را مورد بررسی و تحلیل قرار دهیم:
- عملگرهای تبدیل یا Coversion مانند ToArray و ToDictionary و ToList
- عملگرهای المنت یا Element مانند First و FirstOrDefault و ElementAt
- عملگرهای تولید یا Generation مانند Repeat و Range
- عملگرهای شمارنده یا Quantifier مانند Any و All
- عملگرهای تجمیع یا Aggregation مانند Count و Min و Max و Average و Sum
بخش سوم – عملگرهای تبدیل (Conversion Operators)
دستور ToArray :
برای تبدیل به آرایه بکار میرود . به مثال زیر توجه کنید :
public static void ToArray() { double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; var sortedDoubles = from number in doubles orderby number descending select number; var doublesArray = sortedDoubles.ToArray(); Console.WriteLine("Every other double from highest to lowest:\n————————"); for (int index = 0; index < doublesArray.Length; index += 2) { Console.WriteLine(doublesArray[index]); } }
دستور ToList :
برای تبدیل به لیست Generic از نوع موردنظر بکار میرود . به مثال زیر دقت کنید :
public static void ToList() { string[] words = { "cherry", "apple", "blueberry" }; var sortedWords = from word in words orderby word select word; var wordList = sortedWords.ToList(); Console.WriteLine("The sorted word list:\n—————————"); foreach (var word in wordList) { Console.WriteLine(word); } }
عملگر ToDictionary :
برای تبدیل به دیکشنری بکار میرود . همانطور که می دانید نوع Dictionary دارای دو پارامتر کلید-مقدار (key-value) می باشد . نکته ای که در تبدیل به Dictionary وجود دارد اینست که باید کلید دیکشنری را به صراحت ارائه دهیم . برای روشن شدن مطلب به مثال زیر دقت کنید :
public static void ToDictionary() { var scoreRecords = new[] { new {Name = "Alice", Score = 50}, new {Name = "Bob" , Score = 40}, new {Name = "Cathy", Score = 45} }; var scoreRecordsDict = scoreRecords.ToDictionary(sr => sr.Name); Console.WriteLine("Bob's score: {0}", scoreRecordsDict[“Bob”]); }
توضیح : در مثال فوق ، عبارت Name بعنوان کلید دیکشنری معرفی شده است و Score مقدار آنست.
عملگر ofType :
برای اعمال فیلترینگ روی مجموعه هایی که دارای عناصر همگن نیستند (یعنی دارای نوع های مختلف مانند int , string , double , … می باشند) برای مثال داریم :
public static void OfType() { object[] numbers = { null, 1.0, "two", 3, "four", 5, "six", 7.0 }; var doubles = numbers.OfType(); Console.WriteLine("Numbers stored as doubles:\n--------------------------------"); foreach (var number in doubles) { Console.WriteLine(number); } }
توضیح : در مثال فوق ، می خواهیم عناصری را که از نوع double هستند را به خروجی بدهیم .
بخش چهارم – عملگرهای المنت (Element Operators)
عملگرهای المنت به آنهایی گفته می شوند که یک عنصر را بعنوان خروجی برمیگردانند . مثلا در یک مجموعه می خواهیم یک آیتم خاص را با شرطی خاص برگردانیم مثلا کاربری که ID آن برابر 210 می باشد .
عملگر First :
در پایان یک query می توان از First استفاده کنیم . اگر query بدرستی اجرا شود و آیتم موردنظر را نیز برگرداند ، اولین آیتم را به عنوان خروجی در نظر می گیرد .
» مثال اول
public static void FirstSimple() { var products = new ProductRepository().GetAll(); Product product12 = ( from product in products where product.ProductID == 12 select product ).First(); Console.WriteLine("ID: {0}\tName: {1}\tCategory: {2}", product12.ProductID, product12.ProductName, product12.Category); }
» مثال دوم
در مثال دوم از First شرطی استفاده می کنیم :
{string[] strings = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; string startsWithO = strings.First(s => s[0] == 'o'); Console.WriteLine("A string starting with 'o': {0}", startsWithO); }
گفتیم که اولین رشته ای را برگردان که با حرف o شروع شود .
نکته مهم : کار با عملگر First خطرناک می باشد ! بدین معنی که اگر query آیتمی برنگرداند ، برنامه در run time دچار خطا می شود . بنابراین فقط در مواقعی باید از First استفاده کنیم که مطمئن باشیم query ما حتما یک خروجی دارد .
برای رفع مشکل فوق ، از دات نت ورژن 3.5 به بعد ، عملگر FirstOrDefault تعریف شد .
عملگر FirstOrDefault :
اگر رشته پرس و جوی ما نتیجه ای برنگرداند (مانند مثال زیر) در صورتی که از FirstOrDefault استفاده کرده باشیم ، با خطای runtime مواجه نمی شویم :
public static void FirstOrDefaultSimple() { int[] numbers = { }; int firstNumOrDefault = numbers.FirstOrDefault(); Console.WriteLine(firstNumOrDefault); }
خروجی مثال فوق برابر صفر می باشد . (زیرا پیش فرض int صفر می باشد)
FirstOrDefault شرطی :
در مثال دوم از این عملگر ، شرط مورد نظر را روی خود عملگر پیاده می کنیم :
public static void FirstOrDefaultCondition() { var products = new ProductRepository().GetAll(); Product product789 = products.FirstOrDefault(p => p.ProductID == 789); Console.WriteLine("Product 789 exists: {0}", product789 != null); }
عملگر ElementAt :
در مواردی که می دانیم آیتم موردنظرمان در کدام ایندکس وجود دارد ، از این دستور استفاده میکنیم . ایندکس از صفر شروع می شود . به مثال زیر دقت کنید :
public static void ElementAt() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int fourthLowNum = ( from number in numbers where number > 5 select number ).ElementAt(1); // second number is index 1 because sequences use 0-based indexing Console.WriteLine("Second number > 5: {0}", fourthLowNum); }
توضیح : خروجی مثال فوق ، عدد 8 می باشد.
بخش پنجم – عملگرهای تولید (Generation Operators)
بر خلاف عملگرهایی که تا کنون بررسی کردیم ، Generation Operator ها ، برای تولید مجموعه بکار میروند . دو تا عملگر دارد . بنام های Range و Repeat .
عملگر Range :
با یک مثال این عملگر را توضیح می دهیم :
public static void Range() { var numbers = from number in Enumerable.Range(100, 50) select new { Number = number, OddEven = number % 2 == 1 ? "odd" : "even" }; foreach (var number in numbers) { Console.WriteLine("The number {0} is {1}.", number.Number, number.OddEven); } }
توضیح : توسط دستور Range(100,50) گفتیم که از عدد 100 تا 50 عدد بعد از آن در محدوده تعریفی ما قرار میگیرد . یعنی اعداد 100 تا 149 . در دستور select new دو متغیر جدید بنامهای Number و OddEven تعریف کردیم که Number همان عدد است و OddEven یکی از مقادیر Odd یا Even می باشد . یعنی اعداد زوج و فرد بین 100 تا 149 رامشخص کرده و در حلقه foreach نمایش می دهیم .
عملگر Repeat :
برای تکرار عنصر دلخواه ما به تعداد دلخواه . دو پارامتر می گیرد که اولی مقدار تکرارشونده دلخواه ما می باشد و دومی تعداد تکرار شدن آن :
public static void Repeat() { var numbers = Enumerable.Repeat("Mohtavaban.com", 10); foreach (var number in numbers) { Console.WriteLine(number); } }
در خروجی تابع فوق ، عبارت Mohtavaban.com به تعداد 10 بار تکرار می شود.
بخش ششم- عملگرهای کمیتسنج (Quantifier Operators)
برای بررسی اینکه یک آیتم در مجموعه موردنظر ما وجود دارد یا خیر و یا یک شرطی روی مجموعه ما صادق هست یا خیر . دو تا عملگر در این زمینه وجود دارد به نام های Any و All . نوع خروجی اینها همیشه True , False می باشد .
عملگر Any :
با یک مثال ، این عملگر را توضیح خواهیم داد :
public static void AnySmiple() { string[] words = { "believe", "relief", "receipt", "field" }; bool iAfterE = words.Any(word => word.Contains("ei")); Console.WriteLine("There is a word that contains in the list that contains 'ei': {0}", iAfterE); }
توضیح : توسط Any بررسی کرده ایم که آیا آیتمی در مجموعه ما وجود دارد که شامل حروف ei باشد . خروجی آن true می باشد زیرا کلمه receipt مشمول این شرط می شود .
عملگر All :
به مثال زیر توجه کنید :
public static void AllSimple() { int[] numbers = { 1, 11, 3, 19, 41, 65, 19 }; bool onlyOdd = numbers.All(number => number % 2 == 1); Console.WriteLine("The list contains only odd numbers: {0}", onlyOdd); }
توضیح : توسط All بررسی کردیم که شرط ما روی تمام آیتمها مطابقت می کند یا خیر .
بخش هفتم – عملگرهای تجمیع (Aggregation Operators)
کلمه Aggregation به معنی تجمیع می باشد و کارش به این صورت است که با در نظر گرفتن یک متغیر و یک اشاره گر ، روی collection موردنظر حرکت می کند و مقادیری مانند sum , count , max , min و غیره را برمیگرداند .
عملگر Count :
همانطور که از اسمش مشخص است ، تعداد اعضای یک کالکشن را برمیگرداند . در واقع اشاره گر مذکور با گذر از روی هر یک از آیتم های کالکشن ، یک واحد به count اضافه می کند . مثال زیر را مشاهده کنید :
public static void CountSimple() { int[] factorsOf300 = { 2, 2, 3, 5, 5 }; int uniqueFactors = factorsOf300.Distinct().Count(); Console.WriteLine("There are {0} unique factors of 300.", uniqueFactors); }
توضیح : توسط عملگرهای Distinct , Count توانستیم تعداد اعداد یکتای مجموعه را برگردانیم که در این مثال خروجی برابر عدد 3 می باشد (اعداد 2و3و5)
عملگر Count شرطی :
میخواهیم ابتدا شرط دلخواه خود را روی مجموعه بررسی کنیم و سپس تعداد آیتم های واقع در مجموعه جواب بدست آمده را به خروجی بدهیم :
public static void CountConditional() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(number => number % 2 == 1); Console.WriteLine("There are {0} odd numbers in the list.", oddNumbers); }
در مثال فوق ، تعداد اعداد فرد واقع در آرایه int را بعنوان خروجی دریافت می کنیم .
عملگر Count تو در تو (Nested) :
در مثال زیر می خواهیم از جدول Customer آیدی هر مشتری بعلاوه تعداد سفارشات هر مشتری را به خروجی بدهیم .
public static void CountNested() { var customers = new CustomerRepository().GetAll(); var orderCounts = from customer in customers select new { customer.CustomerID, OrderCount = customer.Orders.Count() }; Console.WriteLine("Customer orders:\n————————–"); foreach (var customer in orderCounts) { Console.WriteLine("Customer: {0}\tOrders Count: {1}", customer.CustomerID, customer.OrderCount); } }
عملگر Sum :
برای عمل جمع کردن روی کالکشن موردنظر بکار میرود . در مثال زیر می خواهیم مجموع اعداد واقع در مجموعه را بدست آوریم :
public static void SumSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; double numbersSum = numbers.Sum(); Console.WriteLine("The sum of the numbers is {0}.", numbersSum); }
در مثال زیر می خواهیم تعداد کل کاراکتر های کلمات واقع در آرایه را بدست آوریم . بدین صورت که طول هر یک از رشته ها را با هم جمع می کنیم :
public static void SumProjection() { string[] words = { "cherry", "apple", "blueberry" }; double totalChars = words.Sum(word => word.Length); Console.WriteLine("There are a total of {0} characters in these words.", totalChars); }
عملگر Min :
برای مینیمم گرفتن از آیتم های مجموعه بکار میرود . مثال ساده زیر را مشاهده کنید :
public static void MinSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int minNum = numbers.Min(); Console.WriteLine("The minimum number is {0}.", minNum); }
در مثال زیر می خواهیم تعداد کاراکترهای کوتاه ترین کلمه را بدست آوریم . بدین صورت که طول هر یک از رشته ها را بدست می آوریم و سپس مینیمم این مجموعه را به خروجی می دهیم :
public static void MinProjection() { string[] words = { "cherry", "apple", "blueberry" }; int shortestWord = words.Min(word => word.Length); Console.WriteLine("The shortest word is {0} characters long.", shortestWord); }
عملگر Max :
مقدار ماکزیمم را از مجموعه موردنظر برمیگرداند . به مثال زیر توجه کنید :
public static void MaxSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int maxNumber = numbers.Max(); Console.WriteLine("The maximum number is {0}.", maxNumber); }
در مثال دوم میخواهیم تعداد حرفهای طولانی ترین کلمه در آرایه رشته ای را بدست آوریم :
public static void MaxProjection() { string[] words = { "cherry", "apple", "blueberry" }; int longestLength = words.Max(word => word.Length); Console.WriteLine("The longest word is {0} characters long.", longestLength); }
عملگر Average :
برای معدل گیری از مجموعه ها بکار میرود . بعنوان مثال می خواهیم میانگین مجموعه عددی را بدست آوریم :
public static void AverageSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; double averageNumber = numbers.Average(); Console.WriteLine("The average number is {0}.", averageNumber); }
در مثال دوم می خواهیم میانگین تعداد کاراکترهای کلمات آرایه را برگردانیم :
public static void AverageProjection() { string[] words = { "cherry", "apple", "blueberry" }; double averageLength = words.Average(word => word.Length); Console.WriteLine("The average word length is {0} characters.", averageLength); }
» در صورت نیاز به تکمیل دانش خود در زمینه عملگرهای تجمیع (aggregation) مطالعه مقاله “Aggregate Operators – LINQ to DataSet” توصیه می شود.
بخش هشتم – عملگرهای محدودیت (Restriction Operators)
عملگرهای محدودیت یا restriction در LinQ ، برای محدود کردن مجموعه جواب می باشند .
» مثال اول :
به خروجی دادن اعداد کوچکتر از 5:
در این مثال ساده زیر میخواهیم اعداد کوچکتر از 5 را از مجموعه عددی به خروجی بدهیم :
public static void Simple1() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var lowNums = from n in numbers where n < 5 select n; Console.WriteLine("Numbers < 5:"); foreach (var x in lowNums) { Console.WriteLine(x); } }
توضیح : توسط عبارت where n < 5 بیان کردیم که اعداد کوچکتر از 5 را میخواهیم .
نکته : در مجموعه های بزرگ ، اگر ابتدا مجموعه را sort کنیم و سپس شرط موردنظر را بر روی ان اعمال کنیم ، عملیات ما سریع تر انجام می شود ولی در مجموعه های کوچک مانند مثال فوق ، سرعت محاسبه محسوس نمی باشد .
» مثال دوم :
خروجی گرفتن از محصولاتی که در انبار موجود نمی باشند:
در مثال دوم کمی قضیه را سخت تر کردیم . میخواهیم پس از گرفتن لیست کامل محصولات از کلاس ProductRepository ، محصولاتی را بدست بیاوریم که در انبار موجود نمی باشند (UnitInStock = 0) :
public static void Simple2() { var products = new ProductRepository().GetAll(); var soldOutProducts = from product in products where product.UnitsInStock == 0 select product; Console.WriteLine("Sold out products:\n—————-"); foreach (var product in soldOutProducts) { Console.WriteLine("{0} is sold out!", product.ProductName); } }
» مثال سوم :
دریافت محصولات موجود در انبار و دارای محدوده قیمت مشخص:
در این مثال میخواهیم دو شرط را روی لیست محصولات بررسی کنیم . بدین صورت که اولا باید در انبار موجود باشند و دوما اینکه قیمت آنها از 30 دلار بیشتر باشد :
public static void Simple3() { var products = new ProductRepository().GetAll(); var expensiveInStockProducts = from product in products where product.UnitsInStock > 0 && product.UnitPrice > 30.00M select product; Console.WriteLine("In-stock products that cost more than 30.00:\n———————————-"); foreach (var product in expensiveInStockProducts) { Console.WriteLine("{0} is in stock and costs more than 30.00.", product.ProductName); } }
» مثال چهارم :
نمایش سفارشات مشتریان ایالت واشنگتن:
در مثال چهارم میخواهیم روی لیست مشتریان ، شروط موردنظر را اعمال کنیم . همانطور که در مقالات قبل ذکر شد ، لیست مشتریان شامل زیرشاخه هایی می باشد که بیانگر سفارشات آنها می باشد . در این مثال میخواهیم سفارشات مشتریانی را نمایش دهیم که از ایالت واشنگتن (WA) باشند :
public static void DrillDown() { var customers = new CustomerRepository().GetAll(); var waCustomers = from customer in customers where customer.Region == "WA" select customer; Console.WriteLine("Customers from Washington and their orders:\n—————————————-"); foreach (var customer in waCustomers) { Console.WriteLine("Customer {0}: {1}", customer.CustomerID, customer.CompanyName); foreach (var order in customer.Orders) { Console.WriteLine(" Order {0}: {1}", order.OrderID, order.OrderDate); } } }
قابلیت Indexed :
در صورتی که ایندکس آیتمی که در حال پردازش است برایمان اهمیت دارد ، می توانیم از Indexed استفاده کنیم . فقط با استفاده از Extension Method می توانیم به این هدف برسیم . برای روشن شدن موضوع به مثال زیر توجه کنید :
» مثال پنجم :
نمایش آیتمهایی از آرایه رشتهای که طول رشته از ایندکس آن کوچکتر باشد:
در این مثال می خواهیم آیتم هایی را از لیست رشته ای نمایش دهیم که طول رشته از ایندکس آن کوچکتر باشد :
public static void Indexed() { string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var shortDigits = digits.Where((digit, index) => digit.Length < index); Console.WriteLine("Short digits:\n————"); foreach (var digit in shortDigits) { Console.WriteLine("The word {0} is shorter than its value.", digit); } }
توضیح : در شرط Where پارامتر اول string می باشد و پارامتر دوم int . نوع خروجی آن نیز bool است . بنابراین مقدار خروجی این تابع کلمات five,six,seven,eight,nine می باشند.
بخش نهم – عملگرهای نمایش (Projection Operators)
عملگرهای نمایش در LinQ برای نمایش متفاوت اطلاعات بکار میروند .
» مثال اول :
افزودن یک واحد به آیتمهای لیست:
در مثال ساده اول می بینیم که نحوه نمایش بدین صورت است که به هر یک از آیتمهای لیست ، یک واحد اضافه می کند و سپس به خروجی می دهد :
public static void Simple1() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var numsPlusOne = from number in numbers select number + 1; Console.WriteLine("Numbers + 1:\n———–"); foreach (var number in numsPlusOne) { Console.WriteLine(number); } }
» مثال دوم :
گرفتن لیست محصولات و نمایش نام آنها:
در این مثال ، لیست تمام محصولات را گرفته (GetAll) و سپس نام آنها را نمایش می دهیم :
public static void Simple2() { var products = new ProductRepository().GetAll(); var productNames = from product in products select product.ProductName; Console.WriteLine("Product Names:\n————"); foreach (var productName in productNames) { Console.WriteLine(productName); } }
» مثال سوم :
نمایش آیتم ها بصورت رندوم:
در مثال سوم ، دو مجموعه داریم که اولی عددی و بعدی رشته ای می باشد که در مجموعه اول ، اعداد را بطور رندوم (تصادفی) و در مجموعه دوم نام هر عدد را نوشتیم . به کد این مثال توجه کنید :
public static void Transformation() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; string[] strings = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var textNumbers = from number in numbers select strings[number]; Console.WriteLine("Number strings:\n—————-"); foreach (var text in textNumbers) { Console.WriteLine(text); } }
توضیح : query این مثال بدین صورت است که در Range Vriable یعنی عبارت مقابل from ، از مجموعه اول استفاده کردیم و در قسمت projection یعنی عبارت مقابل select از مجموعه رشته ای استفاده شده است . بنابراین خروجی این تابع five,four,one,three,nine,eight,six,seven,two,zero می باشند . بعنوان مثال در اولین دوره گردش این کوئری ، number عدد 5 می باشد . پس در مجموعه رشته ای بدنبال strings[5] یعنی ششمین کلمه میگردد (دقت کنید که ایندکس از صفر شروع می شود) .
در ادامه مقاله، مثالهایی را بررسی می کنیم که از Anonymous Types یا انواع بی نام در مقابل عبارت select استفاده کرده باشند .
» مثال چهارم :
استفاده از Anonymous Type
در این مثال می خواهیم از Anonymous Type ها استفاده کنیم و در مقابل عبارت select نوع جدیدی بسازیم . ابتدا کد مثال چهارم را مشاهده کنید :
public static void AnonymousTypes1() { string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" }; var upperLowerWords = from word in words select new { Upper = word.ToUpper(), Lower = word.ToLower() }; foreach (var ul in upperLowerWords) { Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower); } }
توضیح : متغیر words یک آرایه رشته ای می باشد . در کوئری توسط نوع های بی نام (Anonumous Types) پارامترهای projection را دو قسمت کردیم . که اولی UpperCase کلمه را برمیگرداند و دومی LowerCase آن را . در نهایت در حلقه foreach ، به ازای هر رشته در آرایه مذکور ، دو کلمه نمایش داده می شود (UpperCase , LowerCase) .
» مثال پنجم :
مثال دوم از انواع بینام (Anonumous Types):
مثال جالب دیگری که از انواع بی نام استفاده کرده این مثال است . ابتدا به کد زیر توجه کنید :
public static void AnonymousTypes2() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; string[] strings = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var digitOddEvens = from number in numbers select new { Digit = strings[number], Even = (number % 2 == 0) }; foreach (var digit in digitOddEvens) { Console.WriteLine("The digit {0} is {1}.", digit.Digit, digit.Even ? "even" : "odd"); } }
توضیح : دو مجموعه عددی و رشته ای که در مثال سوم تعریف شد را نیز در این مثال بکار میبریم . ولی در این مثال می خواهیم به ازای هر عدد بگوییم کدام فرد است و کدام زوج . توسط انواع بی نام دو پارامتر Digit , Even تعریف کردیم که اولی رشته مربوطه را برمیگرداند و دومی زوج یا فرد بودن عدد را .
» مثال ششم :
نمایش فیلدهای دلخواه از لیست محصولات:
در این مثال ، میخواهیم فقط فیلدهای دلخواه خود را از لیست محصولات بدست آوریم و نمایش دهیم .
به سورس کد این مثال دقت کنید :
public static void AnonymousTypes3() { var products = new ProductRepository().GetAll(); var productInfos = from product in products select new { product.ProductName, product.Category, Price = product.UnitPrice }; Console.WriteLine("Product Info:\n——————-"); foreach (var productInfo in productInfos) { Console.WriteLine("{0} is in the category {1} and costs {2} per unit.", productInfo.ProductName, productInfo.Category, productInfo.Price); } }
برای انجام اینکار مجددا از نوع پرکاربرد Anonymous استفاده کرده ایم . بدین صورت که در مقابل عبارت select ، فیلدهای استخراجی را محدود به سه فیلد کردیم (ProductName,Category,UnitPrice) . در مقابل select new می توان نام دیگری به فیلد موردنظر بدهیم . در مثال جاری ، نام و فهرست محصول را با property name خودشان project میکنیم (نمایش می دهیم) ولی UnitPrice را به Price تغییر نام داده ایم .
در ادامه مقاله، from های تو در تو را بررسی کنیم و همچنین روی where فیلترینگ اعمال کنیم .
» مثال هفتم :
استفاده از دستور select بصورت Lambda
Select هم خاصیت Indexed دارد . برای استفاده از این امکان ، باید دستور select را بصورت lambda بنویسیم . بصورت زیر :
public static void Indexed() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var numsInPlace = numbers.Select((num, index) => new { Num = num, InPlace = (num == index) }); Console.WriteLine("Number: In-place?\n—————"); foreach (var number in numsInPlace) { Console.WriteLine("{0}: {1}", number.Num, number.InPlace); } }
توضیح : ابتدا یک آرایه عددی تعریف کرده ایم . سپس عبارت مقابل select را بصورت lambda تعریف میکنیم که نوع خروجی آن Anonymous می باشد که شامل دو پارامتر Num , InPlace است . کلا کار این کوئری اینست که اگر عددی با ایندکس خود برابر باشد True برمیگرداند و اگر برابر نباشد false برمیگرداند که در این مثال اعداد 3 و 6و 7 در ایندکس خودشان قرار دارند پس در مقابل این اعداد true نوشته می شود و در مقابل سایر آیتم ها false نوشته می شود.
» مثال هشتم :
قابلیت فیلترینگ روی Where:
در این مثال عمل فیلترینگ را روی where اعمال می کنیم :
توضیح : در این کوئری ایندکس را از اعداد مجموعه عددی که از 5 کوچکتر هستند انتخاب کرده ایم یعنی اعداد 4و3و2و1و0 .
public static void Filtered() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var lowNumbers = from number in numbers where number < 5 select digits[number]; Console.WriteLine("Numbers < 5:\n————-"); foreach (var number in lowNumbers) { Console.WriteLine(number); } }
» مثال نهم :
استفاده از دو عبارت from بصورت تو در تو:
در این مثال از دو عبارت from تو در تو استفاده کرده ایم . به سورس کد این مثال توجه کنید :
public static void CompoundFrom1() { int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; int[] numbersB = { 1, 3, 5, 7, 8 }; var pairs = from a in numbersA from b in numbersB where a < b select new { a, b }; Console.WriteLine("Pairs where a < b:\n-------------"); foreach (var pair in pairs) { Console.WriteLine("{0} is less than {1}", pair.a, pair.b); } }
توضیح : دو آرایه عددی numbersA , numbersB داریم . میخواهیم تمام زوج های مرتبی را پیدا کنیم که عدد اول از عدد دوم کوچکتر باشد . در کوئری فوق ، ازای هر عدد از numbersA را با تک تک اعداد از مجموعه دوم مقایسه می کند . هر کدام که a<b بود را داخل متغیرهای a,b می ریزد و در نهایت در یک حلقه foreach تمام این زوج های مرتب را نمایش می دهیم .
بعنوان مثال (0,1) و (0,3) و (0,5) و (0,7) و (0,8) برخی از جوابهای این کوئری می باشند .
» مثال دهم :
اعمال کوئری compound را روی لیست مشتریان و سفارشات آنان:
در این مثال کوئری compound را روی لیست مشتریان و سفارشات آنان اعمال میکنیم . ابتدا به کد زیر توجه کنید :
public static void CompoundFrom2() { var customers = new CustomerRepository().GetAll(); var orders = from customer in customers from order in customer.Orders where order.Total < 500.00M select new { customer.CustomerID, order.OrderID, order.Total }; Console.WriteLine("Orders with Total < 500.00\n————"); foreach (var order in orders) { Console.WriteLine("CustomerID: [{0}]\tOrderID: [{1}]", order.CustomerID, order.OrderID); } }
توضیح : ابتدا لیست کامل مشتریان را از فایل xml مربوطه استخراج کرده ایم و سپس با نوشتن دو from تو در تو ، با شرط Total<500 آیدی مشتری و آیدی سفارش او و نیز جمع سفارشش را به خروجی داده ایم . from اولی را روی customers و from دومی را روی customers.Orders اعمال کرده ایم .
» مثال یازدهم :
نمایش اطلاعات مشتریان و سفارشات با تاریخ مشخص:
در مثال حال حاضر میخواهیم مشتریان و سفارشات آنها را برگردانیم در صورتی که تاریخ سفارش محصول بعد از 01/01/1998 باشد :
public static void CompoundFrom3() { var customers = new CustomerRepository().GetAll(); var orders = from customer in customers from order in customer.Orders where order.OrderDate >= new DateTime(1998, 1, 1) select new { customer.CustomerID, order.OrderID, order.OrderDate }; Console.WriteLine("Orders made since 1998:\n------------------"); foreach (var order in orders) { Console.WriteLine("CustomerID: {0}\tOrderID: {1}\tOrderDate: {2}", order.CustomerID, order.OrderID, order.OrderDate); } }
توضیح : دو عبارت from در این مثال مانند مثال قبل می باشد و فقط شرط where تفاوت دارد و آن اینست که می خواهیم سفارش مشتری بعد از تاریخ 1/1/1998 باشد .
نکته : در دو مثال فوق (مثال 10 و 11) کل دو مجموعه یعنی customer و order در محاسبات دخالت داده می شوند . بنابراین بهتر است ابتدا دو مجموعه را محدود کرده و سپس محاسبات اعمال شوند . در اینصورت کوئری ما در حجم بالای اطلاعات efficient (موثرتر) خواهد بود .
برای انجام اینکار باید بعد از هر from شرط مربوط به آن را بنویسیم . به مثال زیر توجه کنید :
public static void MultipleFrom() { var customers = new CustomerRepository().GetAll(); DateTime cutoffDate = new DateTime(1997, 1, 1); var orders = from customer in customers where customer.Region == "WA" from order in customer.Orders where order.OrderDate >= cutoffDate select new { customer.CustomerID, order.OrderID }; Console.WriteLine("Washington customer having order since 1997:\n-------------------------------"); foreach (var order in orders) { Console.WriteLine("CustomerID: {0}\tOrderID: {1}",order.CustomerID, order.OrderID); } }
توضیح : ابتدا یک تاریخ تعریف کردیم و سپس در کوئری ، بعد از from اولی که مشتریان را برمیگرداند شرط هر مشتری یعنی ساکن ایالت واشنگتن بودن را بنویسیم و بعد از from دومی تاریخ هر سفارش را تعیین کرده ایم .
بخش دهم – عملگرهای قطاعبندی (Partitioning Operators)
در مواقعی که میخواهیم از چند رکورد یافت شده از دیتابیس بپریم و یا فقط چند رکورد اول یا آخر را به خروجی بدهیم . چندین عملگر در این زمینه وجود دارند که میخواهیم راجع به آنها صحبت کنیم .
عملگر Take :
» مثال اول :
داخل take تعداد آیتمی که می خواهیم بعنوان خروجی به ما بدهد را می نویسیم . به مثال اول توجه کنید:
public static void TakeSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var first3Numbers = numbers.Take(3); Console.WriteLine("First 3 numbers:\n—————"); foreach (var n in first3Numbers) { Console.WriteLine(n); } }
توضیح : ابتدا یک آرایه عددی تعریف شده است که میخواهیم در کوئری ، تعداد 3 عدد اول این مجموعه را برگردانیم . یعنی اعداد 5و4و1 .
» مثال دوم :
در مثال دوم نیز میخواهیم اطلاعات و سفارشات 3 مشتری اولی را که ساکن ایالت Washington هستند را به ما بدهد:
public static void TakeNested() { var customers = new CustomerRepository().GetAll(); var first3WAOrders = ( from customer in customers from order in customer.Orders where customer.Region == "WA" select new { customer.CustomerID, order.OrderID, order.OrderDate } ).Take(3); Console.WriteLine("First 3 orders in WA:\n——————"); foreach (var order in first3WAOrders) { Console.WriteLine("CustomerID: {0}\tOrderID: {1}\tOrderDate: {2}",order.CustomerID, order.OrderID, order.OrderDate); } }
توضیح : خروجی این تابع آیدی مشتری و آیدی سفارشات آنان بهمراه تاریخ سفارش خواهد بود . البته 3 مشتری اولی که در شرط فوق صدق می کنند .
عملگر Skip :
عمل پریدن از روی یک یا چند آیتم را انجام می دهد .
» مثال اول :
در مثال زیر توسط skip(4) از روی 4 عدد اول پریدیم ! و سایر اعداد را به خروجی داده ایم :
public static void SkipSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var allButFirst4Numbers = numbers.Skip(4); Console.WriteLine("All but first 4 numbers:\n—————–"); foreach (var n in allButFirst4Numbers) { Console.WriteLine(n); } }
» مثال دوم :
در مثال بعدی ، که شبیه مثال دوم از عملگر take می باشد ، از روی 2 مشتری اولی که در شرط ما صدق میکنند پریدیم و اطلاعات بقیه آنها را استخراج کردیم :
public static void SkipNested() { var customers = new CustomerRepository().GetAll(); var waOrders = from customer in customers where customer.Region == "WA" from order in customer.Orders select new { customer.CustomerID, order.OrderID, order.OrderDate }; var allButFirst2Orders = waOrders.Skip(2); Console.WriteLine("All but first 2 orders in WA:\n————————"); foreach (var order in allButFirst2Orders) { Console.WriteLine("CustomerID: {0}\tOrderID: {1}\tOrderDate: {2}", order.CustomerID, order.OrderID, order.OrderDate); } }
عملگر TakeWhile :
عمل برداشتن اطلاعات را تا زمانی که شرط while صدق میکند انجام میدهد و به محض اینکه شرط false شود عمل take کردن متوقف می شود .
» مثال اول :
به مثال زیر دقت کنید :
public static void TakeWhileSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstNumbersLessThan6 = numbers.TakeWhile(number => number < 6); Console.WriteLine("First numbers less than 6:\n—————–"); foreach (var number in firstNumbersLessThan6) { Console.WriteLine(number); } }
توضیح : عمل برداشتن از ابتدای آرایه یعنی عدد 5 شروع می شود و تا زمانی که آیتم از 6 کوچکتر باشد این عمل را ادامه می دهد . بنابراین خروجی کوئری فوق ، اعداد 5و4و1و3 می باشند .
» مثال دوم :
در مثال دوم از این عملگر ، می خواهیم Index هر آیتم را نیز وارد کوئری کنیم و شرط را نیز روی آن بسط دهیم :
public static void TakeWhileIndexed() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstSmallNumbers = numbers.TakeWhile((number, index) => number >= index); Console.WriteLine("First numbers not less than their position:"); foreach (var number in firstSmallNumbers) { Console.WriteLine(number); } }
توضیح : در کوئری مثال فوق ، تا زمانی که عدد ، بزرگتر و یا مساوی ایندکس خود باشد اعداد را برمیگرداند که در اینجا اعداد 5و4 به خروجی می روند .
عملگر SkipWhile :
بدین صورت کار می کند که تا زمانی که شرط صدق نکند از روی آیتمها می پرد .
» مثال اول :
به مثال جالب زیر دقت کنید :
public static void SkipWhileSimple() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var allButFirst3Numbers = numbers.SkipWhile(number => number % 3 != 0); Console.WriteLine("All elements starting from first element divisible by 3:\n---------------------------"); foreach (var number in allButFirst3Numbers) { Console.WriteLine(number); } }
توضیح : در کوئری فوق گفتیم که تا زمانی که عدد بر 3 بخش پذیر نیست به پریدن ادامه بده ! یعنی از روی اعداد 5و4و1 می پرد و با رسیدن به عدد 3 عمل پرش قطع شده و عمل take آغاز می شود . پس اعداد 3و9و8و6و7و2و0 را نمایش می دهد .
» مثال دوم :
مثال دوم از عملگر SkipWhile دقیقا برعکس مثال دوم عملگر TakeWhile عمل می کند . ابتدا به سورس کد این مثال توجه کنید :
public static void SkipWhileIndexed() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var laterNumbers = numbers.SkipWhile((number, index) => number >= index); Console.WriteLine("All elements starting from first element less than its position:\n------------------------------------"); foreach (var number in laterNumbers) { Console.WriteLine(number); } }
توضیح : در تابع فوق عمل پرش تا زمانی که عدد از ایندکس خود بزرگتر است ادامه می یابد . بنابراین اعداد 1و3و9و8و6و7و2و0 را take می کند و نمایش می دهد .
نکته : برای تمرین بیشتر در زمینه Linq به وب سایت مایکروسافت مراجعه شود .
خب دوستان ، آموزش LinQ در اینجا به پایان می رسد . لطفا با بیان دیدگاه ها و نظرات خود درباره مقالات ، تیم “راهکاری نو” را در نگارش هر چه بهتر مقالات آموزشی برنامه نویسی یاری نمایید.
مطالب زیر را حتما مطالعه کنید
1 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
hi