برای نمونه فایل نمونه سؤالات مصاحبات دات را از اینجا دریافت کنید. یک چنین شکلی دارد:
خوب! چطور میتوان این Watermark را حذف یا حداقل نامرئی کرد؟
برای پاسخ به این سؤال نیاز است ابتدا با نحوه Watermark دار کردن صفحات یک فایل PDF آشناشویم.
الف) ایجاد یک فایل PDF ساده
private static void createPdfFile(string pdfFile) { using (var fs = new FileStream(pdfFile, FileMode.Create, FileAccess.Write, FileShare.None)) { using (var doc = new Document(PageSize.A4)) { using (var witier = PdfWriter.GetInstance(doc, fs)) { doc.Open(); for (int i = 1; i <= 5; i++) { doc.NewPage(); doc.Add(new Paragraph(String.Format("This is page {0}", i))); } doc.Close(); } } } }
ب) افزودن Watermark به فایل PDF تهیه شده
private static void addWatermark(string pdfFile, string watermarkedFile, string watermarkText) { FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); var tahoma = FontFactory.GetFont("Tahoma", BaseFont.IDENTITY_H, 40, Font.NORMAL, BaseColor.BLACK); var reader = new PdfReader(pdfFile); using (var fileStream = new FileStream(watermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None)) { using (var stamper = new PdfStamper(reader, fileStream)) { int pageCount = reader.NumberOfPages; for (int i = 1; i <= pageCount; i++) { var rect = reader.GetPageSize(i); var cb = stamper.GetUnderContent(i); var gState = new PdfGState(); gState.FillOpacity = 0.25f; cb.SetGState(gState); ColumnText.ShowTextAligned( canvas: cb, alignment: Element.ALIGN_CENTER, phrase: new Phrase(watermarkText, tahoma), x: rect.Width / 2, y: rect.Height / 2, rotation: 45f, runDirection: PdfWriter.RUN_DIRECTION_LTR, arabicOptions: 0); } } } }
در اینجا توسط PdfReader، فایل موجود گشوده میشود. به این ترتیب میتوان به تک تک صفحات این فایل دسترسی یافت. از PdfStamper برای نوشتن در این فایل باز شده استفاده میکنیم. متد stamper.GetUnderContent، لایه زیرین متن صفحات را در اختیار ما قرار میدهد. اگر علاقمند به نوشتن بر روی لایه رویی متون هستید از متد stamper.GetOverContent استفاده کنید. در اینجا از PdfGState برای مشخص سازی میزان شفافیت متن درحال نمایش، با مقدار دهی FillOpacity آن استفاده شده است. نهایتا از متد ColumnText.ShowTextAligned برای نمایش متن مورد نظر در مکان و زاویهای مشخص استفاده میکنیم. این متد با زبان فارسی سازگاری دارد و run direction آن قابل تنظیم است.
ج) آشنایی با ساختار سطح پایین Watermark اضافه شده به صفحات
تقریبا اکثر Watermarkهایی که بر روی صفحات PDF درج میشوند، نیمه شفاف هستند تا بتوان متن زیر آنها را مطالعه کرد. این شفافیت همانطور که ذکر شد توسط مقدار دهی شیء PdfGState حاصل میشود. اگر فایل PDF تولیدی در قسمت ب را توسط برنامه iText Rups باز کنیم، به شکل زیر خواهیم رسید:
هدف ما این است که به شیء PdfGState موجود در هر صفحه، دسترسی یافته و مقدار FillOpacity آنرا صفر کنیم. به این ترتیب این Watermark، کاملا شفاف یا نامرئی خواهد شد. در PDF نهایی، چیزی به نام شیء PdfGState وجود ندارد، بلکه با یک سری Dictionary و Array سر و کار داریم.
همانطور که در شکل فوق ملاحظه میکنید، برای رسیدن به Gstateها باید مراحل زیر طی شوند:
1- فایل PDF گشوده شده و سپس به هر صفحه دسترسی یافت.
2- نیاز است RESOURCES صفحه جاری استخراج شوند.
3- در این منابع، باید EXTGSTATE را که همان PdfGStateها هستند، بیابیم.
4- سپس مقدار ca این EXTGSTATE یافت شده را به صفر مقدار دهی کنیم.
private static void removeWatermark(string watermarkedFile, string unwatermarkedFile) { PdfReader.unethicalreading = true; PdfReader reader = new PdfReader(watermarkedFile); reader.RemoveUnusedObjects(); int pageCount = reader.NumberOfPages; for (int i = 1; i <= pageCount; i++) { var page = reader.GetPageN(i); PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES); PdfDictionary extGStates = resources.GetAsDict(PdfName.EXTGSTATE); if (extGStates == null) continue; foreach (PdfName name in extGStates.Keys) { var obj = extGStates.Get(name); PdfDictionary extGStateObject = (PdfDictionary)PdfReader.GetPdfObject(obj); var stateNumber = extGStateObject.Get(PdfName.ca_); if (stateNumber == null) continue; var caNumber = (PdfNumber)PdfReader.GetPdfObject(stateNumber); if (caNumber.FloatValue != 1f) { extGStateObject.Remove(PdfName.ca_); //با تنظیم مقدار به صفر، نامرئی خواهد شد extGStateObject.Put(PdfName.ca_, new PdfNumber(0f)); } } } using (FileStream fs = new FileStream(unwatermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None)) { using (PdfStamper stamper = new PdfStamper(reader, fs)) { stamper.SetFullCompression(); stamper.Close(); } } }
در این متد، watermarkedFile فایلی است که باید watermark آن نامرئی شود و unwatermarkedFile فایل تولیدی حاصل است.
- «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid »
- «استفاده از Kendo UI templates»
صورت مساله
میخواهیم به یک چنین تصویری برسیم؛ که دارای گروه بندی اطلاعات است، فرمت شرطی روی ستون قیمت آن اعمال شده و تاریخ نمایش داده شده در آن نیز شمسی است. همچنین برای مثال ستون قیمت آن دارای ته جمع صفحه بوده و به علاوه یک دکمهی سفارشی به نوار ابزار آن اضافه شدهاست.
مباحث قسمت سمت سرور این مثال با مطلب «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid» دقیقا یکی است. فقط یک خاصیت AddDate نیز در اینجا اضافه شدهاست.
تغییر نحوهی نمایش pager
اگر به قسمت pager تصویر فوق دقت کنید، یک دکمهی refresh، تعداد موارد هر صفحه و امکان وارد کردن دستی شماره صفحه، در آن پیش بینی شدهاست. این موارد را با تنظیمات ذیل میتوان فعال کرد:
$("#report-grid").kendoGrid({ // ... pageable: { previousNext: true, // default true numeric: true, // default true buttonCount: 5, // default 10 refresh: true, // default false input: true, // default false pageSizes: true // default false },
بومی سازی پیغامهای گرید
پیغامهای فارسی را که در تصویر فوق مشاهده میکنید، حاصل پیوست فایل kendo.fa-IR.js هستند:
<!--https://github.com/loudenvier/kendo-global/blob/master/lang/kendo.fa-IR.js--> <script src="js/messages/kendo.fa-IR.js" type="text/javascript"></script>
گروه بندی اطلاعات
برای گروه بندی اطلاعات در Kendo UI Grid دو قسمت باید تغییر کنند.
ابتدا باید فیلد پیش فرض گروه بندی در قسمت data source گرید تعریف شود:
var productsDataSource = new kendo.data.DataSource({ // ... group: { field: "IsAvailable" }, // ... });
$("#report-grid").kendoGrid({ // ... groupable: true, // allows the user to alter what field the grid is grouped by // ...
اضافه کردن ته جمعهای ستونها
این ته جمعها که aggregate نام دارند باید در دو قسمت فعال شوند:
var productsDataSource = new kendo.data.DataSource({ //... aggregate: [ { field: "Name", aggregate: "count" }, { field: "Price", aggregate: "sum" } ] //... });
سپس این متدها را میتوان مطابق فرمت hash syntax قالبهای Kendo UI در قسمت footerTemplate هر ستون تعریف کرد:
$("#report-grid").kendoGrid({ // ... columns: [ { field: "Name", title: "نام محصول", footerTemplate: "تعداد: #=count#" }, { field: "Price", title: "قیمت", footerTemplate: "جمع: #=kendo.toString(sum,'c0')#" } ] // ... });
فرمت شرطی اطلاعات
در ستون قیمت، میخواهیم اگر قیمتی بیش از 2490 بود، با پس زمینهی قهوهای و رنگ زرد نمایش داده شود. برای این منظور میتوان یک قالب Kendo UI سفارشی را طراحی کرد:
<script type="text/x-kendo-template" id="priceTemplate"> #if( Price > 2490 ) {# <span style="background:brown; color:yellow;">#=kendo.toString(Price,'c0')#</span> #} else {# #= kendo.toString(Price,'c0')# #}# </script>
$("#report-grid").kendoGrid({ //... columns: [ { field: "Price", title: "قیمت", template: kendo.template($("#priceTemplate").html()), footerTemplate: "جمع: #=kendo.toString(sum,'c0')#" } ] //... });
فرمت تاریخ میلادی به شمسی در حین نمایش
برای تبدیل سمت کلاینت تاریخ میلادی به شمسی از کتابخانهی moment-jalaali.js کمک گرفته شدهاست:
<!--https://github.com/moment/moment/--> <script src="js/cultures/moment.min.js" type="text/javascript"></script> <!--https://github.com/jalaali/moment-jalaali--> <script src="js/cultures/moment-jalaali.js" type="text/javascript"></script>
$("#report-grid").kendoGrid({ //... columns: [ { field: "AddDate", title: "تاریخ ثبت", template: "#=moment(AddDate).format('jYYYY/jMM/jDD')#" } ] //... });
اضافه کردن یک دکمه به نوار ابزار گرید
نوار ابزار Kendo UI Grid را نیز میتوان توسط یک قالب سفارشی آن مقدار دهی کرد:
$("#report-grid").kendoGrid({ // ... toolbar: [ { template: kendo.template($("#toolbarTemplate").html()) } ] // ... });
<script> // این اطلاعات برای تهیه خروجی سمت سرور مناسب هستند function getCurrentGridFilters() { var dataSource = $("#report-grid").data("kendoGrid").dataSource; var gridState = { page: dataSource.page(), pageSize: dataSource.pageSize(), sort: dataSource.sort(), group: dataSource.group(), filter: dataSource.filter() }; return kendo.stringify(gridState); } </script> <script id="toolbarTemplate" type="text/x-kendo-template"> <a class="k-button" href="\#" onclick="alert('gridState: ' + getCurrentGridFilters());">نوار ابزار سفارشی</a> </script>
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
KendoUI05.zip
خسته نباشید ، مثل هیمشه مطالبتون عالی بود
1) بهروز رسانی شماره نگارش داتنت
اولین قدم در جهت ارتقاء پروژههای قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بستههای نیوگت موجود را نیز به نگارشهای جدیدتر آنها ارتقاء دهید.
2) فعالسازی حالت SSR تعاملی سمت سرور
پایهی تمام تغییرات انجام شدهی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا میشوند. به همین جهت پس از ارتقاء شماره نگارش داتنت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواستهای رسیده را به کامپوننتهای Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
// ... builder.Services.AddRazorComponents().AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
در اینجا ترکیب کامپوننتهای تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.
یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آنرا به None تنظیم کنیم، کلیات ساختار پروژهای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینههای فوق را نداریم و به صورت زیر ساده شدهاست:
// ... builder.Services.AddRazorComponents(); // ... app.MapRazorComponents<App>();
3) ایجاد فایل جدید App.razor
در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژههای Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شدهاند. در این قسمت، کار هدایت درخواستهای رسیده به کامپوننتهای برنامه رخ میدهد و از این پس، صفحهی ریشهی برنامه خواهد بود.
در این تصویر، مقایسهای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده میکنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شدهاست و فایل قدیمی App.razor این پروژهها به Routes.razor، تغییر نام یافتهاست.
نمونهای از فایل App.razor جدید را که در قسمت قبل نیز معرفی کردیم، در اینجا با جزئیات بیشتری بررسی میکنیم:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
- تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شدهاند.
- base href تعریف شده اینبار فقط با یک / شروع میشود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایلهای استاتیک برنامه مانند فایلهای css. و js.، بارگذاری نخواهند شد!
- پیشتر برای رندر HeadOutlet، از یک تگهلپر استفاده میشد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شدهاست.
- تمام component tag helperهای پیشین حذف شدهاند و نیازی به آنها نیست.
- ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شدهاست.
یک نکته: همانطور که مشاهده میکنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شدهاست. یعنی در این نگارش از Blazor میتوان اسکریپتها را در کامپوننتها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارشهای پیشین Blazor) با خطا مواجه خواهید شد.
4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده
همانطور که عنوان شد، فایل قدیمی App.razor این پروژهها به Routes.razor تغییر نام یافتهاست که درج آنرا در قسمت body مشاهده میکنید. محتوای این فایل نیز به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router>
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
@page "/StatusCode/{responseCode}" <h3>StatusCode @ResponseCode</h3> @code { [Parameter] public string? ResponseCode { get; set; } }
یک نکته: اگر پروژهای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شدهاست:
if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); }
سؤال: در اینجا (برنامههای Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
میانافزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل میشود. پس از آن و تشکیل اتصال وبسوکت مورد نیاز، فقط از میانافزار UseStatusCodePagesWithRedirects استفاده میکند.
اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، میتوانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:
404.razor
@page "/StatusCode/404" <PageTitle>Not found</PageTitle> <h1>Not found</h1> <p role="alert">Sorry, there's nothing at this address.</p>
500.razor
@page "/StatusCode/500" <PageTitle>Unexpected error</PageTitle> <h1>Unexpected error</h1> <p role="alert">There was an unexpected error.</p>
401.razor
@page "/StatusCode/401" <PageTitle>Not Authorized</PageTitle> <h1>Not Authorized</h1> <p role="alert">Sorry, you are not authorized to access this page.</p>
5) تعاملی کردن سراسری برنامه
پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار میکند و تعاملی نیست. یعنی تمام کامپوننتهای آن به صورت پیشفرض، یکبار بر روی سرور رندر شده و خروجی آنها به مرورگر کاربر ارسال میشوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیتهای تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننتها، یعنی کل برنامه، اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
نکته 1: اجرای دستور زیر در داتنت 8، قالب پروژهای را ایجاد میکند که رفتار آن همانند پروژههای Blazor Server نگارشهای قبلی داتنت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همهجای آن به صورت پیشفرض، تعاملی است:
dotnet new blazor --interactivity Server --all-interactive
نکته 2: البته ... InteractiveServer، دقیقا همان حالت پیشفرض برنامههای Blazor Server قبلی نیست! این حالت رندر، به صورت پیشفرض به همراه پیشرندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی میشود (که باید به آن دقت داشت و عدم توجه به آن میتواند سبب انجام دوبارهی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیشرندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمتهای تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیشرندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیشفرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن میتوان به صورت زیر عمل کرد:
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
در مورد پیشرندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمتهای بعدی این سری بیشتر بحث خواهد شد.
WF:Windows Workflow #۴
عملگرهای پرس و جوی تبدیل، توالیهایی را که از جنس <IEnumerable<T هستند، به انواع دیگر مجموعه تبدیل میکنند.
از عملگرهای پرس و جوی زیر میتوان برای تبدیل توالیها استفاده کرد :
- OfType
- Cast
- ToArray
- ToList
- ToDictionary
- ToLookup
عملگر OfType
این عملگر عناصری از توالی را که نوع آنها را مشخص میکنیم باز میگرداند.
امضاء عملگر پرس و جوی OfType به صورت زیر است :
public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime()}; IEnumerable<string> query = input.OfType<string>(); foreach (var item in query) { Console.WriteLine(item); }
Apple Sugar
مثال :کد زیر یک ساختار سلسله مراتبی شیء گرا را نمایش میدهد:
class Ingredient { public string Name { get; set; } } class DryIngredient : Ingredient { public int Grams { get; set; } } class WetIngredient : Ingredient { public int Millilitres { get; set; } }
IEnumerable<Ingredient> input = new Ingredient[] { new DryIngredient { Name = "Flour" }, new WetIngredient { Name = "Milk" }, new WetIngredient { Name = "Water" } }; IEnumerable<WetIngredient> query = input.OfType<WetIngredient>(); foreach (WetIngredient item in query) { Console.WriteLine(item.Name); }
Milk Water
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Cast
عملگر Cast همانند عملگر OfType رفتار میکند. این عملگر یک توالی ورودی را دریافت و بر اساس نوع مشخص شده، توالی خروجی را تولید میکند. همهی عناصر توالی ورودی به نوع مشخص شده Cast میشوند. اما بر عکس عملگر OfType که عناصری را که با نوع دادهی ما سازگاری نداشت، نادیده میگرفت، این عملگر در صورت عدم موفقیت در عملیات تغییر نقش (Cast)، یک استثناء را پرتاب میکند.
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime() }; IEnumerable<string> query = input.Cast<string>(); foreach (string item in query) { Console.WriteLine(item); }
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.
پیاده سازی توسط عبارتهای جستجو
کلمهی کلیدی جایگزینی برای عملگر Cast، در عبارتهای جستجو وجود ندارد.این عملگر با استفاده از متغیر Range که در مطالب قبلی این سری معرفی شد، قابل پیاده سازی میباشد.
IEnumerable input = new object[]{ "Apple", "Sugar", "Flour" }; IEnumerable<string> query = from string i in input select i; foreach (var item in query) { Console.WriteLine(item); }
عملگر ToArray
عملگر ToArray یک توالی ورودی را دریافت و یک توالی خروجی را به صورت آرایه تولید میکند. این عملگر باعث اجرای سریع پرس و جو میشود و رفتار پیش فرض LINQ را که اجرای با تاخیر میباشد، تحریف/بازنویسی (Override) میکند.
مثال: در این مثال یک توالی از نوع <IEnumerable<string به یک آرایه رشتهای تبدیل شده است (تبدیل لیست به آرایه).
IEnumerable<string> input = new List<string> { "Apple", "Sugar", "Flour" }; string[] array = input.ToArray();
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToList
عملگر ToList همچون ToArray، اجرای با تاخیر را نادیده میگیرد. عملگر ToList همانطور که از نامش پیداست، توالی خروجی را بهصورت لیست مهیا میکند.
مثال:
IEnumerable<string> input = new[] { "Apple", "Sugar", "Flour" }; List<string> list = input.ToList();
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToDictionary
این عملگر توالی ورودی را به یک دیکشنری جنریک تبدیل میکند (<Dictinary<TKey,TValue) .
سادهترین امضاء عملگر ToDictionary، یک عبارت Lambda میباشد. این عبارت Lambda نشان دهندهی یک تابع است که عنصر کلید(Key) را در دیکشنری، مشخص میکند.
مثال:
class Recipe { public int Id { get; set; } public string Name { get; set; } public int Rating { get; set; } } IEnumerable<Recipe> recipes = new[] { new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 }, new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 }, new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 } }; Dictionary<int, Recipe> dict = recipes.ToDictionary(x => x.Id); foreach (KeyValuePair<int, Recipe> item in dict) { Console.WriteLine($"Key={item.Key}, Recipe={item.Value}"); }
خروجی مثال بالا:
Key=1, Recipe=Apple Pie Key=2, Recipe=Cherry Pie Key=3, Recipe=Beef Pie
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToLookup
این عملگر رفتاری شبیه به عملگر ToDictionary را دارد، اما به جای تولید خروجی از نوع دیکشنری، نمونهای از جنس ILookUp را ایجاد میکند.
در کد زیر خروجی ایجاد شده توسط lookup دستورالعملها (Recipes) را بر حسب امتیاز آنها گروه بندی کرده است. در این مثال کلید، بر حسب Byte میباشد.
مثال :
class Recipe { public int Id { get; set; } public string Name { get; set; } public byte Rating { get; set; } } IEnumerable<Recipe> recipes = new[] { new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 }, new Recipe { Id = 1, Name = "Banana Pie", Rating = 5 }, new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 }, new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 } }; ILookup<byte, Recipe> look = recipes.ToLookup(x => x.Rating); foreach (IGrouping<byte, Recipe> ratingGroup in look) { byte rating = ratingGroup.Key; Console.WriteLine($"Rating {rating}"); foreach (var recipe in ratingGroup) { Console.WriteLine($" - {recipe.Name}"); } }
Rating 5 - Apple Pie - Banana Pie Rating 2 - Cherry Pie Rating 3 - Beef Pie
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای عناصر Element Operators
این عملگرها، یک توالی ورودی را دریافت و تنها یک عنصر از توالی ورودی و یا یک عنصر را به عنوان عنصر پیش فرض باز میگردانند. این نوع عملگرها توالی خروجی را تولید نمیکنند.
عملگر First
این عملگر اولین عنصر توالی را باز میگرداند.
مثال :
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 = 500} }; Ingredient element = ingredients.First(); Console.WriteLine(element.Name);
Sugar
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 = 500} }; Ingredient element = ingredients.First(x=>x.Calories==150); Console.WriteLine(element.Name);
Milk
Unhandled Exception: System.InvalidOperationException: Sequence contains no elements
Ingredient[] ingredients = { }; Ingredient element = ingredients.First();
Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
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 = 500} }; Ingredient element = ingredients.First(x=>x.Calories==1500);
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر FirstOrDefault
عملگر FirstOrDefalt همانند عملگر First عمل میکند، اما با این تفاوت که به جای پرتاب یک استثناء در شرایط معرفی شده در عملگر First، یک مقدار پیش فرض را بر اساس نوع عناصر توالی باز میگرداند. در صورتیکه توالی از نوع عددی باشد، مقدار 0 و اگر عناصر توالی از انواع ارجاعی باشند، مقدار Null و برای مقادیر منطقی، ارزش False بهعنوان مقادیر پیش فرض باز گردانده میشوند.
Ingredient[] ingredients = { }; Ingredient element = ingredients.FirstOrDefault(); Console.WriteLine(element == null);
True
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 = 500} }; Ingredient element = ingredients.FirstOrDefault(x=>x.Calories==1500); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Last
این عملگر آخرین عنصر توالی را باز میگرداند. همچون عملگر First، این عملگر نیز یک امضاء برای دریافت یک عبارت شرط یا پیش بینی دارد. این پیش بینی، آخرین عنصری را که شرط را تامین کند، باز میگرداند. باز هم مثل عملگر First، در صورتی که توالی هیچ عنصری نداشته باشد و یا عدم تامین شرط توسط عناصر توالی، استثنایی رخ خواهد داد.
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 = 500} }; Ingredient element = ingredients.Last(x=>x.Calories==500); Console.WriteLine(element.Name);
Flour
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر LastOrDefault
این عملگر همچون عملگر FirstOrDefault عمل میکند. از بروز استثناء جلوگیری کرده و مقدار پیش فرض را به خروجی ارسال میکند.
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Single
عملگر Single ، تنها عنصر توالی ورودی را باز میگرداند.در صورتی که توالی ما بیش از یک عنصر داشته باشد و یا توالی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد.
Unhandled Exception: System.InvalidOperationException: Sequence contains more than one matching element Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 } }; Ingredient element = ingredients.Single(); Console.WriteLine(element.Name);
Sugar
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Butter", Calories = 150}, new Ingredient {Name = "Milk", Calories = 500} }; Ingredient element = ingredients.Single(x => x.Calories == 150); Console.WriteLine(element.Name);
Butter
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر SingleOrDefault
عملگر SingleOrDefault همچون عملگر Single عمل میکند؛ اما با این تفاوت که اگر توالی هیچ عنصری نداشته باشد، مقدار پیش فرض نوع توالی، باز گردانده میشود و در صورتیکه هیچ عنصری شرط مشخص شده را تامین نکند، باز هم مقدار پیش فرض توالی، به جای رخ دادن استثناء باز گردانده میشود.
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.SingleOrDefault(x => x.Calories == 9999); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ElementAt
عملگر ElementAt عنصری را در یک جایگاه مشخص شدهی در توالی، باز میگرداند.
مثال: در کد زیر سومین عنصر توالی ورودی انتخاب میشود:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.ElementAt(2); Console.WriteLine(element.Name);
Milk
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ElementAtOrDefualt
عملگر ElementAtOrDefualt نیز همچون عملگر ElementAt کار میکند؛ اما در صورت وارد کردن اندیسی بزرگتر از اندیس مجاز توالی، دیگر یک استثناء رخ نخواهد داد و یک مقدار پیش فرض، بر اساس نوع عناصر توالی باز گردانده میشود.
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.ElementAtOrDefault(5); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر DefaultIfEmpty
عملگر DefaultIfEmpty یک توالی را دریافت کرده و به دو شکل عمل میکند:
1- اگر توالی شامل حداقل یک عنصر باشد، این توالی بدون هیچ تغییری به خروجی ارسال میشود.
2- اگر توالی هیچ عنصری نداشته باشد، توالی خروجی خالی نخواهد بود. در این حالت توالی خروجی تنها یک عضو دارد و آن هم مقدار پیش فرضی بر اساس نوع توالی میباشد.
مثال :
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty(); foreach (Ingredient item in query) { Console.WriteLine(item.Name); }
Sugar Egg Milk
کد زیر حالت دوم معرفی شدهی در تعریف DefaultIfEmpty را نشان میدهد.
Ingredient[] ingredients = { }; IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty(); foreach (Ingredient item in query) { Console.WriteLine(item == null); }
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
let alphas = ["1","2","3","4"] ; // method 1 - using splice const index = alphas.findIndex(item=>item == "3"); // or use indexOf("3") if (index !== -1) { alphas.splice(index, 1); // array.splice(start, deleteCount) } // method 2 - using filter let newAlphas = alphas.filter(item => item !=="3");
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
آموزش LINQ بخش چهارم
انواع JOIN :
• Inner JOIN
• Group JOIN
• Left JOIN
کلاسهای زیر را در نظر بگیرید:
/// <summary> /// دستور العمل /// </summary> class Recipe { public int Id { get; set; } public string Name { get; set; } } /// <summary> /// بازخورد /// </summary> class Review { public int RecipeId { get; set; } public string ReviewText { get; set; } }
Inner Join
این دستور عنصری از توالی اول را که متناظر با آن عنصری در توالی دوم وجود داشته باشد، به خروجی میبرد.
مثال:
Recipe[] recipes = { new Recipe {Id = 1, Name = "Mashed Potato"}, new Recipe {Id = 2, Name = "Crispy Duck"}, new Recipe {Id = 3, Name = "Sachertorte"} }; Review[] reviews = { new Review {RecipeId = 1, ReviewText = "Tasty!"}, new Review {RecipeId = 1, ReviewText = "Not nice :("}, new Review {RecipeId = 1, ReviewText = "Pretty good"}, new Review {RecipeId = 2, ReviewText = "Too hard"}, new Review {RecipeId = 2, ReviewText = "Loved it"} }; var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId select new //anonymous type { RecipeName = recipe.Name, RecipeReview = review.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it
Group Join
بکارگیری into به همراه join، دستور Group Join را میسازد.
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup select new //anonymous type { RecipeName = recipe.Name, Reviews = reviewGroup//collection of related reviews }; foreach (var item in query) { Console.WriteLine($"Review for {item.RecipeName}"); foreach (var review in item.Reviews) { Console.WriteLine($"-{review.ReviewText}"); } }
متغیر reviewGroup توالی حاصل از اجرای join را نمایش میدهد. برای ایجاد توالی خروجی، نتیجه به یک نوع بی نام، بازتاب شده است. هر عنصر در نوع بی نام یک گروه را نشان میدهد. نوع بی نام شامل دو خصوصیت RecipeName که مقدار آن از توالی اول میآید و Reviews که حاصل خروجی Join است میباشد.
خروجی مثال بالا به شکل زیر است:
Review for Mashed Potato -Tasty! -Not nice :( -Pretty good Review for Crispy Duck -Too hard -Loved it Review for Sachertorte
Left outer join
در این مثال از مجموعههای تعریف شدهی در بخش اول مطلب استفاده کردهایم:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty() select new //anonymous type { RecipeName = recipe.Name, //RecipeReview = rg.ReviewText SystemNullException RecipeReview = (rg == null ? "n/a" : rg.ReviewText) }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
خروجی مثال فوق به شکل زیر است:
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-n/a
مثال:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" }) select new //anonymous type { RecipeName = recipe.Name, RecipeReview = rg.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-N/A