C# Geeks (.NET) – Telegram
خروجی کد زیر چیست؟🤔
int a = 5, b = 10;
Console.WriteLine( a++ + ++b );
Anonymous Quiz
10%
15
65%
16
26%
17
0%
18
کجا نباید از داداشمون RabbitMQ استفاده کنیم؟🤔


1️⃣ وقتى نياز به real-time response داريم.
چرا؟ چون RabbitMQ صف هست و ارسال/دريافت پيام ممكنه با تاخير انجام شود.
پيشنهادم WebSocket, gRPC يا Redis Pub/Sub است.

2️⃣ وقتى به message replay يا history نياز داريم.
چرا؟ چون RabbitMQ پيام‌ها رو بعد از مصرف حذف می‌كند. البته ميشه كانفيگ كرد كه نگه داره.

3️⃣ زمانى كه پيام‌ها حجمشون خيلى زياد است.
چرا؟ چون در حجم بالا RabbitMQ دچار افت performance می‌شود. Kafka رو پيشنهاد میكنم براى اینجا.

4️⃣ وقتى ترتيب دقيق پردازش پيام‌ها خيلى مهم است. چرا؟ چون RabbitMQ تضمين دقيقى براى ترتيب پيام‌ها ندارد اينجا هم پیشنهادم Kafka است

بقيه‌اش رو شما بگید.👇🏻
🌱 توسعه‌دهنده جونیور از EF Core مستقیم در کنترلر استفاده می‌کنه.

🌳 توسعه‌دهنده میدلول از الگوی Controller-Service-Repository استفاده می‌کنه.

🌲 توسعه‌دهنده سینیور از معماری تمیز (Clean Architecture) استفاده می‌کنه.

🤯 معمار دوباره برمی‌گرده به استفاده از EF Core در کنترلر!

چرا این اتفاق میفته؟ 🤔
• چون پروژه‌های واقعی از راه حل‌های ساده سود می‌برن.

• فقط زمانی لایه اضافه کن که واقعاً بهش نیاز داری.

• یه اپلیکیشن کوچیک می‌تونه با EF Core در کنترلرها یا Minimal API endpoints زنده بمونه.

• یه پروژه متوسط ممکنه از یه لایه اپلیکیشن (مثل سرویس‌ها) سود ببره.

• یه سیستم بزرگ ممکنه به برش‌های عمودی (Vertical Slices) و معماری تمیز یا حتی DDD نیاز داشته باشه.

📌درس اصلی اینه: از اول کار، بیش از حد مهندسی (over-engineer) نکن.

قدم به قدم بساز.
اگه به پیچیدگی بیشتری نیاز پیدا کردی، اضافه‌ش کن.
اگه نه، ساده نگهش دار و برو جلو.

شما همیشه می‌تونید بعداً کد رو بازآرایی (refactor) یا گسترش بدید.
روی تحویل ارزش تمرکز کن، نه روی ساختن لایه‌ها. 🎯

💡: خودِ EF Core از قبل الگوهای Repository و Unit Of Work رو پیاده‌سازی کرده، پس در اکثر مواقع شما نیازی به ساختن کلاس‌های ریپازیتوری خودتون ندارید.
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏛 راهنمای کامل وراثت پیشرفته در #C: از virtual تا sealed


آماده‌اید برای یک شیرجه عمیق به قلب تپنده برنامه‌نویسی شیءگرا؟ امروز می‌خوایم تمام ابزارهای #C برای کنترل دقیق وراثت و چندریختی رو کالبدشکافی کنیم.

1️⃣ تعریف قرارداد برای فرزندان: virtual و abstract


برای اینکه به کلاس‌های فرزند اجازه بدیم رفتار کلاس پدر رو تخصصی‌سازی کنن، از این دو کلمه کلیدی استفاده می‌کنیم:

virtual (قرارداد اختیاری): ✍️

با virtual، کلاس پایه یک پیاده‌سازی پیش‌فرض ارائه میده ولی به فرزندان اجازه override کردن (بازنویسی) اون رفتار رو میده.
public class Asset
{
public string Name;
public virtual decimal Liability => 0;
}
public class House : Asset
{
public decimal Mortgage;
public override decimal Liability => Mortgage;
}


abstract (قرارداد اجباری): 🏗

با abstract، کلاس پایه هیچ پیاده‌سازی‌ای ارائه نمیده و فرزندان رو مجبور به override کردن و پیاده‌سازی اون عضو می‌کنه. کلاسی که عضو abstract داره، خودش هم باید abstract باشه و نمیشه ازش نمونه (new) ساخت.
public abstract class Asset
{
public abstract decimal NetValue { get; }
}
public class Stock : Asset
{
public long SharesOwned;
public decimal CurrentPrice;
public override decimal NetValue => CurrentPrice * SharesOwned;
}


2️⃣ مخفی کردن عضو با new (Hiding)

گاهی وقتا یه عضو در کلاس فرزند، هم‌اسم عضوی در کلاس پدر میشه. به این کار میگن مخفی کردن (Hiding). کلمه کلیدی new فقط برای اینه که به کامپایلر بگیم "من می‌دونم دارم چیکار می‌کنم، این کارم عمدیه!" و جلوی Warning رو بگیریم.

3️⃣ دوئل بزرگ: override در برابر new ⚔️

این مهم‌ترین بخش ماجراست. تفاوت این دو، در رفتار چندریختی (Polymorphic) مشخص میشه:

