اشتراکها
بررسی تازههای Angular CLI 8.2
اشتراکها
قرار دادن Power BI در برنامه خودتان
اشتراکها
کتاب TypeScript Deep Dive
مطالب
آموزش LINQ بخش پنجم
در ادامه سری آموزشی LINQ، موارد دیگری از عبارتهای پرس و جو را بررسی میکنیم:
• عبارت group
• عبارت orderby
• کلمات کلیدی ascending و descending
• کلمه کلیدی by
بررسی عبارت group و کلمهی کلیدی by
عبارت group یک توالی یک بعدی (flat sequence) یا ساده را از ورودی دریافت و یک توالی گروه بندی شده را تولید میکند.
برای آشنایی بیشتر با اینترفیس IGrouping اینجا را مطالعه کنید.
عبارت orderby و کلمات کلیدی ascending و descending
عبارت orderby برای تولید یک توالی مرتب شدهی بصورت صعودی (ascending) و یا نزولی (descending) مورد استفاده قرار میگیرد.
مثال: توالی مثال قبل را در نظر بگیرد:
خروجی مثال بالا:
علملیات مرتب سازی بصورت پیش فرض بصورت صعودی میباشد (Ascending). برای تغییر این حالت میتوانید از کلمهی کلیدی descending استفاده کنید:
با اجرای مجدد برنامه، خروجی زیر را مشاهده خواهید کرد:
خروجی مثال بالا :
همانطور که مشاهده میکنید بر عکس حالت قبلی که خروجی گروهها بصورت نزولی بود، اینجا خروجی بر اساس کالری غذا و بصورت صعودی، نمایش داده شده است.
در این سری آموزشی با دو روش نوشتن پرس و جو، در LINQ آشنا شدیم:
1- Fluent Style (استفاده از متدهای الحاقی برای انجام عملیاتهای مختلف بر روی توالی)
2- Expression Style (استفاده از کلمات کلیدی (key word) برای انجام عملیاتهای مختلف بر روی توالی)
هر یک از روشهای فوق مزایایی دارند که با توجه به شرایطی که با آن روبرو هستیم از آنها استفاده میکنیم:
در مثال فوق نتیجهی اجرای دو پرس و جو، موادی است که کالری آنها بیشتر از 100 میباشد. همانطور که مشاهده میکنید روش اول مختصرتر از روش دوم است. این مورد در زمانی است که تنها یک عملیات بر روی توالی اجرا شود.
• پرس و جویی که قرار است نوشته شود، از نظر عملیات بر روی توالی ساده باشد: در این حالت روش استفاده شده تفاوتی نمیکند و بستگی به روش برگزیده و مورد علاقهی برنامه نویس و یا تیم برنامه نویسی دارد. مثلا زمانیکه تنها عملگرهای Where و orderby بخواهند اجرا شود.
• پرس و جوهایی که با متغیرهای range مختلفی رو برو هستند: در این حالت استفاده از عبارتهای پرس و جو راحت از روش عملگرهای پرس و جو میباشد.
لیستی از عملگرهای جستجو که در روش عبارتهای جستجو معادل آنها مهیا شده است :
• GroupBy
• GroupJoin
• Join
• OrderBy
• OrderByDescending
• Select
• SelectMany
• ThenBy
• ThenByDescending
• Where
در بسیاری از پرس و جوها میتوانیم هر دو روش را با هم ترکیب کنیم که نمونهای از آن، در جلسهی چهارم در زمان استفادهی از متد DefaultIfEmpty استفاده شد. درمثال زیر استفاده از عملگر count، با استفاده از روش اول، به همراه عبارت پرس و جوی تولید شدهی با روش دوم، نمایش داده شده است:
• عبارت group
• عبارت orderby
• کلمات کلیدی ascending و descending
• کلمه کلیدی by
بررسی عبارت group و کلمهی کلیدی by
عبارت group یک توالی یک بعدی (flat sequence) یا ساده را از ورودی دریافت و یک توالی گروه بندی شده را تولید میکند.
مثال: در بخش دوم این سری آموزشی، کلاسی به نام Ingredient تعریف کردیم که مواد غذایی و کالری آنها را نگهداری میکرد. در این مثال قصد داریم مواد غذایی دریافت شده از توالی ورودی را بر اساس کالری آنها، به گروههایی تقسیم کنیم، بطوریکه مواد غذایی با کالریهای یکسان در یک گروه قرار بگیرند:
Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Lard", Calories=500}, new Ingredient{Name = "Butter", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=100}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Oats", Calories=50} }; IEnumerable<IGrouping<int, Ingredient>> query = from i in ingredients group i by i.Calories; foreach (IGrouping<int, Ingredient> group in query) { Console.WriteLine($"Ingredients with {group.Key} calories"); foreach (Ingredient ingredient in group) { Console.WriteLine($"- { ingredient.Name}"); } }
در این مثال، از نوع جنریکی به نام IGrouping استفاده شدهاست که اولین پارامتر آن کلید گروه است (در این مثال کالری مواد غذایی) و دومین پارامتر آن، نوع لیست را مشخص میکند که در اینجا کلاس Ingredient است. اعضای لیست تولید شده، کالری یکسانی دارند. دلیل استفادهی از نوعها بصورت صریح و آشکار (Explicit) خوانایی بیشتر برای تشخیص خروجیهای تولید شدهی در کدها میباشد. ولی راه بهتر، جایگزین کردن کد <<IEnumerable<IGrouping<int, Ingredient با کلمهی کلیدی var میباشد.
خروجی مثال بالا:Ingredients with 500 calories - Sugar - Lard - Butter Ingredients with 100 calories - Egg - Milk Ingredients with 50 calories - Flour - Oats
برای آشنایی بیشتر با اینترفیس IGrouping اینجا را مطالعه کنید.
عبارت orderby و کلمات کلیدی ascending و descending
عبارت orderby برای تولید یک توالی مرتب شدهی بصورت صعودی (ascending) و یا نزولی (descending) مورد استفاده قرار میگیرد.
مثال: توالی مثال قبل را در نظر بگیرد:
IOrderedEnumerable<Ingredient> sortedByNameQuery = from i in ingredients orderby i.Name select i; foreach (var ingredient in sortedByNameQuery) { Console.WriteLine(ingredient.Name); }
Butter Egg Flour Lard Milk Oats Sugar
IOrderedEnumerable<Ingredient> sortedByNameQuery = from i in ingredients orderby i.Name descending select i;
Sugar Oats Milk Lard Flour Egg Butter
عبارت orderby را میتوان با عبارت groupby ترکیب کرد و نتیجهی حاصل از مثال اول این مطلب را بصورت مرتب شده بر اساس کالری مواد غذایی مشاهده کرد.
مثال: Ingredient[] ingredients = { new Ingredient{Name = "Sugar", Calories=500}, new Ingredient{Name = "Lard", Calories=500}, new Ingredient{Name = "Butter", Calories=500}, new Ingredient{Name = "Egg", Calories=100}, new Ingredient{Name = "Milk", Calories=100}, new Ingredient{Name = "Flour", Calories=50}, new Ingredient{Name = "Oats", Calories=50} }; IEnumerable<IGrouping<int, Ingredient>> query = from i in ingredients group i by i.Calories into calorieGroup orderby calorieGroup.Key select calorieGroup; foreach (IGrouping<int, Ingredient> group in query) { Console.WriteLine($"Ingredients with {group.Key} calories"); foreach (Ingredient ingredient in group) { Console.WriteLine($"- { ingredient.Name}"); } }
Ingredients with 50 calories - Flour - Oats Ingredients with 100 calories - Egg - Milk Ingredients with 500 calories - Sugar - Lard - Butter
در این سری آموزشی با دو روش نوشتن پرس و جو، در LINQ آشنا شدیم:
1- Fluent Style (استفاده از متدهای الحاقی برای انجام عملیاتهای مختلف بر روی توالی)
2- Expression Style (استفاده از کلمات کلیدی (key word) برای انجام عملیاتهای مختلف بر روی توالی)
هر یک از روشهای فوق مزایایی دارند که با توجه به شرایطی که با آن روبرو هستیم از آنها استفاده میکنیم:
• تعداد عملگرها : در صورتی که پرس و جو نیاز به یک عملگر بر روی توالی داشته باشد میتوان از روش اول استفاده کرد. به این خاطر که نسبت به روش دوم تعداد دستورات کمتری مورد استفاده قرار خواهد گرفت.
مثال :var q1 = ingredients.Where(x => x.Calories > 100); var q2 = from i in ingredients where i.Calories > 100 select i;
• پرس و جویی که قرار است نوشته شود، از نظر عملیات بر روی توالی ساده باشد: در این حالت روش استفاده شده تفاوتی نمیکند و بستگی به روش برگزیده و مورد علاقهی برنامه نویس و یا تیم برنامه نویسی دارد. مثلا زمانیکه تنها عملگرهای Where و orderby بخواهند اجرا شود.
• پرس و جوهایی که با متغیرهای range مختلفی رو برو هستند: در این حالت استفاده از عبارتهای پرس و جو راحت از روش عملگرهای پرس و جو میباشد.
لیستی از عملگرهای جستجو که در روش عبارتهای جستجو معادل آنها مهیا شده است :
• GroupBy
• GroupJoin
• Join
• OrderBy
• OrderByDescending
• Select
• SelectMany
• ThenBy
• ThenByDescending
• Where
در بسیاری از پرس و جوها میتوانیم هر دو روش را با هم ترکیب کنیم که نمونهای از آن، در جلسهی چهارم در زمان استفادهی از متد DefaultIfEmpty استفاده شد. درمثال زیر استفاده از عملگر count، با استفاده از روش اول، به همراه عبارت پرس و جوی تولید شدهی با روش دوم، نمایش داده شده است:
int mixedQuery = (from i in ingredients where i.Calories > 100 select i).Count();
مطالب
آشنایی با Gridify
Gridify چیست ؟
طبیعتا بخش FrontEnd نرم افزار شما نیاز دارد این اطلاعات را به کاربر نمایش دهد. به همین جهت در بیشتر مواقع از یک گرید برای نمایش این اطلاعات استفاده میشود.
در مثال بالا با استفاده از کلاس GridifyQuery میتوانیم به کنترل هر سه مشکل Sorting - Pagination - Filtering سمت کلاینت بپردازیم. (در ادامه با این کلاس بیشتر آشنا خواهیم شد).
این کد دقیقا معادل کد زیر است.
به طور خلاصه Gridify یک کتابخانه ساده و سریع است که عملیاتهای Filtering , Pagination و Sorting را با استفاده از شرطهای متنی (string based) امکان پذیر میکند.
به طور مثال فرض کنید که یک API را برای دریافت لیست کاربران با نام UsersList نوشتهاید. مثال:
[HttpGet("[action]")] public async Task<IActionResult> UsersList() { var users = await _dbContext.Users.AsNoTracking().ToListAsync(); return Ok(users); }
پس از دریافت اطلاعات از سرور با مشکلات زیر مواجه خواهیم شد.
- عدم پشتیبانی از Pagination: چون API، تمامی کاربران را به سمت کلاینت ارسال میکند؛ به همین جهت، هم با مشکل کارآیی (performance) در آینده مواجه میشویم و هم امکان گذاشتن صفحه بندی (Pagination) وجود نخواهد داشت.
- عدم پشتیبانی از Sorting: اگر در گرید نمایش داده شده کاربر بخواهد اطلاعات را Sort کند، چون چنین امکانی هنوز برای API ما تعریف نشده، این عملیات سمت سرور امکان پذیر نیست.
- عدم پشتیبانی از Filtering: همیشه نمایش تمامی اطلاعات مفید نیست. در اکثر مواقع ما نیاز داریم تا قسمتی از اطلاعات را با شرطی خاص، برگردانیم. به طور مثال لیست کاربران فعال در سامانه یا لیست کاربران غیرفعال.
برای رفع این مشکلات میتوان از کتابخانه Gridify استفاده کرد. مثال :
[HttpGet("[action]")] public async Task<IActionResult> UsersList(GridifyQuery filter) { var users = await _dbContext.Users.AsNoTracking().GridifyAsync(filter); return Ok(users); }
استفاده از Gridify به APIها محدود نمیشود. به طور کلی ما میتوانیم در هر نوع لیستی که امکان استفاده از IQueryable را به ما میدهد از آن استفاده نماییم.
فرض کنید در یک برنامه Console Application قصد داریم یک فیلتر را از کاربر دریافت کرده و آن را روی لیست خروجی خود اعمال کنیم. به دلیل اینکه امکان جستجوی متنی در دات نت وجود ندارد، برای انجام اینکار مجبور خواهیم شد که برای تک تک فیلدهایی که قرار است برای فیلترینگ پشتیبانی کنیم، یک query جداگانه بنویسیم؛ ولی این عملیات توسط کتابخانه Gridify امکان پذیر است. به طور مثال فرض کنید قصد داریم در لیست کاربران، کاربرانی را با نام Ali، پیدا کنیم.
var result = Users.AsQueryable().ApplyFiltering("name==Ali");
var result = Users.AsQueryable().Where(q => q.Name == "Ali");
در اینجا با استفاده از کتابخانه Gridify ما توانستیم یک static Linq را به یک dynamic Linq که در runtime مقدار دهی خواهد شد، تغییر دهیم. به همین جهت استفاده از مورد اول در برنامهی Console ما امکان پذیر است. تا اینجا ما با امکانات کلی این کتابخانه آشنا شدیم. در مقالات بعدی سعی میکنم به سایر امکانات این کتابخانه و بیشتر به جزئیات بپردازم. همینطور برای کسب اطلاعات بیشتر میتوانید به لینک زیر مراجعه نمایید.
در قسمت اول بررسی نحوه برنامه نویسی افزونه outlook ، در مورد استفاده از regular expressions اندکی توضیح داده شد. امروز مثالی دیگر از همین دست را بررسی خواهیم کرد.
چند روز قبل یک ایمیل تبلیغاتی به دست من رسید که فرد ارسال کننده انبوهی از ایمیلها را در قسمت To قرار داده بود (بجای قسمت BCC (رونوشت مخفی)).
خوب، برای جدا کردن انبوهی از ایمیلهای مخلوط با سایر متون چه باید کرد؟ چند ساعت وقت گذاشت و تک تک آنها را به صورت دستی جدا کرد؟ (برای ذخیره سازی در یک دیتابیس برای مثال :) )
یا برای مثال برنامههای download manager توانایی استخراج لینکهای موجود در یک متن کپی شده در حافظه را دارند. آنها به چه صورتی عمل میکنند؟ چگونه میتوانند لینکها را با دقتی بالا و بسیار سریع از لابلای متن موجود تشخیص دهند؟
بهینهترین و سریعترین راه برای این نوع جستجوها استفاده از کتابخانه regular expressions (عبارات با قاعده) در دات نت فریم ورک است. اگر نیاز به یک برگه تقلب (!) در این زمینه داشتید میتوانید به اینجا مراجعه کنید. همچنین در همان سایت، کاربران بسیاری را خواهید یافت که الگوهای ابداعی خود را با دیگران به اشتراگ میگذارند.
برای مثال فرض کنید فایلی را که حاوی مخلوطی از متن و ایمیل است را در یک رشته بارگذاری کردهاید. نحوه استخراج ایمیلهای موجود با استفاده از این امکانات به صورت زیر خواهد بود:
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
class CRegEx
{
/// <summary>
/// استخراج ایمیلهای یک فایل متنی و ذخیره آن در فایلی جدید
/// </summary>
/// <param name="inFilePath">فایل ورودی</param>
/// <param name="outFilePath">فایل خروجی</param>
public static void ExtractEmails(string inFilePath, string outFilePath)
{
string data = File.ReadAllText(inFilePath); //خواندن فایل متنی
//ایجاد شیء عبارت با قاعده بر اساس الگوی تشخیص ایمیلها
Regex emailRegex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
RegexOptions.IgnoreCase);
//پیدا کردن گروه تطابق یافته با الگوی ما
MatchCollection emailMatches = emailRegex.Matches(data);
//ایجاد شیء استرینگ بیلدر برای ذخیره سازی سریع اطلاعات دریافتی
StringBuilder sb = new StringBuilder();
//ذخیره ایمیلهای استخراج شده
foreach (Match emailMatch in emailMatches)
{
sb.AppendLine(emailMatch.Value);
}
//ذخیره کردن اطلاعات استخراج شده در فایلی جدید
File.WriteAllText(outFilePath, sb.ToString());
}
}
چند نکته را باید در اینجا در نظر داشت. حتما آدرسهای اضافه شده را با استفاده از عبارات باقاعده یکبار پیش از اضافه شدن بررسی نمائید (Regex.IsMatch). در صورتیکه یکی از ایمیلها فرمت غیراستانداردی داشته باشد کل کار برگشت خواهد خورد.
و همچنین باید دقت داشت که برای این موضوع حد نصاب وجود دارد. بر روی یکی از میل سرورهای یک هاست ایرانی تست کردم، حداکثر 100 رونوشت مخفی را بیشتر قبول نمیکرد. بنابراین هر بار میشود 100 ایمیل را به صورت یکجا ارسال کرد (که باز هم از روش استفاده از حلقهای که 100 بار ایمیل میزند بسیار بهتر است و هاست دار به علت ایجاد بار اضافی شدید بر روی سرور با شما تماس نخواهد گرفت)