معمولاً تنها در موارد حیاتی یا غیرقابل بازگشت مثل پرداختها یا provisioning حسابها به این میزان از اطمینان نیاز است. 💳
این تصمیم بستگی به سطح اطمینان مورد نیاز دارد:
اگر تکرار عملیات عواقب واقعی دارد (مالی یا دادهای)، باید idempotency را صریحاً اعمال کنید.
در غیر این صورت، retry کردن عملیات ممکن است قابلقبول باشد.
همهی consumerها به سربار بررسیهای idempotency نیاز ندارند.
اگر عملیات شما بهطور طبیعی idempotent است، میتوانید از جدول اضافه و تراکنش صرفنظر کنید.
بهعنوان مثال:
بهروزرسانی projectionها 📊
تنظیم flag وضعیت ✅
یا refresh کردن cache 🧩
همگی نمونههایی از عملیات deterministic هستند که اجرای چندبارهی آنها خطری ندارد.
مثل:
«تنظیم وضعیت کاربر روی Active» یا «بازسازی Read Model» — اینها state را بازنویسی میکنند نه اینکه چیزی به آن اضافه کنند.
برخی از handlerها هم از Precondition Check برای جلوگیری از تکرار استفاده میکنند.
اگر handler در حال بهروزرسانی یک entity باشد، ابتدا میتواند بررسی کند که آیا entity در وضعیت مورد نظر هست یا نه؛ اگر هست، بهسادگی return کند.
این محافظ ساده در بسیاری موارد کافی است.
⚠️ الگوی Idempotent Consumer را بدون فکر در همهجا اعمال نکنید.
فقط در جایی از آن استفاده کنید که از آسیب واقعی (مالی یا ناسازگاری دادهای) جلوگیری کند.
برای سایر موارد، سادگی بهتر است.
سیستمهای توزیعشده ذاتاً غیرقابلپیشبینی هستند. ⚙️ Retryها، پیامهای تکراری (duplicates) و خرابیهای جزئی (partial failures) بخش طبیعی عملکرد آنها محسوب میشوند.
نمیتوانی از وقوعشان جلوگیری کنی، اما میتوانی سیستم را طوری طراحی کنی که کمترین تأثیر را از آنها بگیرد. 💪
از قابلیت Message Deduplication داخلی در Broker خود استفاده کن تا پیامهای تکراری از سمت Producer حذف شوند.
در سمت Consumer، الگوی Idempotent Consumer Pattern را اعمال کن تا مطمئن شوی Side Effectها فقط یکبار رخ میدهند حتی در صورت Retry شدن پیامها. 🔁
همیشه رکورد پیامهای پردازششده و اثر واقعی آنها را در یک تراکنش واحد ذخیره کن.
این کار کلید حفظ Consistency در سیستم توزیعشده است. 🧱
نه هر Message Handlerی نیاز به این الگو دارد.
اگر Consumer شما ذاتاً Idempotent است یا میتواند با یک Precondition ساده پردازش را زود متوقف کند، نیازی به پیچیدگی اضافی نیست. 🚫
اما هرجایی که عملیات باعث تغییر Persistent State یا فراخوانی سیستمهای خارجی میشود، Idempotency دیگر یک انتخاب نیست — بلکه تنها راه تضمین Consistency است. ✅
سیستم خود را طوری بساز که Retryها را تحمل کند.
در این صورت، سیستم توزیعشدهات بسیار قابلاعتمادتر خواهد شد. 🔐
نکتهی جالب اینجاست که وقتی این اصل را واقعاً درک میکنی، آن را در همهی سیستمهای واقعی دنیا میبینی. 🌍
امیدوارم این مطلب برات مفید بوده باشه 💙
⚖️ The Trade-Off
این تصمیم بستگی به سطح اطمینان مورد نیاز دارد:
اگر تکرار عملیات عواقب واقعی دارد (مالی یا دادهای)، باید idempotency را صریحاً اعمال کنید.
در غیر این صورت، retry کردن عملیات ممکن است قابلقبول باشد.
🧠 When Idempotent Consumer Isn’t Needed
همهی consumerها به سربار بررسیهای idempotency نیاز ندارند.
اگر عملیات شما بهطور طبیعی idempotent است، میتوانید از جدول اضافه و تراکنش صرفنظر کنید.
بهعنوان مثال:
بهروزرسانی projectionها 📊
تنظیم flag وضعیت ✅
یا refresh کردن cache 🧩
همگی نمونههایی از عملیات deterministic هستند که اجرای چندبارهی آنها خطری ندارد.
مثل:
«تنظیم وضعیت کاربر روی Active» یا «بازسازی Read Model» — اینها state را بازنویسی میکنند نه اینکه چیزی به آن اضافه کنند.
برخی از handlerها هم از Precondition Check برای جلوگیری از تکرار استفاده میکنند.
اگر handler در حال بهروزرسانی یک entity باشد، ابتدا میتواند بررسی کند که آیا entity در وضعیت مورد نظر هست یا نه؛ اگر هست، بهسادگی return کند.
این محافظ ساده در بسیاری موارد کافی است.
⚠️ الگوی Idempotent Consumer را بدون فکر در همهجا اعمال نکنید.
فقط در جایی از آن استفاده کنید که از آسیب واقعی (مالی یا ناسازگاری دادهای) جلوگیری کند.
برای سایر موارد، سادگی بهتر است.
🧭 Takeaway
سیستمهای توزیعشده ذاتاً غیرقابلپیشبینی هستند. ⚙️ Retryها، پیامهای تکراری (duplicates) و خرابیهای جزئی (partial failures) بخش طبیعی عملکرد آنها محسوب میشوند.
نمیتوانی از وقوعشان جلوگیری کنی، اما میتوانی سیستم را طوری طراحی کنی که کمترین تأثیر را از آنها بگیرد. 💪
از قابلیت Message Deduplication داخلی در Broker خود استفاده کن تا پیامهای تکراری از سمت Producer حذف شوند.
در سمت Consumer، الگوی Idempotent Consumer Pattern را اعمال کن تا مطمئن شوی Side Effectها فقط یکبار رخ میدهند حتی در صورت Retry شدن پیامها. 🔁
همیشه رکورد پیامهای پردازششده و اثر واقعی آنها را در یک تراکنش واحد ذخیره کن.
این کار کلید حفظ Consistency در سیستم توزیعشده است. 🧱
نه هر Message Handlerی نیاز به این الگو دارد.
اگر Consumer شما ذاتاً Idempotent است یا میتواند با یک Precondition ساده پردازش را زود متوقف کند، نیازی به پیچیدگی اضافی نیست. 🚫
اما هرجایی که عملیات باعث تغییر Persistent State یا فراخوانی سیستمهای خارجی میشود، Idempotency دیگر یک انتخاب نیست — بلکه تنها راه تضمین Consistency است. ✅
سیستم خود را طوری بساز که Retryها را تحمل کند.
در این صورت، سیستم توزیعشدهات بسیار قابلاعتمادتر خواهد شد. 🔐
نکتهی جالب اینجاست که وقتی این اصل را واقعاً درک میکنی، آن را در همهی سیستمهای واقعی دنیا میبینی. 🌍
امیدوارم این مطلب برات مفید بوده باشه 💙
🔖هشتگها:
#IdempotentConsumer #Idempotency #DistributedSystems #MessageBroker
⚖️ مقیاسپذیری افقی (Horizontally Scaling) در ASP.NET Core APIs با استفاده از YARP Load Balancingاپلیکیشنهای وب مدرن باید بتوانند تعداد فزایندهای از کاربران را سرویسدهی کنند و افزایش ناگهانی ترافیک را مدیریت نمایند.
زمانی که یک سرور به حد نهایی ظرفیت خود میرسد، عملکرد آن کاهش یافته و منجر به کندی پاسخها، خطاها یا حتی از کار افتادن کامل (downtime) میشود.
Load Balancing
یک تکنیک کلیدی برای مقابله با این چالشها و بهبود Scalability اپلیکیشن شما است. ⚙️
در این مقاله بررسی خواهیم کرد:
• چگونه از YARP (Yet Another Reverse Proxy) برای پیادهسازی Load Balancing استفاده کنیم
• چگونه از Horizontal Scaling برای بهبود عملکرد بهره ببریم
• چگونه از K6 بهعنوان ابزار Load Testing استفاده کنیم
در ادامه، به مفهوم Load Balancing، اهمیت آن و نحوهای که YARP این فرآیند را برای اپلیکیشنهای NET. سادهتر میکند، خواهیم پرداخت.
🧱 انواع مقیاسپذیری نرمافزار (Types of Software Scalability)
قبل از اینکه وارد جزئیات YARP و Load Balancing شویم، بیایید اصول اولیهی Scaling را مرور کنیم.
دو رویکرد اصلی برای مقیاسپذیری وجود دارد:
🔹 Vertical Scaling
در این روش، سرورهای موجود با سختافزار قویتر ارتقا مییابند — افزایش تعداد هستههای CPU، حافظه RAM و فضای ذخیرهسازی سریعتر.
اما این روش چند محدودیت دارد:
هزینهها بهسرعت افزایش مییابد و در نهایت به یک سقف عملکرد (performance ceiling) خواهید رسید.
🔹 Horizontal Scaling
در این روش، سرورهای بیشتری به زیرساخت خود اضافه میکنید و بار کاری را بهصورت هوشمندانه میان آنها توزیع میکنید.
این رویکرد پتانسیل مقیاسپذیری بسیار بیشتری دارد، زیرا میتوانید با افزودن سرورهای جدید، ترافیک بیشتر را مدیریت کنید.
اینجاست که Load Balancing وارد عمل میشود — و YARP در این زمینه بهخوبی میدرخشد. ✨
🧭 افزودن یک Reverse Proxy
YARP
یک کتابخانهی Reverse Proxy با کارایی بالا از مایکروسافت است.این ابزار برای معماریهای مدرن microservice طراحی شده است.Reverse Proxy در جلوی سرورهای backend شما قرار میگیرد و نقش مدیر ترافیک (traffic director) را ایفا میکند. 🚦
راهاندازی YARP بسیار ساده است:
• پکیج YARP NuGet را نصب میکنید،
• یک تنظیم ساده برای تعریف مقاصد backend میسازید،
• و سپس YARP middleware را فعال میکنید.
YARP
این امکان را میدهد تا قبل از رسیدن درخواستها به سرورهای backend، مسیریابی (routing) و تبدیل (transformation) روی آنها انجام دهید.
🧩 مرحلهی اول: نصب پکیج YARP
Install-Package Yarp.ReverseProxy
⚙️ مرحلهی دوم: پیکربندی سرویسها و افزودن Middleware
در این مرحله، سرویسهای موردنیاز را پیکربندی کرده و YARP middleware را به Request Pipeline معرفی میکنیم:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
🧾 مرحلهی سوم: افزودن تنظیمات YARP در appsettings.json
در این فایل، YARP از مفهوم Routes برای نمایش درخواستهای ورودی به Reverse Proxy و از Clusters برای تعریف سرویسهای پاییندستی (downstream services) استفاده میکند.
الگوی {**catch-all} به ما اجازه میدهد تمام درخواستهای ورودی را بهراحتی مسیردهی کنیم.
{
"ReverseProxy": {
"Routes": {
"api-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "{**catch-all}"
},
"Transforms": [{ "PathPattern": "{**catch-all}" }]
}
},
"Clusters": {
"api-cluster": {
"Destinations": {
"destination1": {
"Address": "http://api:8080"
}
}
}
}
}
}این پیکربندی، YARP را بهصورت یک Pass-through Proxy تنظیم میکند.
اما حالا بیایید آن را بهروزرسانی کنیم تا از مقیاسپذیری افقی (Horizontal Scaling) پشتیبانی کند. 🚀
⚙️ Scaling Out با YARP Load Balancing
هستهی اصلی مقیاسپذیری افقی (Horizontal Scaling) با استفاده از YARP در استراتژیهای مختلف Load Balancing آن نهفته است.YARP چندین Load Balancing Strategy مختلف ارائه میدهد که هر کدام رفتار متفاوتی در توزیع درخواستها دارند:
⚖️ PowerOfTwoChoices:
دو مقصد تصادفی را انتخاب میکند و درخواستی را به سروری ارسال میکند که کمترین تعداد درخواست تخصیص دادهشده را دارد.
🔤 FirstAlphabetical:
اولین سرور در دسترس را بر اساس ترتیب الفبایی انتخاب میکند.
🔁 LeastRequests:
درخواستها را به سرورهایی ارسال میکند که کمترین تعداد درخواست فعال دارند.
🔄 RoundRobin:
درخواستها را بهصورت یکنواخت بین تمام سرورهای backend توزیع میکند.
🎲 Random:
برای هر درخواست، بهصورت تصادفی یک سرور backend را انتخاب میکند.
شما میتوانید این استراتژیها را در فایل تنظیمات YARP پیکربندی کنید.
استراتژی Load Balancing با استفاده از ویژگی LoadBalancingPolicy در بخش Cluster مشخص میشود.
🧩 پیکربندی YARP با RoundRobin Load Balancing
در ادامه، نسخهی بهروزشدهی فایل تنظیمات YARP را میبینید که از استراتژی RoundRobin برای توزیع بار استفاده میکند:
{
"ReverseProxy": {
"Routes": {
"api-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "{**catch-all}"
},
"Transforms": [{ "PathPattern": "{**catch-all}" }]
}
},
"Clusters": {
"api-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"destination1": {
"Address": "http://api-1:8080"
},
"destination2": {
"Address": "http://api-2:8080"
},
"destination3": {
"Address": "http://api-3:8080"
}
}
}
}
}
}🧭 ساختار سیستم با YARP Load Balancer
در تصویر، ساختار کلی سیستم ما با یک YARP Load Balancer و مجموعهای از Application Serverهای مقیاسپذیر افقی نشان داده شده است.
درخواستهای ورودی به API ابتدا به YARP ارسال میشوند، و سپس YARP بر اساس استراتژی انتخابشدهی Load Balancing، ترافیک را بین سرورهای اپلیکیشن توزیع میکند.
در این مثال، یک پایگاه داده (Database) داریم که به چندین نمونه از اپلیکیشن سرویسدهی میکند. 🗄
🧪 حالا نوبت تست عملکرد است (Performance Testing)
در ادامه، با استفاده از ابزار K6 به بررسی عملکرد سیستم در شرایط بار بالا خواهیم پرداخت تا اطمینان حاصل شود که استراتژی Load Balancing ما به درستی کار میکند و مقیاسپذیری بهینه حاصل شده است. 🚀
⚡️ Performance Testing با K6
برای مشاهدهی تأثیر مقیاسپذیری افقی (Horizontal Scaling) در عملکرد سیستم، باید تست بارگیری (Load Testing) انجام دهیم.
ابزار K6 یک ابزار مدرن و کاربرپسند برای Load Testing است که توسط توسعهدهندگان بهراحتی قابل استفاده است.
در این بخش، با نوشتن اسکریپتهایی در K6 ترافیک کاربر را روی اپلیکیشن شبیهسازی کرده و معیارهایی مثل میانگین زمان پاسخ (Average Response Time) و تعداد درخواستهای موفق در هر ثانیه (Requests Per Second) را مقایسه خواهیم کرد.
اپلیکیشنی که قرار است بهصورت افقی مقیاسپذیر شود، دارای دو API endpoint است:
• POST /users:
یک کاربر جدید ایجاد میکند، آن را در پایگاه دادهی PostgreSQL ذخیره کرده و شناسهی کاربر را برمیگرداند.
• GET /users/id:
اگر کاربری با شناسهی مشخص وجود داشته باشد، آن را برمیگرداند.
🧪 تست عملکرد K6 شامل مراحل زیر است:
• افزایش تدریجی تا ۲۰ کاربر مجازی (Virtual Users)
• ارسال یک درخواست POST به endpoint /users
• بررسی اینکه پاسخ 201 Created بازگردانده شود
• ارسال یک درخواست GET به endpoint /users/{id}
• بررسی اینکه پاسخ 200 OK بازگردانده شود
توجه داشته باشید که تمام درخواستهای API از طریق YARP Load Balancer عبور میکنند. ⚙️
import { check } from 'k6';
import http from 'k6/http';
export const options = {
stages: [
{ duration: '10s', target: 20 },
{ duration: '1m40s', target: 20 },
{ duration: '10s', target: 0 }
]
};
export default function () {
const proxyUrl = 'http://localhost:3000';
const response = http.post(${proxyUrl}/users);
check(response, {
'response code was 201': (res) => res.status == 201
});
const userResponse = http.get(${proxyUrl}/users/${response.body});
check(userResponse, {
'response code was 200': (res) => res.status == 200
});
}برای اینکه نتایج تست عملکرد سازگارتر باشند، میتوان منابع قابلدسترس در Docker containerها را محدود کرد — مثلاً به ۱ CPU و ۰.۵ گیگابایت RAM:
services:
api:
image: ${DOCKER_REGISTRY-}loadbalancingapi
cpus: 1
mem_limit: '0.5G'
ports:
- 5000:8080
networks:
- proxybackend
🧠 Summary
مقیاسپذیری افقی (Horizontal Scaling) در کنار Load Balancing مؤثر میتواند به شکل چشمگیری عملکرد و مقیاسپذیری اپلیکیشنهای وب را افزایش دهد.
مزایای مقیاسپذیری افقی زمانی بیشتر نمایان میشوند که حجم ترافیک بالا برود و یک سرور بهتنهایی نتواند پاسخگوی نیازها باشد.
و YARP یک Reverse Proxy قدرتمند و کاربرپسند برای اپلیکیشنهای NET. است.
با این حال، در سیستمهای بزرگ و پیچیدهی Distributed Systems، ممکن است استفاده از راهحلهای اختصاصی Load Balancer گزینهی بهتری باشد — چراکه کنترل دقیقتر و قابلیتهای پیشرفتهتری ارائه میدهند.
🔖هشتگها:
#YARP #DotNet #LoadBalancing #HorizontalScaling #PerformanceTesting #K6 #ReverseProxy
🚀 چه چیزهایی در C# 14 جدید است
نسخهی C# 14 یکی از مهمترین و تأثیرگذارترین نسخههای منتشر شده در سالهای اخیر است.
بیایید نگاهی بیندازیم به ویژگیهای کلیدی این نسخه: 👇
🧩 Extension Members
ویژگی Extension Members محبوبترین قابلیت جدید من در C# 14 است.
این ویژگی در واقع تکامل مدرن مفهوم Extension Methods محسوب میشود قابلیتی که از نسخهی C# 3.0 معرفی شده بود.
🔹 نحو سنتی Extension Method
قبل از C# 14، برای ایجاد Extension Method باید متدهای استاتیک را با پارامتر this مینوشتید:
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string value)
{
return string.IsNullOrEmpty(value);
}
public static string Truncate(this string value, int maxLength)
{
if (string.IsNullOrEmpty(value) value.Length <= maxLength)
{
return value;
}
return value.Substring(0, maxLength);
}
}
🔹 نحو جدید با کلیدواژهی extension
در C# 14 سینتکس جدیدی معرفی شده که receiver (نوعی که میخواهید آن را گسترش دهید) را از اعضایی که به آن اضافه میکنید جدا میکند.
به جای قرار دادن this روی هر پارامتر متد، حالا میتوانید یک بلوک extension تعریف کنید که فقط یکبار نوع receiver را مشخص میکند:
public static class StringExtensions
{
extension(string value)
{
public bool IsNullOrEmpty()
{
return string.IsNullOrEmpty(value);
}
public string Truncate(int maxLength)
{
if (string.IsNullOrEmpty(value) value.Length <= maxLength)
return value;
return value.Substring(0, maxLength);
}
}
}
در اینجا بلوک extension نوع receiver را به عنوان پارامتر میگیرد. درون این بلوک، میتوانید متدها و propertyها را طوری بنویسید که انگار واقعاً عضو نوع اصلی هستند.
پارامتر value در تمام اعضای داخل بلوک در دسترس است.
نکتهی مهم اینجاست که هر دو سینتکس قدیمی و جدید در نهایت به کد یکسانی کامپایل میشوند، بنابراین رفتارشان دقیقاً مشابه است.
حتی میتوانید هر دو سبک را در یک کلاس استاتیک بهصورت همزمان استفاده کنید. 💡
⚙️ پشتیبانیهای جدید در نحو extension
نحو جدید extension از موارد زیر پشتیبانی میکند:
🧩 Instance methods
🧩 Instance properties
⚡️ Static methods
⚡️ Static properties
💡 Extension Properties
🌟 Extension Properties
ویژگیهای Extension Properties باعث میشن کد شما خواناتر و گویاتر بشه.
به جای فراخوانی متدها، حالا میتونید از propertyهایی استفاده کنید که طبیعیتر بهنظر میان.
برای مثال، وقتی با کالکشنها کار میکنید، معمولاً بررسی میکنید که آیا خالی هستند یا نه.
به جای نوشتن مکرر !items.Any()، میتونید یک property به نام IsEmpty بسازید: 👇
public static class CollectionExtensions
{
extension<T>(IEnumerable<T> source)
{
public bool IsEmpty => !source.Any();
public bool HasItems => source.Any();
public int Count => source.Count();
}
}
public void ProcessOrders(IEnumerable<Order> orders)
{
if (orders.IsEmpty)
{
Console.WriteLine("No orders to process");
return;
}
foreach (var order in orders)
{
// Process order
}
}
💬 در این مثال، حالا میتونید بهصورت طبیعیتر بنویسید:
if (orders.IsEmpty)
بهجای
if (!orders.Any())
نتیجه: کد سادهتر، تمیزتر و خواناتر. ✨
🧠 فیلدهای خصوصی و Caching
درون یک بلوک extension میتونید فیلدها و متدهای خصوصی تعریف کنید درست مثل کلاسهای معمولی.
این قابلیت زمانی مفیده که نیاز دارید محاسبات سنگین (مثل ToList()) فقط یکبار انجام بشن و نتیجهی اون cache بشه.
مثلاً: 👇
public static class CollectionExtensions
{
extension<T>(IEnumerable<T> source)
{
private List<T>? _materializedList;
public List<T> MaterializedList => _materializedList ??= source.ToList();
public bool IsEmpty => MaterializedList.Count == 0;
public T FirstItem => MaterializedList[0];
}
}
در اینجا فیلد خصوصی _materializedList تضمین میکنه که ToList() فقط یکبار اجرا بشه،
صرفنظر از اینکه چند بار به propertyهای مختلف (مثل IsEmpty یا FirstItem) دسترسی داشته باشید.
به این ترتیب، performance بهشکل محسوسی بهبود پیدا میکنه. ⚡️
⚙️ Static Extension Members
قابلیت Static Extensions به شما اجازه میدهد که متدهای کارخانهای (Factory Methods) یا توابع کمکی (Utility Functions) را بهصورت استاتیک به یک نوع (Type) اضافه کنید، نه به instance آن.
برای ایجاد static extensions، از extension استفاده کنید بدون اینکه پارامتر گیرنده (Receiver Parameter) را نامگذاری کنید: 👇
public static class ProductExtensions
{
extension(Product)
{
public static Product CreateDefault() =>
new Product
{
Name = "Unnamed Product",
Price = 0,
StockQuantity = 0,
Category = "Uncategorized",
CreatedDate = DateTime.UtcNow
};
public static bool IsValidPrice(decimal price) =>
price >= 0 && price <= 1000000;
public static string DefaultCategory => "General";
}
}
حالا میتونید این اعضای استاتیک رو مستقیماً روی خود نوع (Type) فراخوانی کنید:
var product = Product.CreateDefault();
if (Product.IsValidPrice(999.99m))
{
product.Price = 999.99m;
}
✨ این ویژگی باعث میشه که کلاسها تمیزتر و سازمانیافتهتر باشن، مخصوصاً برای ایجاد factory یا helper methods.
🤖 Null-Conditional Assignment
در C# 14، عملگرهای Null-Conditional
یعنی ?. و ?[ ] حالا میتونن برای عمل انتساب (assignment) هم استفاده بشن، نه فقط برای دسترسی به اعضا.
🔸 در نسخههای قبلی #C، باید قبل از انتساب، بهصورت دستی null-check انجام میدادید:
if (user is not null)
{
user.Profile = LoadProfile();
}
اما حالا میتونید همون کار رو خیلی تمیزتر بنویسید: 👇
user?.Profile = LoadProfile();
🧠 این نحو جدید، هم کوتاهتره و هم با نحو null-conditional در خواندن مقدارها (reading values) سازگاری بیشتری داره.
🧩 The field Keyword
کلمهی کلیدی field در C# 14 یکی از ویژگیهای کاربردی جدیده که نیاز به تعریف فیلدهای پشتیبان (backing fields) دستی رو در بسیاری از سناریوهای معمولی حذف میکنه.
🔹 قبلاً باید فیلد خصوصی رو خودتون تعریف میکردید:
public class Record
{
private string _msg;
public string Message
{
get => _msg;
set => _msg = value ?? throw new ArgumentNullException(nameof(value));
}
}
اما حالا با field میتونید مستقیماً به فیلد تولیدشده توسط کامپایلر اشاره کنید:
public class Record
{
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
}
💡 میتونید از field در هر access modifier مثل get، set یا init استفاده کنید.
اگر قبلاً در کدتون متغیری به نام field داشتید، میتونید برای جلوگیری از تداخل از @field یا this.field استفاده کنید.
⚡️ کاربرد عملی: Lazy Initialization و مقدارهای پیشفرض
کلمهی field برای lazy initialization یا تنظیم مقدارهای پیشفرض عالیه: 👇
public class ConfigReader
{
public string FilePath
{
get => field ??= "data/config.json";
set => field = value;
}
public Dictionary<string, string> ConfigValues
{
get => field ??= new Dictionary<string, string>();
set => field = value;
}
}
🧠 به این ترتیب، نیازی نیست فیلدهای پشتیبان جداگانه برای FilePath و ConfigValues تعریف کنید همهچیز تمیزتر و خلاصهتر میشه.
🪄 Lambda Parameters with Modifiers
در C# 14 حالا میتونید از modifierهایی مثل out، ref، in، scoped و ref readonly در پارامترهای lambda استفاده کنید، بدون اینکه مجبور باشید نوع (type) رو بهصورت کامل مشخص کنید.
🔸 در نسخههای قبلی، باید نوع کامل پارامترها رو مینوشتید:
delegate bool TryParse<T>(string text, out T result);
TryParse<int> parse = (string text, out int result) => Int32.TryParse(text, out result);
اما حالا، کامپایلر نوعها رو خودش استنتاج (infer) میکنه و کد شما خلاصهتر میشه: 👇
delegate bool TryParse<T>(string text, out T result);
TryParse<int> parse = (text, out result) => Int32.TryParse(text, out result);
🔹 این ویژگی، هم کد رو مختصرتر میکنه، هم type-safety حفظ میشه.
نتیجه؟ نوشتن delegateها و lambdaهای پیچیده بسیار سادهتر از قبل خواهد بود. 🚀
🧱 Partial Constructors و Events
در C# 14 پشتیبانی از Partial Members گسترش یافته و حالا شامل Constructors و Events هم میشود.
این قابلیت بهویژه برای Source Generatorها بسیار مفید است ⚙️
🏗 Partial Constructors
یک Partial Constructor باید شامل یک بخش تعریفکننده (Defining Declaration) و یک بخش پیادهساز (Implementing Declaration) باشد.
🔹 مثال:
public partial class User
{
// این بخش تعریفکننده است
public partial User(string name);
}
public partial class User
{
// این بخش پیادهساز است
public partial User(string name)
: this() // فراخوانی یک سازنده دیگر در همان کلاس
{
Name = name;
}
public User() { }
public string Name { get; set; }
}
📘 قوانین مربوط به Partial Constructorها:
فقط بخش پیادهساز (implementing part) میتواند از this() یا base() برای فراخوانی یک constructor دیگر استفاده کند.
فقط یکی از بخشهای کلاس میتواند از Primary Constructor استفاده کند.
🔧 این ویژگی در پروژههایی که به صورت خودکار کد تولید میکنند (مانند source generators)، انعطاف زیادی ایجاد میکند و اجازه میدهد بخشی از logic در runtime تولید یا تزریق شود.
⚡️ Partial Events
همچنین، Partial Events نیز نیاز به دو بخش دارند — یکی تعریفکننده (Defining Declaration) و دیگری پیادهساز (Implementing Declaration).
🔹 مثال:
public partial class Downloader
{
public partial event Action<string> DownloadCompleted;
}
public partial class Downloader
{
public partial event Action<string> DownloadCompleted
{
add { }
remove { }
}
}
📘 در اینجا بخش پیادهساز باید شامل هر دو accessor یعنی add و remove باشد.
✨ این قابلیت به ویژه در سناریوهایی مفید است که بخشی از تعریف event توسط ابزار یا generator تولید میشود و بخش دیگر توسط توسعهدهنده پیادهسازی میگردد.
🔗 برای مشاهدهی فهرست کامل ویژگیهای جدید C# 14، میتوانید به مستندات رسمی Microsoft مراجعه کنید.
🚀 جدیدترین قابلیتها در ASP.NET Core نسخهی .NET 10
✅ Validation Support در Minimal APIs
در ASP.NET Core 10 پشتیبانی داخلی از Validation برای Minimal APIها اضافه شده است.
این ویژگی دادههایی که به endpointهای شما ارسال میشوند — شامل query parameters، headers و request bodies را بهصورت خودکار اعتبارسنجی میکند.
برای فعالسازی، کافی است سرویس validation را ثبت کنید:
builder.Services.AddValidation();
سیستم validation بهصورت خودکار نوعهایی را که در handlerهای Minimal API شما استفاده شدهاند شناسایی میکند و با استفاده از attributeهای موجود در فضای نام System.ComponentModel.DataAnnotations آنها را اعتبارسنجی میکند.
میتوانید attributeهای اعتبارسنجی را مستقیماً روی پارامترهای endpoint اعمال کنید:
app.MapPost("/products",
([Range(1, int.MaxValue)] int productId, [Required] string name) =>
{
return TypedResults.Ok(new { productId, name });
});نوعهای record نیز با validation کار میکنند:
public record Product(
[Required] string Name,
[Range(1, 1000)] int Quantity);
app.MapPost("/products", (Product product) =>
{
return TypedResults.Ok(product);
});
وقتی اعتبارسنجی شکست بخورد، runtime بهصورت خودکار پاسخ 400 Bad Request را همراه با جزئیات خطاهای validation بازمیگرداند.
میتوانید برای endpointهای خاص، validation را غیرفعال کنید:
app.MapPost("/products", (int productId, string name) =>
TypedResults.Ok(productId))
.DisableValidation();برای شخصیسازی پاسخهای خطا، میتوانید رابط IProblemDetailsService را پیادهسازی کرده و در Dependency Injection Container ثبت کنید.
این کار امکان تولید پیامهای خطای یکپارچه و کاربرپسند در کل برنامه را فراهم میکند.
🧩 JSON Patch Support در Minimal APIs
برای فعالسازی پشتیبانی از JSON Patch با System.Text.Json، بستهی زیر را نصب کنید:
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
🔄 Server-Sent Events (SSE)
Server-Sent Events (SSE)
روشی سبک و قابلاعتماد برای ارسال مداوم دادهها از سمت سرور به کلاینتها است — بدون پیچیدگیهای پروتکلهای دوطرفه مانند WebSocket.
در مدل SSE، سرور از طریق یک اتصال HTTP واحد، دادههای بلادرنگ را به مرورگر ارسال میکند. برخلاف مدل request-response سنتی، نیازی نیست کلاینتها مدام از سرور درخواست بفرستند.
برای ارسال SSE، باید یک جریان داده از نوع IAsyncEnumerable<T> فراهم کنید.
🔹 مثال سرویسی برای تولید دادههای قیمت سهام:
public record StockPriceEvent(string Id, string Symbol, decimal Price, DateTime Timestamp);
public class StockService
{
public async IAsyncEnumerable<StockPriceEvent> GenerateStockPrices(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var symbols = new[] { "MSFT", "AAPL", "GOOG", "AMZN" };
while (!cancellationToken.IsCancellationRequested)
{
var symbol = symbols[Random.Shared.Next(symbols.Length)];
var price = Math.Round((decimal)(100 + Random.Shared.NextDouble() * 50), 2);
var id = DateTime.UtcNow.ToString("o");
yield return new StockPriceEvent(id, symbol, price, DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
}
}
}
حالا با استفاده از TypedResults.ServerSentEvents یک endpoint برای استریم داده میسازیم:
builder.Services.AddSingleton<StockService>();
app.MapGet("/stocks", (StockService stockService, CancellationToken ct) =>
{
return TypedResults.ServerSentEvents(
stockService.GenerateStockPrices(ct),
eventType: "stockUpdate"
);
});
کلاینتها میتوانند به این endpoint متصل شوند و بهصورت بلادرنگ بروزرسانیهای قیمت را دریافت کنند.
برای راهنمایی کاملتر دربارهی SSE در ASP.NET Core، میتوانید راهنمای رسمی Microsoft را مطالعه کنید.
📘 OpenAPI 3.1 Support
در ASP.NET Core 10 پشتیبانی از تولید مستندات OpenAPI 3.1 اضافه شده است.
اگرچه شماره نسخه ممکن است کوچک به نظر برسد، اما OpenAPI 3.1 تغییرات مهمی دارد، از جمله پشتیبانی کامل از JSON Schema draft 2020-12.
🔹 تغییرات کلیدی در OpenAPI 3.1:
نوعهای nullable بهجای nullable: true از type: [<type>, "null"] استفاده میکنند.
نوعهای عددی مانند int و long ممکن است بدون type: integer و با فیلد pattern ظاهر شوند (بسته به تنظیمات serialization).
نسخهی پیشفرض OpenAPI اکنون 3.1 است.
برای تنظیم دستی نسخهی OpenAPI:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});
📄 YAML Format Support
اکنون ASP.NET Core میتواند مستندات OpenAPI را در قالب YAML نیز ارائه دهد.
YAML خواناتر از JSON است و از رشتههای چندخطی پشتیبانی میکند — مناسب برای مستندات طولانی.
برای فعالسازی:
if (app.Environment.IsDevelopment())
{
app.MapOpenApi("/openapi/{documentName}.yaml");
}
🧠 What's New in Blazor
در .NET 10، بلیزر پیشرفتهای زیادی داشته است: Hot Reload برای Blazor WebAssembly و .NET on WebAssembly
⚙️ پیکربندی محیط (Environment Configuration) در Blazor WebAssembly مستقل
📊 پروفایلینگ و شمارندههای تشخیصی (Performance Profiling & Diagnostic Counters)
❌ پارامتر جدید NotFoundPage برای router
⚡️ پیشلود داراییهای استاتیک (Static Asset Preloading) در Blazor Web Apps
✅ بهبود در Form Validation
🚀 ویژگیهای جدید EF Core 10 در 10 NET.
🧩 Complex Types (انواع پیچیده)
در EF Core 10 میتوان دادههایی را مدل کرد که به یک Entity تعلق دارند اما هویت مستقل ندارند.
در حالی که Entityها به جدولهای جداگانه در دیتابیس نگاشت میشوند، Complex Typeها میتوانند به ستونهایی در همان جدول (Table Splitting) یا به یک ستون JSON نگاشت شوند.
این قابلیت امکان مدلسازی به سبک Document-Based را فراهم میکند و باعث کاهش JOINها و بهبود عملکرد دیتابیس میشود.
📘 مثال Table Splitting:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress);
b.ComplexProperty(c => c.BillingAddress);
});
✅ Optional Complex Types (انواع پیچیده اختیاری)
EF Core 10
از Complex Typeهای اختیاری نیز پشتیبانی میکند:
public class Customer
{
public int Id { get; set; }
public Address ShippingAddress { get; set; }
public Address? BillingAddress { get; set; }
}
🗃 JSON Mapping (نگاشت به JSON)
اکنون میتوانید Complex Typeها را به ستونهای JSON در دیتابیس نگاشت دهید:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
});
💪 Struct Support (پشتیبانی از Struct)
Complex Type
ها حالا میتوانند از Structها نیز استفاده کنند:
public struct Address
{
public required string Street { get; set; }
public required string City { get; set; }
public required string ZipCode { get; set; }
}
🔗 LeftJoin و RightJoin Operators
در نسخههای قبلی EF Core نوشتن LEFT JOIN بسیار پیچیده بود و نیاز به ترکیب SelectMany, GroupJoin, و DefaultIfEmpty داشت.
در .NET 10 حالا میتوانید با متدهای LeftJoin و RightJoin، کوئریهای تمیزتر و سادهتری بنویسید 👇
var query = context.Students
.LeftJoin(
context.Departments,
student => student.DepartmentID,
department => department.Id,
(student, department) => new { student.Name, Department = department?.Name }
);
🔍 این ویژگیها EF Core را به سمت یک ORM مدرنتر، قدرتمندتر و سازگار با معماریهای Document-oriented و Microservice سوق میدهد.
Nobody cares about your career progression more than you do.
Not your boss. Not your partner. Not your mom, dad, cat, dog, or fish.
You can be set up with an amazing manager who wants you to succeed and tries their best to help you grow.
But at the end of the day, YOU are in the driver's seat.
Not your boss. Not your partner. Not your mom, dad, cat, dog, or fish.
You can be set up with an amazing manager who wants you to succeed and tries their best to help you grow.
But at the end of the day, YOU are in the driver's seat.
🚀 کاوش File-based Apps در #C در 10 NET.
سیشارپ همیشه یکجورهایی پُر از تشریفات و ساختارهای اضافی بوده، و خودمان هم این را خوب میدانیم. حتی سادهترین برنامهٔ "Hello World" معمولاً به یک فایل راهحل (solution)، یک فایل پروژه (csproj)، و آنقدر کد boilerplate نیاز داشت که از خودتان میپرسیدید شاید بهتر بود از یک زبان اسکریپتی استفاده میکردید!
خب، مایکروسافت بالاخره صدایمان را شنید. با NET 10، File-based Apps. معرفی شدهاند. و واقعاً وقتش بود.
📌 File-based Apps چیستند؟
ایده بسیار ساده است:
کد #C خود را در یک فایل تکی cs. بنویسید و مستقیم اجرا کنید. همین.
نیازی نیست برای یک اسکریپت کوچک که میخواهید چند فایل CSV را پردازش کنید یا یک API endpoint را تست کنید، یک ساختار کامل پروژه بسازید.
در این حالت همچنان همهٔ چیزهایی که #C را فوقالعاده میکنند حفظ میکنید:
🔸️type safety
🔸️performance
کتابخانههای قدرتمند استاندارد💪🏻
اما حالا میتوانید از #C برای اسکریپتهای موقتی استفاده کنید؛ جاهایی که ساختن یک پروژهٔ کامل تبدیل به یک دردسر اضافه میشود.
و بله:
• میتوانید NuGet package اضافه کنید
• میتوانید پروژههای دیگر #C را reference کنید
• میتوانید SDK خاصی را هدف قرار دهید
• و تنظیمات پروژه را فقط با یک فایل انجام دهید
همهٔ این کارها از طریق directiveهایی که با :# شروع میشوند.
این قابلیت روی قابلیت top-level statements که در C# 9 معرفی شد ساخته شده. اگر قرار است اجازه دهیم مردم کلاس و متد Main را حذف کنند، خب چرا اجازه ندهیم کل فایل پروژه را هم حذف کنند؟
🚀 شروع کار با File-based Apps
فرض کنید میخواهید سریع بررسی کنید یک تاریخ مشخص مربوط به چه روزی از هفته است. کافی است یک فایل با نام date-checker.cs بسازید:
var targetDate = new DateTime(2025, 12, 31);
Console.WriteLine($"New Year's 2025 falls on a {targetDate.DayOfWeek}");
Console.WriteLine($"That's {(targetDate - DateTime.Today).Days} days from now");
و اجرا کنید:
dotnet run date-checker.cs
اولین باری که این دستور را اجرا میکنید، CLI یک مقدار جادوی پشتصحنه انجام میدهد:
🔹️یک پروژهٔ مجازی میسازد
🔹️کد را کامپایل میکند
🔹️همهچیز را cache میکند
🔹️و دفعات بعدی اجرای برنامه تقریباً لحظهای است، چون بهقدری هوشمند است که تشخیص دهد چیزی تغییر کرده یا نه.
📌مثال واقعی: پردازش سریع دادهها
در اینجا است که قضیه جذاب میشود. فرض کنیم لازم باشد خیلی سریع مقداری دادهی JSON را پردازش کنید و یک گزارش تولید کنید.
بیایید با System.Text.Json و CsvHelper یک کار عملی انجام بدهیم:
#:package CsvHelper@33.1.0
using System.Text.Json;
using CsvHelper;
using System.Globalization;
var json = await File.ReadAllTextAsync("sales_data.json");
var sales = JsonSerializer.Deserialize<List<SaleRecord>>(json);
var topProducts = sales
.GroupBy(s => s.Product)
.Select(g => new {
Product = g.Key,
TotalRevenue = g.Sum(s => s.Amount),
UnitsSold = g.Count()
})
.OrderByDescending(p => p.TotalRevenue)
.Take(10);
using var writer = new StreamWriter("top_products.csv");
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.WriteRecords(topProducts);
Console.WriteLine("Report generated! Check top_products.csv");
record SaleRecord(string Product, decimal Amount, DateTime Date);
توجه کنید که در اینجا داریم package reference، عملیات async، LINQ و record typeها را با هم ترکیب میکنیم—همه در یک فایل واحد که کاملاً شبیه یک اسکریپت منسجم عمل میکند. این همان کاری است که معمولاً برای انجامش سراغ Python میرفتید، اما حالا میتوانید در دنیای #C بمانید.
🛠ساخت یک پروژه جاهطلبانهتر با Aspire
شما حتی میتوانید یک Aspire AppHost را فقط در یک فایل بسازید—که اگر خوب فکر کنید واقعاً چیز عجیبی است:
#:sdk Aspire.AppHost.Sdk@13.0.0
#:package Aspire.Hosting.AppHost@13.0.0
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache")
.WithDataVolume();
var postgres = builder.AddPostgres("postgres")
.WithDataVolume()
.AddDatabase("tododb");
var todoApi = builder.AddProject<Projects.TodoApi>("api")
.WithReference(cache)
.WithReference(postgres);
builder.AddNpmApp("frontend", "../TodoApp")
.WithReference(todoApi)
.WithReference("api")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints();
builder.Build().Run();
با استفاده از دستور #:sdk Aspire.AppHost.Sdk@13.0.0، فایل تکستونهی شما تبدیل به یک orchestrator کامل برای یک distributed application میشود. شما در حال تعریف زیرساخت، اتصال وابستگیها، و راهاندازی یک محیط توسعهی کامل هستید—بدون اینکه هیچ فایل project بسازید.
این قابلیت بهویژه زمانی کاربردی است که دارید معماری را prototype میکنید یا لازم دارید خیلی سریع یک محیط تست بالا بیاورید.
Migrating to a Full Project 🚀 — مهاجرت به یک پروژهی کامل
در نهایت، بعضی اسکریپتها از ریشهی تکفایلی خود بزرگتر میشوند. شاید لازم باشد چیزها را به چند فایل تقسیم کنید، یا شاید بخواهید پشتیبانی کامل IDE برای Debugging داشته باشید. این انتقال بدون دردسر است:
dotnet project convert MyUtility.cs
این دستور یک ساختار پروژهی کامل ایجاد میکند، درحالیکه تمام package referenceها و انتخابهای SDK شما را حفظ میکند. کد شما به Program.cs منتقل میشود و یک .csproj دریافت میکنید که تمام آن #:directiveهایی را که استفاده کرده بودید منعکس میکند. 📦⚙️
Current Limitations ⚠️ — محدودیتهای فعلی
در حال حاضر، این قابلیت کاملاً تکفایلی است. اگر به چند فایل نیاز دارید، باید تا NET 11. صبر کنید یا پروژه را تبدیل کنید. البته همچنان میتوانید به پروژهها و packageهای دیگر reference بدهید، اما فایل اصلی اسکریپت باید تنها یک فایل باشد. 📄
مکانیزم caching ممکن است گاهی دچار سردرگمی شود، مخصوصاً اگر سریع روی نسخههای package تکرار کنید. و با اینکه پشتیبانی IDE بهتر شده، هنوز به سطح پروژههای کامل نرسیده است—بهخصوص برای IntelliSense مربوط به packageهایی که به صورت داینامیک reference میکنید. 💡
اما برای کاری که طراحی شده است (قابلدسترستر کردن #C برای سناریوهای noscripting)، بهطرز شگفتانگیزی خوب عمل میکند. میتوانید از آن برای build noscriptها، کارهای یکبارهی data migration، تست سریع API، یا حتی آموزش #C بدون اینکه روز اول بخواهید ساختار یک solution را توضیح بدهید، استفاده کنید. 🧪⚙️
Where This Leaves Us 🎯 — نتیجهی این قابلیت
File-based app
ها حسی ایجاد میکنند شبیه اینکه #C بالاخره پذیرفته است که همهچیز قرار نیست یک برنامهی enterprise باشد. گاهی لازم دارید یک فایل log را parse کنید، گاهی باید سریع یک الگوریتم را تست کنید، و گاهی دارید برنامهنویسی را به کسی آموزش میدهید و نمیخواهید روز اول برایش از solution file حرف بزنید. 📝⚡️
این قابلیت انقلابی در توسعهی #C ایجاد نمیکند، اما شکاف آزاردهندهای را پر میکند که سالها توسعهدهندگان را اذیت کرده بود. اگر تا امروز یک پوشه پر از اسکریپتهای سریع داشتید چون #C برای چنین کارهایی سنگین به نظر میرسید، شاید وقتش رسیده دوباره به زبان محبوبتان شانس بدهید. 💙🤖
در نهایت، بهترین زبان برای یک اسکریپت سریع، همان زبانی است که از قبل بلد هستید—و حالا #C در این زمینه بسیار قانعکنندهتر شده است. ⚡️💻
Forwarded from tech-afternoon (Amin Mesbahi)
کمتر از ۲ هفته دیگه (۲۵ نوامبر ۲۰۲۵)، کتاب
Crafting Engineering Strategy:
How Thoughtful Decisions Solve Complex Problems
یا: طراحی استراتژی مهندسی: چگونه تصمیمگیریهای سنجیده مسائل پیچیده رو حل میکنن؛ منتشر میشه. توی معرفی اولیه اومده:
خیلی از مهندسها تصور میکنن سازمانشون استراتژی مهندسی نداره! در حالی که در واقع، اغلبشون دارن؛ ولی ممکنه این حس ناشی از ناکارآمدی اسراتژیها باشه. نویسنده، یعنی ویل لارسون (نویسنده کتابهایی مثل Elegant Puzzle یا Staff Engineer) یک راهنمای کاربردی، و در عین حال غنی از مثالهای واقعی، برای مسیریابی در پیچیدگیهای فنی، و همچنین پیچیدگیهای سازمانی از طریق «استراتژی ساختاریافته و هدفمند» ارائه میده.
این کتاب که برای مهندسهای ارشد، رهبران مهندسی، و معمارها نوشته شده. مثالهای واقعی از شرکتهایی مثل استرایپ، اوبر، و کَلم استخراج ارائه میده. چارچوب پیشنهادی نویسنده، برای شکلدهی تصمیمگیریهای حیاتی در مورد مهاجرت سیستمها، منسوخ کردن APIها، سرمایهگذاریهای پلتفرم و موارد مشابه کاربرد داره. کتاب، در طول مسیر، یاد قراره تا یاد بده برنامهریزی فنی رو با ارتباطات، حاکمیت، و تفکر سیستمی تقویت کنید. چه در حال شکلدهی به مسیر تیمتون باشید و چه رهبری یک ابتکار در سطح شرکت رو به عهده داشته باشین، «طراحی استراتژی مهندسی» به شما کمک میکنه تصمیمهای سنجیدهای بگیرید که پایدار باشن.
دلیل معرفی این کتاب اول موضوعش بود، دوم اینکه من چند کتاب از این نویسنده رو خوندم و سبک نوشتار و مسیر پرداختن به موضعش رو دوست دارم (این یک نظر شخصیه و شاید برای شما صدق نکنه)
نسخه کاغذی با قیمت 36.92€ عرضه خواهد شد.
در مورد نویسنده
Please open Telegram to view this post
VIEW IN TELEGRAM
🚀 Fast SQL Bulk Inserts With C# and EF Core
فرقی نمیکند دارید یک پلتفرم تحلیل داده میسازید، یک سیستم قدیمی را مهاجرت میدهید، یا یک موج بزرگ از کاربران جدید وارد سیستمتان میشوند—بهاحتمال زیاد زمانی میرسد که باید مقدار عظیمی داده را وارد دیتابیس کنید.
وارد کردن رکوردها یکییکی مثل این است که خشکشدن رنگ را در حرکت آهسته تماشا کنید! روشهای سنتی اصلاً جواب نمیدهند.
پس یاد گرفتن تکنیکهای bulk insert سریع با #C و EF Core تبدیل به یک مهارت ضروری میشود.
در این مقاله، چندین روش برای انجام bulk insert در #C بررسی میکنیم:
• Dapper
• EF Core
• EF Core Bulk extensions
• SQL Bulk Copy
• Entity Framework Extensions
تمام مثالها بر اساس یک کلاس User و جدول Users در SQL Server هستند:
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
}
این لیست کامل تمام روشهای bulk insert نیست. چند گزینه دیگر بررسی نشدهاند، مثل تولید دستی SQL یا استفاده از Table-Valued parameters.
📌EF Core Simple Approach
بیایید با یک مثال ساده با EF Core شروع کنیم. ما یک ApplicationDbContext میسازیم، یک شی User اضافه میکنیم و SaveChangesAsync را صدا میزنیم. این کار باعث میشود هر رکورد بهصورت جداگانه در دیتابیس insert شود. یعنی برای هر رکورد یک round trip به دیتابیس نیاز داریم.
using var context = new ApplicationDbContext();
foreach (var user in GetUsers())
{
context.Users.Add(user);
await context.SaveChangesAsync();
}
و نتایج دقیقاً همانقدر ضعیف هستند که انتظار دارید:
EF Core - Add one and save برای 100 کاربر: 20 ms
EF Core - Add one and save برای 1,000 کاربر: 260 ms
EF Core - Add one and save برای 10,000 کاربر: 8,860 ms
نتایج 100,000 و 1,000,000 رکورد حذف شدند چون اجرای آنها بیش از حد طولانی شد.
این مثال را بهعنوان «چگونه bulk insert انجام ندهیم» در نظر بگیرید.
🧩Dapper Simple Insert
Dapper
یک SQL-to-object mapper ساده برای NET. است. این ابزار اجازه میدهد مجموعهای از اشیا را بهراحتی در دیتابیس insert کنیم.
من از قابلیت Dapper برای unwrap کردن یک collection داخل یک SQL INSERT statement استفاده میکنم.
using var connection = new SqlConnection(connectionString);
connection.Open();
const string sql =
@"
INSERT INTO Users (Email, FirstName, LastName, PhoneNumber)
VALUES (@Email, @FirstName, @LastName, @PhoneNumber);
";
await connection.ExecuteAsync(sql, GetUsers());
نتایج بسیار بهتر از مثال ابتدایی هستند:
Dapper - Insert range برای 100 کاربر: 10 ms
Dapper - Insert range برای 1,000 کاربر: 113 ms
Dapper - Insert range برای 10,000 کاربر: 1,028 ms
Dapper - Insert range برای 100,000 کاربر: 10,916 ms
Dapper - Insert range برای 1,000,000 کاربر: 109,065 ms
🔸️EF Core Add and Save
اما EF Core هم هنوز کنار نکشیده. مثال اول عمداً خیلی ضعیف پیادهسازی شده بود. EF Core میتواند چندین SQL statement را با هم batch کند، پس از این قابلیت استفاده میکنیم.
اگر یک تغییر ساده بدهیم، میتوانیم عملکرد بهمراتب بهتری بگیریم. اول، همهٔ اشیا را به ApplicationDbContext اضافه میکنیم. سپس فقط یکبار SaveChangesAsync را صدا میزنیم.
این EF یک batched SQL statement میسازد—یعنی چندین INSERT را با هم گروه میکند—و همه را یکجا به دیتابیس میفرستد. این کار تعداد round trip ها را کم میکند و باعث بهبود سرعت میشود.
using var context = new ApplicationDbContext();
foreach (var user in GetUsers())
{
context.Users.Add(user);
}
await context.SaveChangesAsync();
نتایج benchmark این پیادهسازی:
EF Core - Add all and save برای 100 کاربر: 2 ms
EF Core - Add all and save برای 1,000 کاربر: 18 ms
EF Core - Add all and save برای 10,000 کاربر: 203 ms
EF Core - Add all and save برای 100,000 کاربر: 2,129 ms
EF Core - Add all and save برای 1,000,000 کاربر: 21,557 ms
به یاد داشته باشید: Dapper برای insert کردن 1,000,000 رکورد حدود 109 ثانیه زمان گرفت. با EF Core و batched queries، همان کار را در حدود 21 ثانیه انجام میدهیم.
EF Core AddRange و Save ✨
این یک جایگزین برای مثال قبلی است. به جای فراخوانی Add برای همهٔ آبجکتها، میتوانیم AddRange را صدا بزنیم و یک کالکشن ارسال کنیم.
میخواستم این پیادهسازی را نشان دهم چون آن را به نمونهٔ قبلی ترجیح میدهم.
using var context = new ApplicationDbContext();
context.Users.AddRange(GetUsers());
await context.SaveChangesAsync();
نتایج بسیار مشابه نمونهٔ قبلی هستند: 📊
EF Core - Add range و Save، برای ۱۰۰ کاربر: ۲ ms
EF Core - Add range و Save، برای ۱,۰۰۰ کاربر: ۱۸ ms
EF Core - Add range و Save، برای ۱۰,۰۰۰ کاربر: ۲۰۴ ms
EF Core - Add range و Save، برای ۱۰۰,۰۰۰ کاربر: ۲,۱۱۱ ms
EF Core - Add range و Save، برای ۱,۰۰۰,۰۰۰ کاربر: ۲۱,۶۰۵ ms
EF Core Bulk Extensions ⚡️
یک کتابخانهٔ فوقالعاده به نام EF Core Bulk Extensions وجود دارد که میتوانیم برای افزایش عملکرد استفاده کنیم. شما با این کتابخانه میتوانید کارهای بیشتری از جمله bulk insert انجام دهید، بنابراین ارزش بررسی دارد. این کتابخانه متنباز است و دارای لایسنس Community میباشد اگر شرایط استفادهٔ رایگان را داشته باشید. بخش لایسنس را برای جزئیات بررسی کنید.
برای استفادهٔ ما، متد BulkInsertAsync گزینهٔ عالی است. میتوانیم کالکشن آبجکتها را ارسال کنیم و یک SQL bulk insert انجام خواهد شد.
using var context = new ApplicationDbContext();
await context.BulkInsertAsync(GetUsers());
عملکرد آن نیز شگفتانگیز است: 🚀
EF Core - Bulk Extensions، برای ۱۰۰ کاربر: ۱.۹ ms
EF Core - Bulk Extensions، برای ۱,۰۰۰ کاربر: ۸ ms
EF Core - Bulk Extensions، برای ۱۰,۰۰۰ کاربر: ۷۶ ms
EF Core - Bulk Extensions، برای ۱۰۰,۰۰۰ کاربر: ۷۴۲ ms
EF Core - Bulk Extensions، برای ۱,۰۰۰,۰۰۰ کاربر: ۸,۳۳۳ ms
برای مقایسه، ما با EF Core batched queries برای درج ۱,۰۰۰,۰۰۰ رکورد حدود ۲۱ ثانیه نیاز داشتیم. میتوانیم همان کار را با کتابخانهٔ Bulk Extensions تنها در ۸ ثانیه انجام دهیم. 😎
SQL Bulk Copy 🧩
اگر نتوانیم عملکرد دلخواه را از EF Core بگیریم، میتوانیم از SqlBulkCopy استفاده کنیم. SQL Server عملیات bulk copy را به صورت native پشتیبانی میکند، پس از آن استفاده کنیم.
این پیادهسازی کمی پیچیدهتر از مثالهای EF Core است. باید یک نمونه SqlBulkCopy را کانفیگ کنیم و یک DataTable ایجاد کنیم که شامل آبجکتهایی باشد که میخواهیم درج کنیم.
using var bulkCopy = new SqlBulkCopy(ConnectionString);
bulkCopy.DestinationTableName = "dbo.Users";
bulkCopy.ColumnMappings.Add(nameof(User.Email), "Email");
bulkCopy.ColumnMappings.Add(nameof(User.FirstName), "FirstName");
bulkCopy.ColumnMappings.Add(nameof(User.LastName), "LastName");
bulkCopy.ColumnMappings.Add(nameof(User.PhoneNumber), "PhoneNumber");
await bulkCopy.WriteToServerAsync(GetUsersDataTable());
با این حال، عملکرد فوقالعاده سریع است: 🔥
SQL Bulk Copy، برای ۱۰۰ کاربر: ۱.۷ ms
SQL Bulk Copy، برای ۱,۰۰۰ کاربر: ۷ ms
SQL Bulk Copy، برای ۱۰,۰۰۰ کاربر: ۶۸ ms
SQL Bulk Copy، برای ۱۰۰,۰۰۰ کاربر: ۶۴۶ ms
SQL Bulk Copy، برای ۱,۰۰۰,۰۰۰ کاربر: ۷,۳۳۹ ms
در ادامه نحوهٔ ایجاد DataTable و پر کردن آن با لیست آبجکتها آمده است: 🏗
DataTable GetUsersDataTable()
{
var dataTable = new DataTable();
dataTable.Columns.Add(nameof(User.Email), typeof(string));
dataTable.Columns.Add(nameof(User.FirstName), typeof(string));
dataTable.Columns.Add(nameof(User.LastName), typeof(string));
dataTable.Columns.Add(nameof(User.PhoneNumber), typeof(string));
foreach (var user in GetUsers())
{
dataTable.Rows.Add(
user.Email, user.FirstName, user.LastName, user.PhoneNumber);
}
return dataTable;
}
Entity Framework Extensions 💎
آیا میتوانیم بهتر از SqlBulkCopy عمل کنیم؟
شاید، حداقل نتایج بنچمارک من نشان میدهد که میتوانیم.
یک کتابخانهٔ فوقالعاده دیگر به نام Entity Framework Extensions وجود دارد. این فقط یک کتابخانهٔ bulk insert نیست - بنابراین توصیه میکنم حتماً بررسی شود. با این حال، امروز آن را برای bulk insert استفاده خواهیم کرد.
برای استفادهٔ ما، متد BulkInsertOptimizedAsync گزینهٔ عالی است. میتوانیم کالکشن آبجکتها را ارسال کنیم و یک SQL bulk insert انجام خواهد شد. همچنین برخی بهینهسازیها زیر هود انجام میدهد تا عملکرد بهتر شود.
using var context = new ApplicationDbContext();
await context.BulkInsertOptimizedAsync(GetUsers());
عملکرد آن فوقالعاده است: ⚡️🔥
EF Core - Entity Framework Extensions، برای ۱۰۰ کاربر: ۱.۸۶ ms
EF Core - Entity Framework Extensions، برای ۱,۰۰۰ کاربر: ۶.۹ ms
EF Core - Entity Framework Extensions، برای ۱۰,۰۰۰ کاربر: ۶۶ ms
EF Core - Entity Framework Extensions، برای ۱۰۰,۰۰۰ کاربر: ۶۳۶ ms
EF Core - Entity Framework Extensions، برای ۱,۰۰۰,۰۰۰ کاربر: ۷,۱۰۶ ms
جمعبندی 🎯
کار SqlBulkCopy برای حداکثر سرعت و سادگی بهترین است. 👑
با این حال، Entity Framework Extensions عملکرد فوقالعادهای ارائه میدهند و همزمان سهولت استفادهای که EF Core دارد را حفظ میکنند. 💎⚡️
بهترین گزینه به نیازهای خاص پروژهٔ شما بستگی دارد:
فقط عملکرد مهم است؟ SqlBulkCopy راه حل شماست. 🔥
به سرعت عالی و توسعهٔ آسان نیاز دارید؟ EF Core انتخاب هوشمندانه است. ⚡️
به دنبال تعادل بین عملکرد و سهولت استفاده هستید؟ Entity Framework Extensions ⚖️
تصمیم با شماست که بهترین گزینه برای پروژهٔ خودتان کدام است.
امیدوارم مفید بوده باشد. 🙌
🔖هشتگها:
#EFCore #Dapper #SqlBulkCopy #BulkExtensions #EntityFrameworkExtensions #Performance #CSharp #Database #DotNet #Programming #BulkInsert
Forwarded from tech-afternoon (Amin Mesbahi)
بعد از دیدن مطلبی که مسعود بیگی در کانال تندتک درباره حذف Story Point از تسکهاشون نوشت، و مواضع مختلفی که در اینباره توی کامیونیتیهای انگلیسی و فارسی وجود داره، تصمیم گرفتم چند خطی رو به این بحث بپردازم. امیدوارم کمک کنه به تصمیمگیری بهتر دوستان...
مرگِ یک آیین قدیمی یا تکامل طبیعی؟
سوال تکراری همیشگی بعد از اینکه یک تیم یا شرکت تخمین Story Point رو کنار میگذاره؛ اینه: «خب جایگزینش چیه؟»
ولی عملا سوال بهتر اینه که اصلاً نیاز واقعیمون به Story Point چی بوده؟ و چرا به وجود اومده؟
این بحث، بحثِ خیلی از تیمهای نوپا و بالغ دنیا طی سالهای اخیر بوده.
عملا Story Point در سالهای ابتدایی پیدایش مفهوم agile در توسعه نرمافزار، ناجی خیلی از تیمها بود. یعنی وقتی تیمها از تخمین زمانی (مثل person-hour یا person-day) خسته شده بودن و میدیدن این اعداد عموما دروغ میگن؛ Story Point با نگاه «بیخیال ساعت شید، فقط بگید این کار نسبت به اون یکی چقدر بزرگتره» به وجود اومد. و واقعاً هم کمک کرد. تیمها بهتر پیشبینی میکردن، Velocity داشتن، ظرفیت اسپرینت مشخص بود.
اما این سالها بعضی از تیمها Story Point رو کنار گذاشتن، مهمترین دلایلی که من دیدم، اینا بوده:
۱. طی گذر زمان؛ Story Point تبدیل به دروغ شد!
همون چیزی که قرار بود جلوی دروغ رو بگیره، خودش تبدیل به دروغ شد.
چرا؟ چون مدیر محصول یا PO میگفت: «این فیچر باید تو همین اسپرینت جا بشه، پس ۸ نزنید، ۵ بزنید!»
یا توسعهدهنده از ترس فشار بعدی، همیشه عدد بزرگتر میزد.
نتیجه؟ Velocity غیرواقعی، تخمین غیرواقعی، بیاعتمادی کامل. من از حدودای ۲۰۱۲-۲۰۱۳ یادم میاد که نهضت NoEstimates# با این گفتمان راه افتاده که تخمین، هزینه داره و اغلب دقتش اونقدری نیست که ارزش هزینهش رو داشته باشه!
۲. تیمها دیگه نیازی به «واحد خیالی» ندارن
وقتی تیم کوچیکه، در اثر تجربه و همنشینی گاهی دیگه نیازی نمیبینن بگن «این ۵ پوینته، اون ۸ پوینته».
فقط میگن: «این کار حدود ۲-۳ روزه، اون یکی یه هفته».
و این تخمین «تقریبی و رنجدار» گاهی دقیقتر از Story Point در میاد!
از طرفی، تیمهای خوب به جای اندازه تسک، روی شکل دادن (Shaping) فیچرها تمرکز میکنند و تخمین ریز نمیزنن.
۳. کار رو باید انجام داد چه یه روز چه ده روز!
بعضی تیمها ماهیت تسکهاشون «ضرورتِ محصوله»، یعنی قابل حذف یا جایگزینی نیست، محصول هم باید تولید شه. لذا برای مدیر و شرکت مهمه که زودتر برسه، ولی اگر چند وقت دیرتر هم بشه، راهی جز پذیرش نداره. اینجاست که تیم میگه خب so what؟ تخمین بزنم که چی بشه؟ من که اول و آخر باید این کار رو انجام بدم!
۴. کارهای روتین، یا قابل پیشبینی هستن
وقتی کارها به طور نسبی زمان مشابهی برای اجرا نیاز دارن، یا توی چند دستهی قابل پیشبینی قرار میگیرن، تکرار مکررات باعث میشه تیم بیخیال تخمین بشه و میدونه چه کاری حدودا طی چی زمانی انجام میشه. اینجا دیگه به جای story point گاها T-Shirt sizing هم جواب میده (S, M, L, XL)
پس کِی و کجا Story Point هنوز مفیده؟
- انترپرایزها: تیم بزرگ، سازمان بزرگ، وابستگیهای بین تیمی زیاد، مدیریت بودجه سختگیرانه و... توی چنین محیطهایی اینقدر در همتنیدگی کارها، بوجه، تیمها، و.. زیاده که یکی از مهارتهای مدیر تیم، کمک به بهبود و تدقیق تخمینهاست. اینقدری که توی شرکتهای بزرگ tech با ماشینلرنینگ و هوشمصنوعی این تخمینها بهبود پیدا میکنه، چون دیر رسیدن یک ماههی یک محصول گاها عدمالنفع یا حتی خسارتهای چند ده میلیون دلاری میتونه به بار بیاره.
قبلا همینجا هم نوشتم که انترپرایز الزاما تعداد کارمند زیاد نیست، باید ساختار و رویهها قابل انترپرایز خطاب شدن باشه!
- تیمهای جدید که هنوز Flow پایداری ندارن
- وقتی تیمها توزیعشده (distributed) هستن و ارتباط لحظهای کمه
یادمون نره که Agile یعنی انعطاف. اگر ابزاری به درد تیم شما نمیخورد، کنارش بگذارید. اینکه process templateهای متنوعی وجود داره، از مدلهای ساده مثل kanban تا scrum و agile و حتی مدلهای خیلی سختگیر و دقیقی مثل CMMI هم هست، یعنی «نسخه واحد برای همه تیمها و محصولات وجود نداره».
سوال این نیست که "Story Point خوبه یا بد؟"
سوال اینه: "برای تیم ما، در مرحله فعلی، چه چیزی بهترین کمک رو میکنه؟"
بعضیها هم که story point رو گذاشتن کنار از سر بلد نبودنشون بوده! نه از سر مناسب نبودنش! و اساسا باید عارضه رو در خودشون پیدا میکردن یا اینکه تا زمان بلوغ، میرفتن سراغ متد دیگهای که مبتنی بر SP نباشه، نه اینکه متد رو نگه دارن و SP رو از دلش بکشن بیرون.
این موضوع خیلی مفصله ولی امیدوارم در حد سرنخ دادن کمک کرده باشه... 😊
Please open Telegram to view this post
VIEW IN TELEGRAM