• override:
رفتار واقعی چندریختی رو پیاده می‌کنه. مهم نیست متغیر شما از چه نوعی باشه (پدر یا فرزند)، همیشه متدِ نوعِ واقعی آبجکت صدا زده میشه.

• new:
چندریختی رو می‌شکنه! متدی که صدا زده میشه، بستگی به نوع متغیر در زمان کامپایل داره، نه نوع واقعی آبجکت.

این مثال رو ببینید تا کامل متوجه بشید:
public class BaseClass 
{
public virtual void Foo() { Console.WriteLine("BaseClass.Foo"); }
}
public class Overrider : BaseClass
{
public override void Foo() { Console.WriteLine("Overrider.Foo"); }
}

public class Hider : BaseClass
{
public new void Foo() { Console.WriteLine("Hider.Foo"); }
}

// --- نتایج ---
Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // خروجی: Overrider.Foo
b1.Foo(); // خروجی: Overrider.Foo <- (چون override شده، رفتار واقعی حفظ میشه)

Hider h = new Hider();
BaseClass b2 = h;
h.Foo(); // خروجی: Hider.Foo
b2.Foo(); // خروجی: BaseClass.Foo <- (چون new شده، متد کلاس پایه اجرا میشه!)


4️⃣ قفل کردن وراثت با sealed 🔒

حالا اگه بخواید جلوی override شدن بیشتر رو در زنجیره وراثت بگیرید، از sealed استفاده می‌کنید.

• sealed روی متد:
می‌تونید یه متد override شده رو sealed کنید تا کلاس‌های فرزند بعدی دیگه نتونن اون رو تغییر بدن.

• sealed روی کلاس:
یا می‌تونید کل یک کلاس رو sealed کنید تا دیگه هیچ کلاسی نتونه ازش ارث‌بری کنه.

🤔 حرف حساب

تسلط بر این کلمات کلیدی، شما رو از یه کاربر ساده وراثت، به یک معمار واقعی کلاس‌ها تبدیل می‌کنه.

🔖 هشتگ‌ها:
#OOP #Inheritance #Override #Sealed #Polymorphism
Outbox Pattern✨️
پیاده‌سازی الگوی Outbox 📬


در سیستم‌های توزیع‌شده، ما اغلب با چالش همگام‌سازی دیتابیس و سیستم‌های خارجی مواجه هستیم. تصور کنید سفارشی را در دیتابیس ذخیره کرده و سپس پیامی را به یک message broker منتشر می‌کنید. اگر هر یک از این عملیات‌ها شکست بخورد، سیستم شما در وضعیتی ناسازگار (inconsistent) قرار می‌گیرد.

الگوی Outbox این مشکل را با در نظر گرفتن انتشار پیام به عنوان بخشی از تراکنش دیتابیس شما حل می‌کند. به جای انتشار مستقیم پیام‌ها، ما آن‌ها را در یک جدول Outbox در دیتابیس خود ذخیره می‌کنیم و از عملیات اتمیک اطمینان حاصل می‌کنیم. سپس یک فرآیند جداگانه این پیام‌ها را به طور قابل اعتماد منتشر می‌کند.

در این مقاله، ما به پیاده‌سازی این الگو در NET.، از راه‌اندازی تا مقیاس‌پذیری، خواهیم پرداخت.

چرا به الگوی Outbox نیاز داریم؟ 🤔

الگوی transactional Outbox یک مشکل رایج را در سیستم‌های توزیع‌شده حل می‌کند. این مشکل زمانی رخ می‌دهد که شما باید دو کار را همزمان انجام دهید: ذخیره داده و ارتباط با یک کامپوننت خارجی.

برای مثال، یک میکروسرویس را تصور کنید که باید:

🔹️ یک سفارش جدید را در دیتابیس خود ذخیره کند.

🔹️ در مورد این سفارش جدید به سیستم‌های دیگر اطلاع دهد.

🔹️ اگر یکی از این مراحل شکست بخورد، سیستم شما می‌تواند در وضعیتی ناسازگار قرار گیرد.
// کد با مشکل سازگاری بالقوه
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
// ...
await unitOfWork.CommitAsync(cancellationToken);

// تراکنش دیتابیس در این نقطه کامل شده است
// 💥 اگر برنامه اینجا کرش کند یا event bus در دسترس نباشد چه؟
await eventBus.Send(new OrderCreatedIntegrationEvent(order.Id));

return new OrderDto { Id = order.Id, Total = order.Total };
}


الگوی transactional Outbox به حل این مشکل کمک می‌کند. 📈 ما هم سفارش و هم پیام Outbox را در یک تراکنش دیتابیس واحد ذخیره می‌کنیم. این یک عملیات "همه یا هیچ" است.

سپس یک پردازشگر Outbox جداگانه، ارسال واقعی پیام را مدیریت می‌کند. ⚠️ نکته مهمی که باید در اینجا متوجه شوید این است که الگوی Outbox به ما تحویل حداقل-یک‌باره (at-least-once delivery) را می‌دهد. این یعنی ما باید مصرف‌کنندگان پیام خود را idempotent بسازیم.

پیاده‌سازی الگوی Outbox 💾

ابتدا، جدول Outbox خود را ایجاد می‌کنیم:
CREATE TABLE outbox_messages (
id UUID PRIMARY KEY,
type VARCHAR(255) NOT NULL,
content JSONB NOT NULL,
occurred_on_utc TIMESTAMP WITH TIME ZONE NOT NULL,
processed_on_utc TIMESTAMP WITH TIME ZONE NULL,
error TEXT NULL
);

