- Find the Code You’re Looking For: Enhanced search capabilities to help you quickly locate the code you need, even in the largest projects.
- More Meaningful Code Reviews: Improvements to code review workflows, making it easier to spot potential issues and collaborate with your team.
- Updates to pull request creation: Continual improvements to the pull request creation experience.
- Familiar keyboard shortcuts: Some common keyboard shortcuts now match those in other popular IDEs.
- AI-Generated Breakpoint Expressions: Automatically suggest breakpoints based on your code, helping you debug more efficiently.
- Understand Your Symbols: Improved symbol recognition to ensure you get the most accurate suggestions.
- Refined Suggestions: More precise and context-aware code completions, reducing the need for manual edits.
- GitHub Copilot is even more secure: GitHub Copilot Business customers to prevent specified files or repositories from being used to inform code completion suggestions made by GitHub Copilot.
یک نکتهی تکمیلی: به روز رسانی کتابخانهی UAParser
در این مطلب از کتابخانهی UAParser استفاده شد. این کتابخانه، چندسالی است که بهروز نشده؛ البته چون نیازی نبوده! در اصل، این کتابخانه از فایل yaml مخصوصی که به صورت جاسازی شده (embedded) در آن قرار دارد، برای شناسایی مرورگرها استفاده میکند (مفهوم استفاده از متد ()Parser.GetDefault همین مورد است). بنابراین یا باید خودتان این فایل yaml را دستی به روز کرده (کار مخزن کد فعال UAParser-Core، فقط همین یک مورد است) و سپس کتابخانه را مجددا کامپایل و استفاده کنید و یا میتوانید محتویات فایل yaml ذکر شده را دریافت و سپس با استفاده از متد Parser.FromYaml این کتابخانه، اطلاعات جدید دریافتی را پردازش و استفاده کنید؛ مانند UAParserService.
نوشتن Middleware سفارشی در ASP.NET Core
یک نکتهی تکمیلی: کار با اینترفیس IMiddleware جهت تعریف میانافزارهای سفارشی
اگر امروز قصد تعریف میانافزارهای سفارشی را دارید، بهتر است از روش باز public async Task Invoke(HttpContext context) که در این مطلب معرفی شد، دیگر استفاده نکنید؛ چون مهمترین محدودیتهای آن، داشتن طول عمر Singleton غیرقابل تغییر و همچنین عدم امکان پیاده سازی اینترفیس IDisposable در آن جهت پاکسازی خودکار منابع است. امروز روش توصیه شده، استفاده از اینترفیس IMiddleware است. در این حالت متد Task Invoke فوق، به متد مشخص و ثابت Task InvokeAsync(HttpContext context, RequestDelegate next) تغییر میکند. چون در این حالت دیگر نمیتوان پارامترهای این متد مشخص را مانند قبل که اینترفیسی را پیاده سازی نمیکرد، به صورت پویا کم و زیاد کرد، میتوان سرویسهای مدنظر را به سازندهی کلاس، تزریق کرد. به همین جهت نیاز است، آنرا به نحو زیر به سیستم تزریق وابستگیها معرفی کرد:
builder.Services.AddTransient<MyNewMiddleware>();
این الزام به تعریف آن به صورت یک سرویس رسمی، مزیتهای زیر را به همراه دارد:
الف) میتوان طول عمری، غیر از Singleton را هم در صورت نیاز، تعریف کرد (و مشکل کار با سرویسهایی با طول عمرهای غیر از Singleton کمتر میشود).
ب) چون طول عمر این میانافزار اکنون توسط سیستم تزریق وابستگیها مدیریت میشود، اگر این میانافزار اینترفیس IDisposable را پیاده سازی کند، کار پاکسازی منابع آن خودکار خواهد شد.
نکته 1: روش معرفی آن به سیستم تزریق وابستگیها، به صورت Concrete type است؛ یعنی اصل کلاس باید معرفی شود (مانند سطر فوق) و نه اینکه به صورت متداول زیر به همراه ذکر اینترفیس IMiddleware باشد:
builder.Services.AddTransient<IMiddleware, MyNewMiddleware>();
مابقی کار با آن، با میانافزارهای متداول، تفاوتی ندارد. یعنی قسمت UseMiddleware آن یکی است:
app.UseMiddleware<MyNewMiddleware>();
بنابراین این روش نسبت به روش متداول قبلی، دو تفاوت پیاده سازی اینترفیس مشخص IMiddleware و ثبت کلاس آن به صورت یک سرویس رسمی را دارد؛ مابقی نکات آن، مانند قبل است.
نکته 2: اگر از Scrutor برای ثبت خودکار سرویسهای برنامه استفاده میکنید، روش ثبت خودکار اینگونه سرویسها به صورت زیر و با استفاده از متد ()AsSelf است:
services.Scan(scan => scan.FromAssembliesOf(typeof(IDataSeedersRunner)) .AddClasses(classes => classes.Where(type => { var allInterfaces = type.GetInterfaces(); return allInterfaces.Contains(typeof(IMiddleware)) && allInterfaces.Contains(typeof(ISingletonService)); })) .AsSelf() .WithSingletonLifetime());
در این مثال، تمام IMiddleware هایی که با نشانگر ISingletonService هم مزین شدهاند، یافت شده و به صورت Concrete type هایی، با طول عمر Singleton، به سیستم تزریق وابستگیها اضافه میشوند.
معادل Storybook برای Blazor
The clone of "Storybook" for Blazor, a frontend workshop for building UI components and pages in isolation.
با اجرای این کوئری خاص به همراه AsNoTrackingWithIdentityResolution خطای زیر مشاهده میشود و اصلا کار نمیکند (چون فیلد JSON هم دارد):
System.InvalidOperationException: Invalid token type: 'StartObject'.
در EF-Core و در طی طول عمر «یک» Context:
- اگر یک کوئری معمولی، به همراه Change Tracker گرفته شود، اطلاعات اشیاء بازگشتی از آن، بر اساس ID آنها «کش موقت میشوند». اگر در ادامهی همین Context، مجددا این کوئری تکرار شود، این اشیاء مشابه با ID یکسان، نمونه سازی مجدد «نمیشوند» (هرچند یکبار دیگر از بانک اطلاعاتی دریافت شدهاند و این کش موقت، به معنای عدم واکشی اطلاعات از بانک اطلاعاتی نیست) و از همان کش محلی Change Tracker مجددا خوانده میشوند.
- اگر یک کوئری از نوع AsNoTracking گرفته شود، اشیاء بازگشتی از آن، بر اساس ID آنها «کش نمیشوند». اگر همین کوئری مجددا در طول عمر Context جاری تکرار شود، نمونه سازی «مجددی» از اشیاء مشابه با ID یکسان را شاهد «خواهیم بود» و EF آنها را از کش موقت Change Tracker جاری، بازیابی مجدد نمیکند. بنابراین تکرار این کوئری، مصرف حافظهی بیشتری را به همراه دارد؛ هرچند اگر فقط قرار است یکبار انجام شود (برای انجام یک عملیات گزارشگیری فقط خواندنی)، به دلیل نداشتن سیستم ردیابی، سربار کمتری را از حالت اول دارد.
- اگر یک کوئری از نوع AsNoTrackingWithIdentityResolution گرفته شود، اشیاء بازگشتی از آن بر اساس ID آنها «کش موقت میشوند». اگر همین کوئری مجددا در طول عمر Context جاری تکرار شود، نمونه سازی مجددی از اشیاء مشابه با ID یکسان را شاهد «نخواهیم بود» (هر چند کوئری از بانک اطلاعاتی، دادههایی را واکشی کردهاست)؛ اما وارد سیستم Tracking هم نمیشوند و تغییرات بر روی آنها ردیابی نمیشوند. به همین جهت این روش در حین تکرار کوئریهای مشابه در طول عمر یک Context نسبت به AsNoTracking، مصرف حافظهی کمتری دارد.
بنابراین وجود AsNoTrackingWithIdentityResolution فقط در طی طول عمر یک Conetxt و برای کاهش سربار نمونه سازی اشیاء مشابه با IDهای یکسان کوئریهای آن Context ارزشمند است و اگر Context شما از چندین کوئری مشابه تشکیل نمیشود، عملا کار بیشتری را انجام نمیدهد و تفاوتی با AsNoTracking ندارد.
آیا متد AsNoTrackingWithIdentityResolution را تست کردید که جواب میده یا نه؟
آزمایش بار برنامههای داتنت با k6
LINQPad بر روی macOS
LINQPad is coming to macOS! using AvaloniaUI XPF
چرا CSV هنوز مهم است؟
این پروژه با هدف آشنایی با دامین مربوط به قفلهای هوشمند و کنترل دسترسی به آنها انجام شده است. در سورس کد آن نحوه استفاده از امکانات Resource-based Authorization و Logical CQRS در کنار طراحی یک Rich Domain را می توانید مشاهده کنید. همچنین روش برقراری ارتباط با این قفلها از طریق پروتکل MQTT با استفاده از Emqx در آن تعبیه شده است.
نگارش بهروز «یک نکتهی تکمیلی: استفاده از DateTime.UtcNow و EF Core»
به صورت خلاصه اگر در بانک اطلاعاتی خود، اطلاعات زمانی را برای مثال توسط DateTime.UtcNow ذخیره میکنید، هرچند DateTimeKind آن به Utc تنظیم میشود، اما هنگام دریافت مقدار آن از بانک اطلاعاتی، این DateTimeKind بازیابی نشده (ADO.NET چنین قابلیتی را ندارد) و ... شاهد ناهماهنگیهایی خواهیم بود. برای یکدست سازی برنامه در این حالت میتوان از تبدیلگرهای زیر استفاده کرد (برای دو حالت DateTime و ?DateTime):
public class NullableDateTimeAsUtcValueConverter() : ValueConverter<DateTime?, DateTime?>( v => !v.HasValue ? v : ToUtc(v.Value), v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v) { private static DateTime? ToUtc(DateTime v) => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(); } public class DateTimeAsUtcValueConverter() : ValueConverter<DateTime, DateTime>( v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
که اینبار و در نگارشهای جدیدتر EF، روش معرفی سراسری آنها به صورت زیر ساده میشود:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { ArgumentNullException.ThrowIfNull(configurationBuilder); configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeAsUtcValueConverter>(); configurationBuilder.Properties<DateTime?>().HaveConversion<NullableDateTimeAsUtcValueConverter>(); }
یک نکتهی تکمیلی: تاثیر فراخوانی متد AsNoTracking بر روی کوئریهای خود ارجاعی
همانطور که در مطلب «مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code first» هم مشاهده کردید، خود EF، قابلیت تشکیل درخت نهایی خود ارجاع دهنده را دارد و به این ترتیب کوئری گرفتن از نتیجهی آن، بسیار ساده میشود. اما ... اگر در این بین، از متد AsNoTracking برای بهینه سازی، کاهش میزان حافظه و حذف پروکسیهای ردیابی تغییرات EF استفاده شود، دیگر این درخت خودکار، تشکیل نخواهد شد. برای پوشش این حالت میتوان به صورت زیر عمل کرد:
الف) تشکیل یک کلاس پایه برای تعریف سادهتر و مشخص رابطههای خود ارجاعی
public abstract class BaseEntity { public int Id { get; set; } } public abstract class BaseSelfReferencingEntity<TSelfEntity> : BaseEntity where TSelfEntity : BaseEntity { public virtual TSelfEntity? Reply { set; get; } public int? ReplyId { get; set; } public virtual ICollection<TSelfEntity>? Children { get; set; } }
که ساختار معرفی شدهی در اینجا، با توضیحات موجود در متن، انطباق دارد.
ب) پر کردن درخت نهایی حاصل به صورت دستی:
چون دیگر EF این درخت را برای ما تشکیل نمیدهد، اکنون باید خودمان کار تشکیل آنرا به صورت زیر انجام دهیم:
public static class SelfReferencingExtensions { public static List<TEntity> ToSelfReferencingTree<TEntity>(this ICollection<TEntity>? originalList) where TEntity : BaseSelfReferencingEntity<TEntity> { var results = new List<TEntity>(); if (originalList is null || originalList.Count == 0) { return results; } foreach (var rootItem in originalList.Where(x => !x.ReplyId.HasValue)) { results.Add(rootItem); AppendChildren(originalList, rootItem); } return results; } private static void AppendChildren<TEntity>(ICollection<TEntity> originalList, TEntity parentItem) where TEntity : BaseSelfReferencingEntity<TEntity> { foreach (var kid in originalList.Where(x => x.ReplyId.HasValue && x.ReplyId.Value == parentItem.Id)) { parentItem.Children ??= new List<TEntity>(); parentItem.Children.Add(kid); AppendChildren(originalList, kid); } } }
در اینجا کار تشکیل درخت نهایی، با استفاده از یک متد بازگشتی، انجام میشود.
پس از این مقدمات، نحوهی استفاده از آن به صورت زیر است:
var comments = await _comments.AsNoTracking() .Where(x => x.ParentId == postId) .OrderBy(x => x.Id) .Take(count) .ToListAsync(); var commentsTree = comments.ToSelfReferencingTree();
کوئری نویسی ابتدایی آن، کاملا استاندارد و بدون هیچگونه نکتهی خاصی است. ابتدا تمام نظرات یک مطلب (به صورت AsNoTracking) بازگشت داده میشوند و سپس متد ToSelfReferencingTree کار اتصالات نهایی درخت پاسخها را به صورت خودکار انجام میدهد.
یک نکتهی تکمیلی: امکان تعریف مسیریابی صفحات، با استفاده از ویژگی Route
عموما مسیریابیهای صفحات Blazor، به صورت زیر تعریف میشوند:
@page "/counter"
و اگر نیاز باشد تا این مسیر را در قسمتهای دیگری هم ذکر کنیم (برای مثال در لینکها و یا متد NavigateTo)، باید دقیقا همین مسیر و عبارت را در چندین قسمت برنامه تکرار کنیم. برای رفع این مشکل، با استفاده از ویژگی Route میتوان مسیریابی فوق را به صورت زیر بازنویسی کرد:
@attribute [Route(Constants.CounterRoute)]
در این حالت میتوان از مزیت تعریف مسیر مدنظر به صورت یک ثابت، به صورت زیر استفاده کرد:
public static class Constants { public const string CounterRoute = "/counter"; }
و اگر در قسمت دیگری از برنامه نیاز به ارجاعی به آن بود، میتوان همین رشتهی ثابت را مجددا مورد استفاده قرار داد:
NavigationManager.NavigateTo(Constants.CounterRoute);