سری 6 قسمتی در رابطه با User Stories
ثبت رکورد جدید به جای بروزرسانی آن
پیشنیازهای این سری
در این سری از بانک اطلاعاتی استاندارد مثال به همراه SQL Server 2016، به نام WideWorldImporters استفاده میکنیم. برای دریافت آن، به قسمت releases مثالهای مایکروسافت مراجعه کرده و فایل WideWorldImporters-Full.bak را دریافت کنید. پس از دریافت این فایل، برای restore سریع آن، میتوانید دستور زیر را اجرا کنید که در آن باید مسیر فایل bak دریافتی و همچنین مسیر ایجاد فایلهای mdf/ldf/ndf را مطابق مسیرهای سیستم خودتان اصلاح نمائید (فقط مسیر پوشهها را نیاز است تغییر دهید):
use master; RESTORE DATABASE WideWorldImporters FROM disk='D:\path\WideWorldImporters-Full.bak' WITH MOVE 'WWI_Primary' TO 'D:\SQL_Data\WideWorldImporters.mdf', MOVE 'WWI_Log' TO 'D:\SQL_Data\WideWorldImporters_log.ldf', MOVE 'WWI_UserData' TO 'D:\SQL_Data\WideWorldImporters_UserData.ndf', MOVE 'WWI_InMemory_Data_1' TO 'D:\SQL_Data\WideWorldImporters_InMemory_Data_1'
یافتن اطلاعاتی در مورد کوئریها
SQL Server زمانیکه یک کوئری را اجرا میکند، اطلاعاتی را نیز به همراه آن تولید خواهد کرد که سبب ایجاد یک Query Plan میشود و در آن، اطلاعاتی مانند جداول مورد استفاده، نوع جوینها، ایندکسهای استفاده شده و غیره وجود دارند. علاوه بر آن، Query Statistics نیز قابل دسترسی هستند که در آن مدت زمان اجرای یک کوئری، میزان I/O صورت گرفته و میزان مصرف CPU کوئری، ذکر میشوند. برای دسترسی یافتن به این اطلاعات، میتوان به اشیاء مختلف SQL Server مراجعه کرد؛ مانند dynamic management objects یا به اختصار DMO's، همچنین extended events، traces، query stores و یا حتی management studio. مهمترین تفاوت اینها نیز در نحوهی دسترسی به اطلاعات آنها است که میتواند زنده (live) و یا ذخیره شده در جائی باشند. در اینجا تنها منبعی که امکان مشاهدهی این اطلاعات را به صورت زنده میسر میکند، management studio است. البته live در اینجا به معنای امکان مشاهدهی تمام اطلاعات مرتبط با یک کوئری، مانند آمار و کوئری پلن آن در داخل محیط management studio، پس از اجرای یک کوئری است. در این قسمت بیشتر به روش استخراج اطلاعات آماری کوئریهای زنده میپردازیم و در قسمتهای بعدی، سایر گزینههای نامبرده شده را نیز بررسی خواهیم کرد.
مشاهدهی زندهی دادههای مرتبط با اجرای یک کوئری در management studio
پس از restore بانک اطلاعاتی مثال WideWorldImporters که عنوان شد، در برنامهی Microsoft SQL Server Management Studio، کوئری زیر را اجرا میکنیم:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
اینجا است که نیاز به اطلاعات بیشتری در مورد نحوهی اجرای این کوئری داریم. برای استخراج این اطلاعات، اینبار گزینههای تولید و جمع آوری اطلاعات آماری IO و TIME را روشن میکنیم و سپس همان کوئری قبلی را اجرا خواهیم کرد:
USE [WideWorldImporters]; GO SET STATISTICS IO ON; GO SET STATISTICS TIME ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 504 ms. (53 rows affected) Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 10 ms.
استخراج اطلاعات Actual Execution Plan یک کوئری
کوئری را زیر با فرض IO ON و TIME ON حاصل از اجرای کوئری قبل، اجرا میکنیم:
USE [WideWorldImporters]; GO SET STATISTICS XML ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO SET STATISTICS XML OFF; GO
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 7 ms. (53 rows affected) Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. (1 row affected) SQL Server Execution Times: CPU time = 15 ms, elapsed time = 179 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
اگر بر روی این XML کلیک کنیم، برگهی جدید نمایش گرافیکی این plan ظاهر میشود:
با کلیک راست بر روی این برگه، میتوان اطلاعات آنرا جهت بررسیهای بعدی و یا به اشتراک گذاری آن ذخیره کرد.
در این plan اگر اشارهگر ماوس را بر روی هر کدام از عناصر آن حرکت دهیم، اطلاعاتی مانند actual number of rows نیز مشاهده میشود، در کنار اطلاعات تخمینی؛ به همین جهت به آن Actual Execution Plan هم گفته میشود.
این یک روش دسترسی به Execution Plan است. روش دوم آن با استفاده از امکانات رابط کاربری خود Management Studio است؛ با فشردن دکمههای Ctrl+M و یا انتخاب گزینهی Include actual execution plan از منوی Query آن. پس از آن کوئری زیر را اجرا کنید:
SET STATISTICS IO ON; GO SET STATISTICS TIME ON; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
استخراج اطلاعات Estimated Execution Plan یک کوئری
تا اینجا نحوهی استخراج اطلاعات Actual Execution Plan را بررسی کردیم که به همراه اطلاعات دقیق حاصل از اجرای کوئری نیز بود؛ مانند actual number of rows. نوع دیگری از Execution Planها را نیز میتوان از SQL Server درخواست کرد که به آنها Estimated Execution Plan گفته میشود و حاصل اجرای کوئری نیستند؛ بلکه تخمینی هستند از روش اجرای این کوئری توسط SQL Server. برای فعالسازی محاسبهی آن، ابتدا کوئری زیر را در management studio انتخاب کنید:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
همانطور که مشاهده میکنید، اینبار نتیجهی حاصل، به همراه اطلاعاتی مانند actual number of rows نیست و صرفا تخمینی است از روش اجرای این کوئری، توسط SQL Server.
جمع آوری اطلاعات آماری کلاینتها
در منوی Query، گزینهای تحت عنوان Include client statistics نیز وجود دارد. با انتخاب آن، اگر کوئری زیر را اجرا کنیم:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
در اینجا مشخص میشود که آیا عملیات insert/update/delete انجام شدهاست. چه تعداد ردیف تحت تاثیر اجرای این کوئری قرار گرفتهاند. چه تعداد تراکنش انجام شدهاست. همچنین اطلاعات آماری شبکه و زمان نیز در اینجا ارائه شدهاند.
در همین حالت، کوئری جدید زیر را با تغییر قسمت where کوئری قبلی، اجرا کنید:
SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [s].[StateProvinceName] LIKE 'O%'; GO
در اینجا حداکثر 10 کوئری را میتوان با هم مقایسه کرد و بیشتر از آن سبب حذف موارد قدیمی از لیست میشود.
عدم نمایش ردیفهای بازگشت داده شدهی توسط کوئری در حین جمع آوری اطلاعات آماری
هربار اجرای یک کوئری در management studio، به همراه بازگشت و نمایش ردیفهای مرتبط با آن کوئری نیز میباشد. اگر میخواهید در حین بررسی کارآیی کوئریها از نمایش این ردیفها صرف نظر کنید (تا بار این برنامه کاهش یابد)، میتوانید از منوی Query، گزینهی Query Options را انتخاب کرده و در قسمت Results، گزینهی Grid آن، گزینهی discard results after execution را انتخاب کنید تا دیگر برگهی results نمایش داده نشود و وقت و منابع را تلف نکند. بدیهی است پس از پایان کار بررسی آماری، نیاز به عدم انتخاب این گزینه خواهد بود.
کتابخانه TimezZ
Fast timer plugin for countdown and count forward Demo
With this plugin you can easily put a timer on your site, it works both ways. You can use two version, one version is the version for modern browsers with the standards of the ES2017 and the version for old browsers with ES2015 standards. Using the config you can change the tags as letters and numbers, you can also change the text output next to the numbers.
خلاصهای از روشهای کار با کوکیها در ASP.NET Core
ایجاد یک کوکی جدید
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Core1RtmEmptyTest.Controllers { public class TestCookiesController : Controller { public IActionResult Index() { this.Response.Cookies.Append("key", "value", new CookieOptions { HttpOnly = true, Path = this.Request.PathBase.HasValue ? this.Request.PathBase.ToString() : "/", Secure = this.Request.IsHttps }); return Content("OK!"); } } }
همانطور که در تصویر نیز مشخص است، طول عمر این کوکی، به سشن تنظیم شدهاست و با پایان سشن جاری مرورگر (بسته شدن کل مرورگر)، این کوکی نیز غیرمعتبر شده و به صورت خودکار حذف خواهد شد. برای تعیین عمر دقیق یک کوکی میتوان از خاصیت Expires شیء CookieOptions که در مثال فوق مقدار دهی نشدهاست، استفاده کرد؛ مانند:
Expires = DateTimeOffset.UtcNow.AddDays(2)
خواندن محتویات کوکی ذخیره شده
پس از ثبت کوکی در Response، خواندن آن در Request بعدی به شکل زیر است:
var value = this.Request.Cookies["key"];
شیء this.Request.Cookies از نوع IRequestCookieCollection است:
public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>, IEnumerable { string this[string key] { get; } ICollection<string> Keys { get; } bool ContainsKey(string key); bool TryGetValue(string key, out string value); }
در مستندات آن عنوان شدهاست که در حالت استفادهی از indexer، درصورت یافت نشدن کلید، string.Empty بازگشت داده میشود (که آزمایشات null را نمایش میدهند). اما در حالت استفادهی از TryGetValue بر اساس خروجی bool آن دقیقا میتوان مشخص کرد که آیا این کوکی وجود داشتهاست یا خیر.
در اینجا همچنین متد ContainsKey نیز جهت بررسی وجود یک کلید، در مجموعهی کلیدها نیز پیش بینی شدهاست.
بنابراین بهتر است جهت یافتن مقادیر کوکیها از روش ذیل استفاده کرد:
string cookieValue; if (this.Request.Cookies.TryGetValue(key, out cookieValue)) { // TODO: use the cookieValue } else { // this cookie doesn't exist. }
حذف کوکیهای موجود
در اینجا متد Delete نیز پیش بینی شدهاست که باید بر روی کوکیهای Response فراخوانی شود:
this.Response.Cookies.Delete("key");
همانطور که در تصویر نیز مشخص است، در صورت عدم تنظیم CookieOptions، این کوکی جدید اضافه شده، دارای تاریخ انقضای 1970 است که سبب خواهد شد تا توسط مرورگر، غیرمعتبر درنظر گرفته شده و حذف شود.
طراحی یک تامین کنندهی کوکیهای امن
پس از آشنایی با مقدمات کوکیها و همچنین «بررسی تغییرات رمزنگاری اطلاعات در NET Core.»، اکنون میتوان یک تامین کنندهی کوکیهای رمزنگاری شده را برای ASP.NET Core به نحو ذیل طراحی کرد:
public interface ISecureCookiesProvider { void Add(HttpContext context, string token, string value); bool Contains(HttpContext context, string token); string GetValue(HttpContext context, string token); void Remove(HttpContext context, string token); } public class SecureCookiesProvider : ISecureCookiesProvider { private readonly IProtectionProvider _protectionProvider; public SecureCookiesProvider(IProtectionProvider protectionProvider) { _protectionProvider = protectionProvider; } public void Add(HttpContext context, string token, string value) { value = _protectionProvider.Encrypt(value); context.Response.Cookies.Append(token, value, getCookieOptions(context)); } public bool Contains(HttpContext context, string token) { return context.Request.Cookies.ContainsKey(token); } public string GetValue(HttpContext context, string token) { string cookieValue; if (!context.Request.Cookies.TryGetValue(token, out cookieValue)) { return null; } return _protectionProvider.Decrypt(cookieValue); } public void Remove(HttpContext context, string token) { if (context.Request.Cookies.ContainsKey(token)) { context.Response.Cookies.Delete(token); } } /// <summary> /// Expires at the end of the browser's session. /// </summary> private CookieOptions getCookieOptions(HttpContext context) { return new CookieOptions { HttpOnly = true, Path = context.Request.PathBase.HasValue ? context.Request.PathBase.ToString() : "/", Secure = context.Request.IsHttps }; } }
- در این تامین کنندهی کوکیهای امن، IProtectionProvider تزریقی به سازندهی کلاس را در مطلب «تغییرات رمزنگاری اطلاعات در NET Core.» پیشتر ملاحظه کردهاید.
- در اینجا برای ثبت سرویس جدید، تنظیمات ابتدایی برنامه چنین شکلی را پیدا میکنند و پس از آن میتوان سرویس ISecureCookiesProvider را به کنترلرهای برنامه تزریق و استفاده کرد:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IProtectionProvider, ProtectionProvider>(); services.TryAddSingleton<ISecureCookiesProvider, SecureCookiesProvider>();
کتابچه آموزش LINQ
Dear developer friends, I wanted to share with you a book that particularly amazed me. This book teaches you LINQ like you never have before and all in just 35 pages. I present to you Steven Giesel‘s book “LINQ Explained with sketches”.
We kindly ask you for your valuable input on the priorities for modernizing investments that the WPF team should focus on, as outlined in the roadmap document for 2023.
Please use thumbs up (no other emoji responses will be considered to avoid duplication) on options below which you would like us to prioritize.
C# 8.0 - Nullable Reference Types
تغییرات نحوهی تعریف موجودیتها در C# 8.0
تا پیش از C# 8.0، برای تعریف فیلدهای نال نپذیر و نال پذیر در موجودیتهای EF Core، به صورت زیر عمل میشد:
public class Person_BeforeCS8 { [Required] public string FirstName { get; set; } // NOT NULL public string MiddleName { get; set; } // NULL }
public class Person_AfterCS8 { public string FirstName { get; set; } = null!; // NOT NULL public string? MiddleName { get; set; } // NULL }
مزیت این روش نسبت به Person_BeforeCS8 این است که اینبار علاوه بر نالنپذیر تعریف شدن فیلد FirstName، نحوهی استفادهی از آن در برنامه (عدم انتساب نال به آن) نیز تحت کنترل کامپایلر قرار میگیرد که پیشتر با ویژگی Required چنین امری میسر نبود.
بنابراین در موجودیتهای برنامهی مبتنی بر C# 8.0، دیگر نیاز به استفادهی از ویژگی Required نبوده و نالپذیری با عملگر ? مشخص میشود.
کار با وابستگیها و ارتباطهای نالپذیر
فرض کنید یک چنین کوئری را در EF Core 3.0 و C# 8.0 نوشتهاید:
var parentPosts = db.Posts.Where(p => p.ParentPost.Id == postId).ToList();
var parentPosts = db.Posts.Where(p => p.ParentPost!.Id == postId).ToList();