و کلاس #C برای نمایش ورودی Outbox:
public sealed class OutboxMessage
{
public Guid Id { get; init; }
public string Type { get; init; }
public string Content { get; init; }
public DateTime OccurredOnUtc { get; init; }
public DateTime? ProcessedOnUtc { get; init; }
public string? Error { get; init; }
}


✨️یک رویکرد زیبا برای پیاده‌سازی این، استفاده از domain events برای نمایش نوتیفیکیشن‌ها است. قبل از تکمیل تراکنش، می‌توانید تمام رویدادها را برداشته و آن‌ها را به عنوان پیام‌های Outbox ذخیره کنید.
پردازش Outbox ⚙️

پردازشگر Outbox کامپوننت بعدی است که به آن نیاز داریم. این می‌تواند یک فرآیند فیزیکی جداگانه یا یک background worker در همان فرآیند باشد.

من از Quartz برای زمان‌بندی background jobها برای پردازش Outbox استفاده خواهم کرد.
[DisallowConcurrentExecution]
public class OutboxProcessorJob(...) : IJob
{
public async Task Execute(IJobExecutionContext context)
{
// پیام‌های پردازش نشده را از دیتابیس واکشی می‌کند
var messages = await connection.QueryAsync<OutboxMessage>(...);
foreach (var message in messages)
{
try
{
// پیام را deserialize کرده و به message broker منتشر می‌کند
await publishEndpoint.Publish(deserializedMessage);

// پیام را به عنوان پردازش شده علامت‌گذاری می‌کند
await connection.ExecuteAsync(
"UPDATE outbox_messages SET processed_on_utc = @ProcessedOnUtc WHERE id = @Id",
...);
}
catch (Exception ex)
{
// خطا را لاگ می‌کند
}
}
await transaction.CommitAsync();
}
}


یک راه جایگزین برای پردازش پیام‌های Outbox، استفاده از Transaction log tailing است. ما می‌توانیم این را با استفاده از Postgres logical replication پیاده‌سازی کنیم.

ملاحظات و مزایا و معایب 🧐

الگوی Outbox، ضمن کارآمدی، پیچیدگی و نوشتن‌های اضافی در دیتابیس را به همراه دارد.

🔹 توصیه می‌کنم مکانیزم‌های تلاش مجدد (retry) را در پردازشگر Outbox برای بهبود قابلیت اطمینان پیاده‌سازی کنید.

🔹 ضروری است که مصرف‌کنندگان پیام idempotent را پیاده‌سازی کنید.

🔹 با گذشت زمان، جدول Outbox می‌تواند به طور قابل توجهی رشد کند. مهم است که یک استراتژی آرشیو از همان ابتدا پیاده‌سازی کنید.

مقیاس‌پذیری پردازش Outbox 🚀

با رشد سیستم شما، ممکن است یک پردازشگر Outbox نتواند حجم پیام‌ها را مدیریت کند.

🔹 یک رویکرد ساده، افزایش فرکانس اجرای job پردازشگر Outbox است.

🔹 یک استراتژی مؤثر دیگر، افزایش اندازه بچ (batch size) هنگام واکشی پیام‌های پردازش نشده است.

🔹 برای سیستم‌های با حجم بالا، پردازش Outbox به صورت موازی می‌تواند بسیار مؤثر باشد. یک مکانیزم قفل‌گذاری برای ادعای بچ‌های پیام پیاده‌سازی کنید.

جمع‌بندی

الگوی Outbox یک ابزار قدرتمند برای حفظ سازگاری داده در سیستم‌های توزیع‌شده است. با جداسازی عملیات دیتابیس از انتشار پیام، الگوی Outbox تضمین می‌کند که سیستم شما حتی در مواجهه با شکست‌ها، قابل اعتماد باقی بماند.

به یاد داشته باشید که مصرف‌کنندگان خود را idempotent نگه دارید، استراتژی‌های مقیاس‌پذیری مناسب را پیاده‌سازی کنید، و رشد جدول Outbox خود را مدیریت کنید.

🔖 هشتگ‌ها:
#SoftwareArchitecture #SystemDesign #Microservices #DistributedSystems #OutboxPattern #EventDrivenArchitecture #DataConsistency
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏛 کالبدشکافی وراثت در #C: سازنده‌ها، required و ترتیب اجرا

وقتی کلاس‌های فرزند می‌خوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیق‌ترین قوانین ساخت و ساز در سلسله‌مراتب وراثت شیرجه می‌زنیم.

1️⃣ کلیدواژه base: صحبت با کلاس پدر

base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:

صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base می‌تونید به پیاده‌سازی اصلی در کلاس پدر دسترسی پیدا کنید.
public override decimal Liability => base.Liability + Mortgage;

صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا می‌زنید.
public class Subclass : Baseclass
{
public Subclass(int x) : base(x) { }
}


2️⃣ قوانین سازنده‌ها در وراثت ⚖️

• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.

• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی می‌کنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل می‌گیرید!

