معرفی کد آنالیزر Serilog
همانطور که میدانید Serilog قویترین و محبوبترین کتابخانه Logging در دات نت است. اگر از آن استفاده میکنید پیشنهاد میکنم افزونه و کتابخونه زیر رو هم نصب کنین
ابزار Serilog Analyzer یک آنالیزر roslyn-based برای Serilog بوده و خطاهای رایج و اشتباهات متداول به هنگام استفاده از Serilog را گوشزد کرده و اصلاح میکند.
درخواست مستندات
امکان نمونه سازی بدون قید و شرط کلاسها
تعریف کلاس Article1 را به صورت زیر درنظر بگیرید:
public class Article1 { public string Title { get; set; } public string? Subtitle { get; set; } public string Author { get; set; } public DateTime Published { get; set; } }
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618) Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
مدیریت کردن نحوهی نمونه سازی کلاسها، با وابستگی به سازندههای آن
یکی از روشهای مدیریت مشکلی که تا اینجا بررسی شد، تعریف سازندههای متعددی برای یک کلاس است؛ تا توسط آن بتوان مقدار دهی یک سری از خواص را اجباری کرد:
public class Article2 { public Article2(string title, string subtitle, string author, DateTime published) { Title = title; Subtitle = subtitle; Author = author; Published = published; } public Article2(string title, string author, DateTime published) { Title = title; Author = author; Published = published; } public string Title { get; set; } public string? Subtitle { get; set; } public string Author { get; set; } public DateTime Published { get; set; } }
- تعداد سطرهای تعریف این کلاس، به شدت افزایش یافتهاست.
- با اضافه شدن خواص بیشتری به کلاس، به تعاریف بیشتری نیاز خواهد بود.
- سازندهها کار خاصی را بجز نگاشت مقادیر ارائه شده، به خواص کلاس، انجام نمیدهند.
- نمونه سازی این کلاسها، شکل طولانی و غیرواضح زیر را پیدا میکند و زیبایی inline object initializers را ندارند:
Article2 article = new("C# 11 Required Keyword", "A new language feature", "Name", new DateTime(2022, 11, 12));
البته روش دیگر مدیریت یک چنین اخطارهایی، استفاده از مقدار ویژهی !default است که سبب محو اخطارهای یاد شده میشود؛ اما باز هم مقدار دهی آنرا الزامی نمیکند. فقط به این معنا است که قول میدهیم این خاصیت را در جای دیگری مقدار دهی کنیم و هیچگاه نال نباشد!
public string Title { get; set; } = default!;
مدیریت کردن نحوهی نمونه سازی کلاسها، بدون وابستگی به سازندههای آن در C# 11.0
C# 11 به همراه واژهی کلیدی جدیدی به نام required است تا دیگر نیازی نباشد همانند راه حل فوق، سازندههای متعددی را جهت اجبار به مقدار دهی خواص یک شیء، تعریف کنیم. در این حالت تعریف کلاس Article به صورت زیر خلاصه میشود و دیگر به همراه اخطارهای کامپایلر نمایش داده شده نیز نیست:
public class Article3 { public required string Title { get; set; } public string? Subtitle { get; set; } public required string Author { get; set; } public DateTime Published { get; set; } }
معرفی ویژگی جدید SetsRequiredMembers
کلاس Book زیر را که به همراه یک خاصیت required و دو سازندهاست، درنظر بگیرید:
public class Book { public Book() => Name = string.Empty; public Book(string name) => Name = name; public required string Name { get; set; } }
Book book = new("Book's Name");
Required member 'Book.Name' must be set in the object initializer or attribute constructor. [CS11Tests]csharp(CS9035)
public class Book { [SetsRequiredMembers] public Book() => Name = string.Empty; [SetsRequiredMembers] public Book(string name) => Name = name; public required string Name { get; set; } }
محدودیتهای کار با خواص required
- واژهی کلیدی required را میتوان تنها به خواص و فیلدهای نوعهای class, record, record struct اعمال کرد. امکان اعمال این واژهی کلیدی به اجزای یک اینترفیس وجود ندارد.
- میدان دید اعضای required باید حداقل در حد نوعهای دربرگیرندهی آنها باشند. برای مثال اگر کلاسی public است، نمیتوان در آن یک فیلد required با میدان دید protected را تعریف کرد.
- نوعهای مشتق شدهی از یک نوع پایه، نمیتوانند اعضای required آنرا مخفی کنند و اگر قصد بازنویسی آنرا دارند، باید حتما واژهی کلیدی required را لحاظ کنند.
- اگر سازندهای به سازندهی دیگری از طریق ذکر ()base و یا ()this زنجیر شده باشد نیز باید ویژگی SetsRequiredMembers مرتبط را تکرار کند.
با اجازه بنده می خواهم برای بلاگفا همچنین کاری انجام بدهم.
در مور تبدیلش به CHM می خواستم راهنمایی بگیرم
با کسب اجازه از ایده شما
متشکرم
تهیه یک بانک اطلاعاتی نمونه
برای نمایش امکانات کار با روش Database first، نیاز است یک بانک اطلاعاتی را به صورت مستقل و متداولی ایجاد کنیم. به همین جهت اسکریپت SQL ذیل را توسط Management studio اجرا کنید تا بانک اطلاعاتی BloggingCore2016، به همراه دو جدول به هم وابسته، در آن ایجاد شوند:
CREATE DATABASE [BloggingCore2016] GO USE [BloggingCore2016] GO CREATE TABLE [Blog] ( [BlogId] int NOT NULL IDENTITY, [Url] nvarchar(max) NOT NULL, CONSTRAINT [PK_Blog] PRIMARY KEY ([BlogId]) ); GO CREATE TABLE [Post] ( [PostId] int NOT NULL IDENTITY, [BlogId] int NOT NULL, [Content] nvarchar(max), [Title] nvarchar(max), CONSTRAINT [PK_Post] PRIMARY KEY ([PostId]), CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE ); GO INSERT INTO [Blog] (Url) VALUES ('https://www.dntips.ir/'), ('http://blogs.msdn.com/dotnet'), ('http://blogs.msdn.com/webdev'), ('http://blogs.msdn.com/visualstudio') GO
پیشنیازهای مهندسی معکوس ساختار بانک اطلاعاتی در EF Core
در قسمت اول در حین بررسی «برپایی تنظیمات اولیهی EF Core 1.0 در یک برنامهی ASP.NET Core 1.0»، چهار مدخل جدید را به فایل project.json برنامه اضافه کردیم. مدخل جدید Microsoft.EntityFrameworkCore.Tools که به قسمت tools آن اضافه شد، پیشنیاز اصلی کار با EF Core Migrations است. همچنین وجود مدخل Microsoft.EntityFrameworkCore.SqlServer.Design برای تدارک امکانات مهندسی معکوس ساختار یک بانک اطلاعاتی SQL Server ضروری است.
تبدیل ساختار دیتابیس BloggingCore2016 به کدهای معادل EF Core آن
پس از فعال سازی ابزارهای خط فرمان EF Core، به پوشهی اصلی پروژه مراجعه کرده، کلید shift را نگه دارید. سپس کلیک راست کرده و گزینهی Open command window here را انتخاب کنید تا خط فرمان از این پوشه آغاز شود. در ادامه دستور ذیل را صادر کنید:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
اجرا این دستور سبب اتصال به رشتهی اتصالی ذکر شده که به بانک اطلاعاتی BloggingCore2016 اشاره میکند، میشود. سپس پروایدر مدنظر ذکر شدهاست. سوئیچ o محل درج فایلهای نهایی را مشخص میکند. برای مثال در اینجا فایلهای نهایی مهندسی معکوس شده در پوشهی Entities درج میشوند (تصویر فوق). همچنین در اینجا امکان ذکر فایل context تولیدی نیز وجود دارد. اگر علاقمند باشید تا تمام ریز جزئیات این عملیات را نیز مشاهده کنید، میتوانید پارامتر اختیاری verbose را نیز به انتهای دستور اضافه نمائید.
بقیه مراحل کار با این فایلهای تولید شده، با نکاتی که تاکنون عنوان شدهاند یکی است. برای مثال اگر میخواهید رشتهی اتصالی پیش فرض را از این Context تولید شده خارج کنید:
public partial class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true"); }
بررسی پارامترهای دیگر ابزار مهندسی معکوس به Code First
اگر دستور dotnet ef dbcontext scaffold --help را صادر کنیم، خروجی راهنمای ذیل را میتوان مشاهده کرد:
Usage: dotnet ef dbcontext scaffold [arguments] [options] Arguments: [connection] The connection string of the database [provider] The provider to use. For example, Microsoft.EntityFrameworkCore.SqlServer Options: -a|--data-annotations Use DataAnnotation attributes to configure the model where possible. If omitted, the output code will use only the fluent API. -c|--context <name> Name of the generated DbContext class. -f|--force Force scaffolding to overwrite existing files. Otherwise, the code will only proceed if no output files would be overwritten. -o|--output-dir <path> Directory of the project where the classes should be output. If omitted, the top-level project directory is used. --schema <schema> Selects a schema for which to generate classes. -t|--table <schema.table> Selects a table for which to generate classes. -e|--environment <environment> The environment to use. If omitted, "Development" is used. -h|--help Show help information -v|--verbose Enable verbose output
- حالت پیش فرض تنظیمات روابط مدلها در این روش، حالت استفاده از Fluent API است. اگر میخواهید آنرا به حالت استفادهی از Data Annotations تغییر دهید، پارامتر a- و یا data-annotations-- را در دستور نهایی ذکر کنید.
- حالت پیش فرض تولید فایلهای نهایی این روش، عدم بازنویسی فایلهای موجود است. اگر میخواهید پس از تغییر بانک اطلاعاتی، مجددا این فایلها را از صفر تولید کنید، پارامتر f- و یا force- را در دستور نهایی ذکر کنید.
بنابراین اگر میخواهید هربار فایلهای نهایی را بازنویسی کنید و همچنین روش کار با Data Annotations را ترجیح میدهید، دستور نهایی، شکل زیر را پیدا خواهد کرد:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose --force --data-annotations
کار با یک بانک اطلاعاتی موجود، با روش مهاجرتهای Code First
فرض کنید میخواهید از یک بانک اطلاعاتی از پیش موجود EF 6.x (یا هر بانک اطلاعاتی از پیش موجود دیگری)، به روش پیش فرض EF Core استفاده کنید. برای این منظور:
- ابتدا جدول migration history قدیمی آنرا حذف کنید؛ چون ساختار آن با EF Core یکی نیست.
- سپس با استفاده از دستور dotnet ef dbcontext scaffold فوق، معادل کلاسها، روابط و Context سازگار با EF Core آنرا تولید کنید.
- در ادامه رشتهی اتصالی پیش فرض آنرا از کلاس Context تولیدی خارج کرده و از یکی از روشهای مطرح شدهی در مطلب «شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه» استفاده کنید.
- سپس نیاز است این Context جدید را توسط متد services.AddDbContext به لیست سرویسهای برنامه اضافه کنید. این مورد نیز در قسمت اول بررسی شدهاست.
- مرحلهی بعد، افزودن جدول __EFMigrationsHistory جدید EF Core، به این بانک اطلاعاتی است. برای این منظور به روش متداول فعال کردن مهاجرتها، دستور ذیل را صادر کنید:
dotnet ef migrations add InitialDatabase
using Microsoft.EntityFrameworkCore.Migrations; namespace Core1RtmEmptyTest.DataLayer.Migrations { public partial class InitialDatabase : Migration { protected override void Up(MigrationBuilder migrationBuilder) { } protected override void Down(MigrationBuilder migrationBuilder) { } } }
سپس دستور به روز رسانی بانک اطلاعاتی را صادر کنید:
dotnet ef database update
پس از این مرحله، روش کار، Code first خواهد بود. برای مثال خاصیتی را به کلاسی اضافه میکنید و سپس دو دستور ذیل را صادر خواهید کرد که در آن v2 یک نام دلخواه است:
dotnet ef migrations add v2 dotnet ef database update
گام سوم: افزودن یک button
... <div> <div> <input type="text" id="inputName" maxlength="15"> </div> <div> <button id="generateButton">Aye! Gimme a name!</button> </div> </div> ...
import 'dart:html'; ButtonElement genButton;
void main() { querySelector('#inputName').onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); }
... void setBadgeName(String newName) { querySelector('#badgeName').text = newName; }
... void generateBadge(Event e) { setBadgeName('Meysam Khoshbakht'); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { // To do: add some code here. } else { // To do: add some code here. } }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { genButton..disabled = false ..text = 'Aye! Gimme a name!'; } else { genButton..disabled = true ..text = 'Arrr! Write yer name!'; } }
گام چهارم: ایجاد کلاس PirateName
در این مرحله فقط کد مربوط به فایل dart را تغییر میدهیم. ابتدا کلاس PirateName را ایجاد میکنیم. با ایجاد نمونه ای از این کلاس، یک نام بصورت تصادفی انتخاب میشود و یا نامی بصورت اختیاری از طریق سازنده انتخاب میگردد.
نخست کتابخانه dart:math را به ابتدای فایل dart اضافه کنید
import 'dart:html'; import 'dart:math' show Random;
توضیحات
- با استفاده از کلمه کلیدی show، شما میتوانید فقط کلاسها، توابع و یا ویژگیهای مورد نیازتان را import کنید.
- کلاس Random یک عدد تصادفی را تولید میکند
در انتهای فایل کلاس زیر را تعریف کنید
... class PirateName { }
در داخل کلاس یک شی از کلاس Random ایجاد کنید
class PirateName { static final Random indexGen = new Random(); }
توضیحات
- با استفاده از static یک فیلد را در سطح کلاس تعریف میکنیم که بین تمامی نمونههای ایجاد شده از کلاس مشترک میباشد
- متغیرهای final فقط خواندنی میباشند و غیر قابل تغییر هستند.
- با استفاده از new میتوانیم سازنده ای را فراخوانی نموده و نمونه ای را از کلاس ایجاد کنیم
دو فیلد دیگر از نوع String و با نامهای _firstName و _appelation به کلاس اضافه میکنیم
class PirateName { static final Random indexGen = new Random(); String _firstName; String _appellation; }
متغیرهای خصوصی با (_) تعریف میشوند. Dart کلمه کلیدی private را ندارد.
دو لیست static به کلاس فوق اضافه میکنیم که شامل لیستی از name و appelation میباشد که میخواهیم آیتمی را بصورت تصادفی از آنها انتخاب کنیم.
class PirateName { ... static final List names = [ 'Anne', 'Mary', 'Jack', 'Morgan', 'Roger', 'Bill', 'Ragnar', 'Ed', 'John', 'Jane' ]; static final List appellations = [ 'Jackal', 'King', 'Red', 'Stalwart', 'Axe', 'Young', 'Brave', 'Eager', 'Wily', 'Zesty']; }
کلاس List میتواند شامل مجموعه ای از آیتمها میباشد که در Dart تعریف شده است.
سازنده ای را بصورت زیر به کلاس اضافه میکنیم
class PirateName { ... PirateName({String firstName, String appellation}) { if (firstName == null) { _firstName = names[indexGen.nextInt(names.length)]; } else { _firstName = firstName; } if (appellation == null) { _appellation = appellations[indexGen.nextInt(appellations.length)]; } else { _appellation = appellation; } } }
توضیحات
- سازنده تابعی همنام کلاس میباشد
- پارامترهایی که در {} تعریف میشوند اختیاری و Named Parameter میباشند. Named Parameterها پارمترهایی هستند که جهت مقداردهی به آنها در زمان فراخوانی، از نام آنها استفاده میشود.
- تابع nextInt() یک عدد صحیح تصادفی جدید را تولید میکند.
- جهت دسترسی به عناصر لیست از [] و شمارهی خانهی لیست استفاده میکنیم.
- ویژگی length تعداد آیتمهای موجود در لیست را بر میگرداند.
در این مرحله یک getter برای دسترسی به pirate name ایجاد میکنیم
class PirateName { ... String get pirateName => _firstName.isEmpty ? '' : '$_firstName the $_appellation'; }
توضیحات
- Getterها متدهای خاصی جهت دسترسی به یک ویژگی به منظور خواندن مقدار آنها میباشند.
- عملگر سه گانه :? دستور میانبر عبارت شرطی if-else میباشد
- $ یک کاراکتر ویژه برای رشتههای موجود در Dart میباشد و میتواند محتوای یک متغیر یا ویژگی را در رشته قرار دهد. در رشته '$_firstName the $_appellation' محتوای دو ویژگی _firstName و _appellation در رشته قرار گرفته و نمایش مییابند.
- عبارت (=> expr;) یک دستور میانبر برای { return expr; } میباشد.
تابع setBadgeName را بصورت زیر تغییر دهید تا یک پارامتر از نوع کلاس PirateName را به عنوان پارامتر ورودی دریافت نموده و با استفاده از Getter مربوط به ویژگی pirateName، مقدار آن را در badge name نمایش دهد.
void setBadgeName(PirateName newName) { querySelector('#badgeName').text = newName.pirateName; }
تابع updateBadge را بصورت زیر تغییر دهید تا یک نمونه از کلاس PirateName را با توجه به مقدار ورودی کاربر در فیلد input تولید نموده و تابع setBadgeName رافراخوانی نماید. همانطور که در کد مشاهده میکنید پارامتر ورودی اختیاری firstName در زمان فراخوانی با ذکر نام پارامتر قبل از مقدار ارسالی نوشته شده است. این همان قابلیت Named Parameter میباشد.
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(new PirateName(firstName: inputName)); ... }
تابع generateBadge را بصورت زیر تغییر دهید تا به جای نام ثابت Meysam Khoshbakht، از کلاس PirateName به منظور ایجاد نام استفاده کند. همانطور که در کد میبینید، سازندهی بدون پارامتر کلاس PirateName فراخوانی شده است.
void generateBadge(Event e) { setBadgeName(new PirateName()); }
همانند گام سوم برنامه را اجرا کنید و نتیجه را مشاهده نمایید.
Roslyn #1
PM> Uninstall-package Microsoft.CodeDom.Providers.DotNetCompilerPlatform PM> Uninstall-package Microsoft.Net.Compilers
<Target Name="CopyRoslynFiles" AfterTargets="AfterBuild" Condition="!$(Disable_CopyWebApplication) And '$(OutDir)' != '$(OutputPath)'"> <ItemGroup> <RoslynFiles Include="$(CscToolPath)\*" /> </ItemGroup> <MakeDir Directories="$(WebProjectOutputDir)\bin\roslyn" /> <Copy SourceFiles="@(RoslynFiles)" DestinationFolder="$(WebProjectOutputDir)\bin\roslyn" SkipUnchangedFiles="true" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" /> </Target>
<system.codedom> <compilers> <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701"></compiler> <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"></compiler> </compilers> </system>
هزینه استفاده از دات نت فریم ورک چقدر است؟
در مورد قیمت VS یکی از بچهها صحبت خوبی داشت؛ اگر قرار بود ما VS را خریداری کنیم هرسال یا هر چند سال یکبار این کار را میکردیم و مایکروسافت با توجه به Optionهای خوبی که برای اینطور خرید کردن دارد (Renew و ...)، میتوان VS Ultimate که اگر اشتباه نکنم 11 هزار دلار است را با 3 هزار دلار تهیه کنیم. وقتی همه حقوق مولفین رعایت شود مطمئناً 3 هزار دلار برای یک برنامه نویس متوسط به بالا مبلغ بسیار مناسبی خواهد بود. مشترک MSDN بودن هم که شامل سیستمعاملها و ... هم خواهد شد.
هزینه استفاده از دات نت فریم ورک چقدر است؟
در مورد قیمت VS یکی از بچهها صحبت خوبی داشت؛ اگر قرار بود ما VS را خریداری کنیم هرسال یا هر چند سال یکبار این کار را میکردیم و مایکروسافت با توجه به Optionهای خوبی که برای اینطور خرید کردن دارد (Renew و ...)، میتوان VS Ultimate که اگر اشتباه نکنم 11 هزار دلار است را با 3 هزار دلار تهیه کنیم. وقتی همه حقوق مولفین رعایت شود مطمئناً 3 هزار دلار برای یک برنامه نویس متوسط به بالا مبلغ بسیار مناسبی خواهد بود. مشترک MSDN بودن هم که شامل سیستمعاملها و ... هم خواهد شد.