3️⃣ required members (از C# 11):

جایگزین مدرن
گاهی وقتا زنجیره سازنده‌ها خیلی طولانی میشه. از #C 11 به بعد، می‌تونید پراپرتی‌ها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت می‌سازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
public class Asset
{
public required string Name;
}
// درسته
Asset a1 = new Asset { Name = "House" };

// خطای زمان کامپایل! چون Name مقداردهی نشده
Asset a2 = new Asset();


💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، می‌تونید از اتریبیوت [SetsRequiredMembers] استفاده کنید.

4️⃣ ترتیب دقیق ساخت (Deep Dive) 🔬

وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:

فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.

• آرگومان‌های پاس داده شده به base(...) ارزیابی میشن.

• این روند تا بالاترین کلاس در سلسله‌مراتب ادامه پیدا می‌کنه.

فاز دوم (از پدر به فرزند):
• حالا بدنه‌ی سازنده‌ها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.

مثال کتاب برای درک بهتر این ترتیب:
public class B
{
int x = 1; // مرحله ۳: این اجرا میشه
public B(int x)
{
// مرحله ۴: بدنه سازنده پدر اجرا میشه
}
}
public class D : B
{
int y = 1; // مرحله ۱: این اول از همه اجرا میشه
public D(int x)
: base(x + 1) // مرحله ۲: آرگومان base ارزیابی میشه
{
// مرحله ۵: بدنه سازنده فرزند در آخر اجرا میشه
}
}

🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاس‌های شیءگرا تبدیل می‌کنه.

🔖 هشتگ‌ها:
#OOP #Inheritance #BestPractices #Constructor
Enumها به عنوان رشته در EF Core:
کدی خواناتر برای دیتابیس شما 📜


وقتی از Enumها در Entity Framework Core استفاده می‌کنید، به صورت پیش‌فرض به شکل عدد (0, 1, 2) در دیتابیس ذخیره میشن. این کار از نظر پرفورمنس خوبه، ولی وقتی مستقیم به دیتابیس نگاه می‌کنید، این عددها هیچ معنایی ندارن! 🧐

اما یه راه حل خیلی ساده و تمیز برای افزایش خوانایی و قابلیت نگهداری دیتابیس وجود داره: ذخیره کردن Enumها به صورت رشته.

جادوی HasConversion

با استفاده از متد HasConversion در Fluent API، می‌تونید به راحتی به EF Core بگید که مقادیر Enum رو به جای عدد، به صورت نام رشته‌ای اون‌ها ذخیره کنه.

1️⃣ Enum شما:
public enum OrderStatus 
{
Pending,
Completed,
Cancelled
}

2️⃣ انتیتی شما:
public class Order 
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}

3️⃣ پیکربندی در DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Status)
.HasConversion<string>(); // تمام جادو اینجاست!
}


حالت پیش‌فرض (بدون HasConversion): 👎

| Id | Status |
| :-- | :--- |
| 1 | 0 |
| 2 | 1 |

حالت جدید (با HasConversion): 👍

| Id | Status |
| :-- | :--- |
| 1 | "Pending" |
| 2 | "Completed" |

🤔 حرف حساب و تجربه شما
این تغییر کوچیک، دیباگ کردن و کار مستقیم با دیتابیس رو خیلی راحت‌تر می‌کنه. با اینکه ذخیره‌سازی به صورت عدد کمی بهینه‌تره، اما در اکثر پروژه‌ها، خوانایی بالاتر Enum به صورت رشته، ارزشش رو داره.

</Link>

🔖 هشتگ‌ها:
#EntityFrameworkCore #EFCore #Database
6️⃣ نکته برای بهبود مهارت‌های نرم به عنوان یک مهندس نرم‌افزار 💡


1️⃣ خودآگاهی (Self-Awareness)
بازخورد گرفتن از منتور، همکاران و مدیر، یک ابزار عالی برای ساختن خودآگاهیه.

2️⃣ ارتباطات (Communication)
ارتباط واضح و موثر، برای موفقیت در توسعه نرم‌افزار حیاتیه.

🔹 اطلاعات پیچیده رو به بخش‌های کوچیکتر تقسیم کنید.

🔹 از روش‌های ارتباطی مناسب استفاده کنید (چت، تماس، جلسه).

🔹 مخاطب خودتون رو در نظر بگیرید و پیامتون رو متناسب با اونها تنظیم کنید.

3️⃣ گوش دادن فعال (Active Listening)
این بخش ضروری از ارتباطه و فقط به معنی شنیدن نیست، بلکه درک کردن پیامه.

🔹 سوالات شفاف‌کننده بپرسید.

🔹 تماس چشمی برقرار کنید.

🔹 چیزی که شنیدید رو خلاصه کنید تا مطمئن بشید درست فهمیدید.

🔹 قبل از اینکه سریع جواب بدید، یکی دو ثانیه مکث کنید.

4️⃣ ایجاد همدلی (Empathy)
همدلی یعنی توانایی درک احساسات دیگران. ایجاد همدلی با کاربران به شما کمک می‌کنه نیازهاشون رو بهتر بفهمید.

🔹 تحقیقات کاربر انجام بدید.

🔹 خودتون رو جای اونها بذارید! 👟

🔹 بازخوردهای مشتریان رو برای درک بهتر، مرور کنید.

🔹 از ابزارهای آنالیتیکس برای مشاهده رفتار مشتری استفاده کنید.

5️⃣ همکاری (Collaboration)
کار تیمی موثر برای موفقیت پروژه‌ها حیاتیه. حتی اگه تنها دولوپر تیم باشید، باز هم باید با بقیه ذی‌نفعان همکاری کنید!

🔹 تنوع رو بپذیرید! 🧑‍🤝‍🧑

🔹 فرهنگ همکاری رو تقویت کنید.

🔹 روی ایجاد اعتماد و احترام تمرکز کنید.

🔹 مسئولیت‌پذیر باشید و در مورد "شکست‌ها" شفاف باشید.

6️⃣ سازگاری (Adaptability)
پذیرای تغییر اولویت‌ها و تطبیق با نیازمندی‌های جدید باشید. من نمی‌تونم چیزهای زیادی رو به شما قول بدم، جز اینکه اولویت‌ها همیشه تغییر خواهند کرد.

🔹 بپذیرید که تغییر، اجتناب‌ناپذیره. 🔄

🔹 انعطاف‌پذیری رو با نیازهای بیزینس متعادل کنید.

🔹 پذیرای بازخورد و انتقاد سازنده باشید.

🔹 روی راه‌های موفقیت تمرکز کنید، نه روی موانع. 🚀

string link ="Link";
📖 سری آموزشی کتاب C# 12 in a Nutshell

⚔️ دوئل Overloading و Polymorphism: کدام متد اجرا می‌شود؟

در دنیای شیءگرایی، دو تا از قدرتمندترین مفاهیم، Overloading (چند متد با اسم یکسان) و Polymorphism (چندریختی) هستن. اما وقتی این دو با هم روبرو میشن، چه اتفاقی میفته؟ کامپایلر چطور تصمیم می‌گیره کدوم متد رو اجرا کنه؟

جواب این سوال، یکی از ظریف‌ترین و مهم‌ترین نکات #C هست که درک عمیق شما از زبان رو نشون میده.

1️⃣ صحنه نبرد: دو متد Overload شده

فرض کنید این کلاس‌ها و دو متد Foo رو داریم که override نشدن، بلکه overload شدن:
public class Asset { }
public class House : Asset { }
static void Foo(Asset a) => Console.WriteLine("Foo(Asset) called");
static void Foo(House h) => Console.WriteLine("Foo(House) called");


2️⃣ قانون بازی: انتخاب در زمان کامپایل! ⚖️

قانون اینه: انتخاب بین متدهای Overload شده، در زمان کامپایل و بر اساس نوع متغیر شما اتفاق میفته، نه نوع واقعی آبجکتی که در زمان اجرا داخل اون متغیره.

این رفتار با override کردن کاملاً متفاوته!
House h = new House();
// اینجا نوع متغیر h در زمان کامپایل House است، پس کامپایلر Foo(House) رو انتخاب می‌کنه.
Foo(h);
// خروجی: Foo(House) called
Asset a = new House();
// مهم! اینجا با اینکه آبجکت داخل a از نوع House است،
// اما نوع متغیر a در زمان کامپایل Asset است!
// پس کامپایلر، Foo(Asset) رو انتخاب می‌کنه.
Foo(a);
// خروجی: Foo(Asset) called


3️⃣ راه فرار: استفاده از dynamic 🚀

حالا اگه بخوایم این تصمیم رو به زمان اجرا موکول کنیم تا بر اساس نوع واقعی آبجکت تصمیم گرفته بشه، می‌تونیم متغیر رو به dynamic کست کنیم. این کار به DLR (Dynamic Language Runtime) میگه که در زمان اجرا، بهترین متد رو پیدا کنه.
Asset a = new House();
Foo((dynamic)a);
// خروجی: Foo(House) called


🤔 حرف حساب و تجربه شما
این تفاوت ظریف، نشون میده که Overloading یک نوع چندریختی در زمان کامپایل (Static Polymorphism) هست، در حالی که override کردن، چندریختی در زمان اجرا (Dynamic Polymorphism) رو پیاده می‌کنه.

🔖 هشتگ‌ها:
#DotNet #OOP #Polymorphism #Overloading
5️⃣ قابلیت EF Core که باید بدانید 💡

بیایید صادق باشیم. همه ما میلیون‌ها کار روی سرمان ریخته و شیرجه عمیق در هر گوشه و کنار EF Core ممکن است در بالای لیست اولویت‌های شما نباشد.
اما قضیه این است: EF Core قدرتمند است و دانستن چند ویژگی کلیدی می‌تواند زمان و خستگی زیادی را از شما بگیرد.
بنابراین، من شما را با تک تک ویژگی‌های EF Core بمباران نخواهم کرد.
در عوض، من پنج مورد ضروری را که واقعاً باید بدانید، دستچین کرده‌ام.

ما موارد زیر را بررسی خواهیم کرد:

1️⃣ Query Splitting
- بهترین دوست جدید دیتابیس شما

2️⃣ Bulk Updates and Deletes
- کارایی به توان دو

3️⃣ Raw SQL Queries
- وقتی نیاز دارید یاغی شوید

4️⃣ Query Filters
- برای تمیز و مرتب نگه داشتن همه چیز

5️⃣ Eager Loading
- چون تنبلی همیشه هم خوب نیست

بزن بریم!

1️⃣ Query Splitting (تقسیم کوئری) ✂️

تقسیم کوئری یکی از آن ویژگی‌های EF Core است که به ندرت به آن نیاز دارید. تا اینکه یک روز، به آن نیاز پیدا می‌کنید. Query splitting در سناریوهایی که چندین کالکشن را eager load می‌کنید، مفید است. این به ما کمک می‌کند تا از مشکل انفجار کارتزین (cartesian explosion) جلوگیری کنیم.
Department department =
context.Departments
.Include(d => d.Teams)
.Include(d => d.Employees)
.Where(d => d.Id == departmentId)
.AsSplitQuery() // جادو اینجاست
.First();

با AsSplitQuery، EF Core برای هر navigation کالکشن، یک کوئری SQL اضافی اجرا خواهد کرد. با این حال، مراقب باشید که بیش از حد از آن استفاده نکنید.

2️⃣ Bulk Updates and Deletes (آپدیت‌ها و حذف‌های دسته‌ای) ⚡️


EF Core 7
دو API جدید برای انجام آپدیت‌ها و حذف‌های دسته‌ای اضافه کرد، ExecuteUpdate و ExecuteDelete. آنها به شما اجازه می‌دهند تعداد زیادی از ردیف‌ها را در یک رفت و برگشت به دیتابیس به طور موثر آپدیت کنید.
// افزایش ۵٪ حقوق تمام کارمندان فروش در یک کوئری
context.Employees
.Where(e => e.Department == "Sales")
.ExecuteUpdate(s => s.SetProperty(e => e.Salary, e => e.Salary * 1.05m));

// حذف تمام سبدهای خرید قدیمی‌تر از یک سال در یک کوئری
context.Carts
.Where(o => o.CreatedOn < DateTime.Now.AddYears(-1))
.ExecuteDelete();

این کار بدون بارگذاری انتیتی‌ها در حافظه، مستقیماً دستور UPDATE یا DELETE را در دیتابیس اجرا می‌کند.

3️⃣ Raw SQL Queries (کوئری‌های SQL خام) 👨‍💻

EF Core 8
یک ویژگی جدید اضافه کرد که به ما اجازه می‌دهد انواع داده مپ‌نشده را با SQL خام کوئری بزنیم.
public class ProductSummary
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal TotalSales { get; set; }
}

var productSummaries = await context.Database
.SqlQuery<ProductSummary>(
$"""
SELECT p.ProductId, p.ProductName, SUM(oi.Quantity * oi.UnitPrice) AS TotalSales
FROM Products p ...
""")
.ToListAsync();

متد SqlQuery یک IQueryable برمی‌گرداند، که به شما اجازه می‌دهد کوئری‌های SQL خام را با LINQ ترکیب کنید.

4️⃣ Query Filters (فیلترهای کوئری) 🔍

فیلتر‌های کوئری مانند دستورات WHERE قابل استفاده مجدد هستند که می‌توانید به انتیتی‌های خود اعمال کنید. این فیلترها به طور خودکار به کوئری‌های LINQ هر زمان که انتیتی‌های نوع مربوطه را بازیابی می‌کنید، اضافه می‌شوند.
کاربردهای رایج:

• حذف منطقی (Soft Deletes)

• چند-مستأجری (Multi-tenancy)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// _currentTenantId بر اساس درخواست/زمینه فعلی تنظیم می‌شود
modelBuilder.Entity<Product>().HasQueryFilter(p => p.TenantId == _currentTenantId);
}

// حالا، کوئری‌ها به طور خودکار بر اساس مستأجر فیلتر می‌شوند
var productsForCurrentTenant = context.Products.ToList();
5️⃣ Eager Loading (بارگذاری مشتاقانه)

بارگذاری مشتاقانه قابلیتی در EF Core است که به شما اجازه می‌دهد انتیتی‌های مرتبط را به همراه انتیتی اصلی خود در یک کوئری دیتابیس واحد بارگذاری کنید.
internal sealed class 
VerifyEmail(AppDbContext context)
{
public async Task<bool> Handle(Guid tokenId)
{
EmailVerificationToken? token = await context.EmailVerificationTokens
.Include(e => e.User) // User مرتبط را همزمان لود کن
.FirstOrDefaultAsync(e => e.Id == tokenId);

// ...
}
}

EF Core
یک کوئری SQL واحد تولید می‌کند که جداول EmailVerificationToken و User را join می‌کند.

خلاصه 📝

پس، این هم از این! پنج ویژگی EF Core که، صراحتاً، نمی‌توانید از ندانستنشان شانه خالی کنید. به یاد داشته باشید، تسلط بر EF Core زمان می‌برد، اما این ویژگی‌ها یک پایه محکم برای ساختن فراهم می‌کنند.

یک توصیه دیگر این است که عمیقاً درک کنید دیتابیس شما چگونه کار می‌کند. تسلط بر SQL همچنین به شما اجازه می‌دهد بیشترین ارزش را از EF Core بدست آورید.

🔖 هشتگ‌ها:
#EntityFrameworkCore #EFCore #Performance #Database #SQL
💡 تکنیک Clean Code: جایگزینی if های پیچیده با متدهای گویا

شرط‌های if پیچیده، خوانایی کد را به شدت پایین می‌آورند. 😫
وقتی چند شرط مجزا با هم ترکیب می‌شوند، درک منطق در نگاه اول حتی سخت‌تر هم می‌شود.

اما می‌توانیم این مشکل را با یک بازآرایی (refactoring) ساده حل کنیم.

راه حل: ↙️
منطق شرط را به یک متد یا پراپرتی با اسم گویا منتقل کنید.

👍🏻حالا نام توصیفی متد، توضیح می‌دهد که شرط چیست.
به یاد داشته باشید: درک زبان طبیعی برای انسان‌ها، همیشه راحت‌تر از درک کد است.
📖 سری آموزشی کتاب C# 12 in a Nutshell

👑 پدر همه تایپ‌ها: object و راز Boxing/Unboxing در #C

تا حالا شده بخواید یه کالکشن بسازید که بتونه هر نوع داده‌ای رو تو خودش نگه داره، از int و bool گرفته تا string و کلاس‌های خودتون؟

این کار به لطف پدر همه تایپ‌ها در دات‌نت، یعنی System.Object، ممکنه. اما این قابلیت، یه راز عملکردی مهم به اسم Boxing و Unboxing رو تو دل خودش داره.

1️⃣ object: جد بزرگ همه!

در #C، هر نوع داده‌ای، چه Value Type (مثل int) و چه Reference Type (مثل string)، به صورت پنهان از کلاس System.Object ارث‌بری می‌کنه. این یعنی شما می‌تونید هر متغیری رو به یه متغیر از نوع object تبدیل کنید (Upcast).

مثال (یک Stack همه‌کاره):
public class Stack
{
int position;
object[] data = new object[10];
public void Push(object obj) => data[position++] = obj;
public object Pop() => data[--position];
}
// --- نحوه استفاده ---
var stack = new Stack();
stack.Push("hello");
stack.Push(123); // حتی int!
stack.Push(false); // حتی bool!

// موقع خروج، باید Downcast کنیم
int myNumber = (int)stack.Pop(); // 123
string myString = (string)stack.Pop(); // "hello"


2️⃣ جادوی پشت پرده: Boxing و Unboxing 🎁

سوال اینجاست: چطور یه int که Value Type هست، می‌تونه مثل یه object که Reference Type هست رفتار کنه؟ با جادوی Boxing و Unboxing.

• Boxing (بسته‌بندی):
وقتی شما یه Value Type (مثل int) رو داخل یه متغیر object می‌ریزید، CLR یه "جعبه" روی هیپ (Heap) می‌سازه، کپی از مقدار شما رو داخل اون میذاره و رفرنس اون جعبه رو به شما میده.

• Unboxing (باز کردن بسته):
وقتی می‌خواید مقدار رو از اون جعبه در بیارید، عملیات برعکس یعنی Unboxing اتفاق میفته که نیاز به کست صریح داره.
int x = 9;
object obj = x; // Boxing: مقدار x در یک جعبه روی هیپ کپی می‌شود
int y = (int)obj; // Unboxing: مقدار از جعبه کپی شده و به y ریخته می‌شود


3️⃣ تله‌ها و نکات مهم ⚠️ تله InvalidCastException:

Unboxing
باید به نوع دقیق و اصلی انجام بشه. اگه سعی کنید یه int باکس شده رو به long آنباکس کنید، با خطای InvalidCastException مواجه میشید.

object obj = 9; // این یک int باکس شده است
// long l = (long)obj; // InvalidCastException!

تله کپی شدن مقدار:

نکته حیاتی: Boxing یه کپی از مقدار شما رو میسازه. این یعنی اگه بعداً متغیر اصلی رو تغییر بدید، مقدار داخل جعبه دست‌نخورده باقی می‌مونه!

int i = 3;
object boxed = i; // یک کپی از 3 در boxed قرار گرفت
i = 5; // تغییر i اصلی، تأثیری روی مقدار باکس شده ندارد
Console.WriteLine(boxed); // خروجی: 3

🤔 حرف حساب و تجربه شما

دونستن مفهوم Boxing و Unboxing برای درک پرفورمنس در #C حیاتیه، چون این عملیات‌ها هزینه حافظه و پردازش دارن. به همین دلیله که جنریک‌ها (Generics) اختراع شدن تا جلوی این اتفاق رو بگیرن (که بعداً بهش می‌رسیم).

🔖 هشتگ‌ها:
#OOP #MemoryManagement #Boxing
پیاده‌سازی حذف نرم (Soft Delete) با EF Core 💾

حذف کردن یا نکردن، مسئله این است. 🤔

روش سنتی برای حذف اطلاعات در پایگاه داده، از طریق "حذف سخت" (hard delete) است. یک حذف سخت، یک رکورد را برای همیشه از جدول پایگاه داده پاک می‌کند. اگرچه این روش ساده به نظر می‌رسد، اما ریسک قابل توجهی دارد: وقتی داده‌ای حذف شد، برای همیشه از بین رفته است. 💥

به جای حذف فیزیکی یک رکورد، در "حذف نرم" (soft delete) آن را به عنوان حذف شده علامت‌گذاری می‌کنیم، معمولاً با قرار دادن یک فلگ مانند IsDeleted به مقدار true. رکورد در پایگاه داده باقی می‌ماند، اما به طور موثر از کوئری‌های عادی برنامه پنهان می‌شود. 🛡️

امروز، به جزئیات نحوه پیاده‌سازی حذف نرم با استفاده از EF Core خواهیم پرداخت. در مورد فیلترهای کوئری سراسری بحث خواهیم کرد، روش‌های کارآمد برای مدیریت داده‌های حذف نرم شده را بررسی می‌کنیم و مزایا و معایب آن را می‌سنجیم. 🚀

حذف نرم (Soft Delete) چیست؟ 🧐

حذف نرم یک استراتژی پایداری داده است که از حذف دائمی رکوردها از پایگاه داده شما جلوگیری می‌کند. به جای حذف داده از پایگاه داده، یک فلگ روی رکورد تنظیم می‌شود که آن را به عنوان "حذف شده" مشخص می‌کند.
این رویکرد به برنامه اجازه می‌دهد تا در کوئری‌های عادی این رکوردها را نادیده بگیرد. با این حال، در صورت لزوم می‌توانید این رکوردها را بازیابی کنید. حذف نرم همچنین زمانی کاربردی است که بخواهید قیود کلید خارجی (foreign key constraints) را حفظ کنید. حذف نرم یک عملیات "غیرمخرب" است، در مقابل حذف سخت که در آن داده‌ها به طور کامل حذف می‌شوند. 🔄
یک حذف سخت از دستور DELETE در SQL استفاده می‌کند:
DELETE FROM bookings.Reviews
WHERE Id = @BookingId;

از طرف دیگر، یک حذف نرم از دستور UPDATE استفاده می‌کند:
UPDATE bookings.Reviews
SET IsDeleted = 1, DeletedOnUtc = @UtcNow
WHERE Id = @BookingId;

داده‌ها هنوز در پایگاه داده وجود دارند و این عملیات قابل بازگشت است.
اما باید به خاطر داشته باشید که هنگام کوئری گرفتن از پایگاه داده، داده‌های حذف نرم شده را فیلتر کنید:
SELECT *
FROM bookings.Reviews
WHERE IsDeleted = 0;

بیایید ببینیم چگونه می‌توانیم حذف نرم را با EF Core پیاده‌سازی کنیم. 👨‍💻

حذف نرم با استفاده از رهگیرها (Interceptors) در EF Core ⚙️

رهگیرهای EF Core یک مکانیزم قدرتمند برای رهگیری و تغییر عملیات پایگاه داده فراهم می‌کنند. به عنوان مثال، می‌توانید عملیات ذخیره تغییرات (saving changes) را برای پیاده‌سازی قابلیت حذف نرم رهگیری کنید.
بیایید یک اینترفیس نشانگر ISoftDeletable برای نمایش موجودیت‌های قابل حذف نرم ایجاد کنیم:
public interface ISoftDeletable
{
bool IsDeleted { get; set; }

DateTime? DeletedOnUtc { get; set; }
}

موجودیت‌هایی که باید از حذف نرم پشتیبانی کنند، این اینترفیس را پیاده‌سازی خواهند کرد. شما باید مایگریشن مربوطه را برای ایجاد این ستون‌ها در پایگاه داده اعمال کنید.

مولفه بعدی که نیاز داریم یک SaveChangesInterceptor است که به ما اجازه می‌دهد به متد SavingChangesAsync (یا SavingChanges) متصل شویم. ما می‌توانیم به ChangeTracker دسترسی پیدا کنیم و به دنبال ورودی‌هایی بگردیم که ISoftDeletable را پیاده‌سازی کرده و برای حذف شدن علامت‌گذاری شده‌اند. این موضوع را می‌توانیم با بررسی اینکه وضعیت موجودیت (entity state) برابر با EntityState.Deleted است، تشخیص دهیم.
وقتی موجودیت‌هایی را که برای حذف علامت‌گذاری شده‌اند پیدا کردیم، در یک حلقه آن‌ها را پیمایش کرده و وضعیتشان را به EntityState.Modified تغییر می‌دهیم. همچنین باید مقادیر مربوط به پراپرتی‌های IsDeleted و DeletedOnUtc را تنظیم کنید. این کار باعث می‌شود EF به جای عملیات DELETE، یک عملیات UPDATE ایجاد کند.
public sealed class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is null)
{
return base.SavingChangesAsync(
eventData, result, cancellationToken);
}

IEnumerable<EntityEntry<ISoftDeletable>> entries =
eventData
.Context
.ChangeTracker
.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (EntityEntry<ISoftDeletable> softDeletable in entries)
{
softDeletable.State = EntityState.Modified;
softDeletable.Entity.IsDeleted = true;
softDeletable.Entity.DeletedOnUtc = DateTime.UtcNow;
}

return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}


این رویکرد تضمین می‌کند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی می‌کنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
services.AddSingleton<SoftDeleteInterceptor>();

services.AddDbContext<ApplicationDbContext>(
(sp, options) => options
.UseSqlServer(connectionString)
.AddInterceptors(
sp.GetRequiredService<SoftDeleteInterceptor>()));


فیلتر کردن خودکار داده‌های حذف نرم شده 🔍

برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئری‌ها حذف می‌شوند، می‌توانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما می‌توانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیت‌ها اعمال کنیم تا رکوردهای علامت‌گذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئری‌ها را به شدت ساده می‌کند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
}
}


یک محدودیت این است که شما نمی‌توانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئری‌ها لحاظ کنید. شما می‌توانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
dbContex.Reviews
.IgnoreQueryFilters()
.Where(r => r.ApartmentId == apartmentId)
.ToList();

کوئری‌های سریع‌تر با استفاده از ایندکس فیلتر شده (Filtered Index) 🚀


برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، می‌توانید از ایندکس‌های فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده می‌کنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی می‌کنند، بهبود می‌بخشد. اکثر پایگاه داده‌های محبوب از ایندکس‌های فیلتر شده پشتیبانی می‌کنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);

modelBuilder.Entity<Review>()
.HasIndex(r => r.IsDeleted)
.HasFilter("IsDeleted = 0");
}
}