اگه با EF Core کار کرده باشید، احتمالاً خیلی وقتها برای کلید اصلی این مدلو انتخاب کردید:
builder.Property(x => x.Id)
.ValueGeneratedOnAdd();
یعنی مقدار Id توسط دیتابیس (مثلاً SQL Server با IDENTITY) ساخته میشه.
تا اینجا همهچیز کاملاً استاندارده.
اما اینجا یه دام خیلی ظریف وجود داره 👇
سناریوی مشکلدار 🧨
فرض کنید در Domain Layer اومدید برای Id یک ولیدیشن گذاشتید:
public class Order
{
public int Id { get; private set; }
public Order()
{
if (Id <= 0)
throw new DomainException("Id must be positive");
}
}
یا حتی در setter:
set
{
if (value <= 0)
throw new DomainException();
_id = value;
}
در ظاهر کاملاً منطقیه:
«شناسه نباید منفی یا صفر باشه» ✅
ولی برنامهتون موقع ()SaveChanges کرش میکنه! 💥
دلیل واقعی چیـه؟ (رفتار داخلی EF) 🧠
ءEF Core وقتی یک entity جدید رو Add میکنه، هنوز هیچ Id واقعیای نداره.
اما برای اینکه بتونه Change Tracking و Relationship ها رو مدیریت کنه، مجبور میشه:
"یک Temporary Key براش بسازه."
و این کلید موقت معمولاً:
• عدد منفیه
• فقط داخل حافظه است
• هرگز وارد دیتابیس نمیشه
مثلاً:
Order.Id = -2147482647
✨️ءEF از این مقدار استفاده میکنه تا:
• ءFKها رو ست کنه
• ءGraph از entityها رو track کنه
• بعد از Insert، مقدار واقعی رو جایگزین کنه
پس چرا برنامه کرش میکنه؟ 💣
چون شما در Domain گفتید:
«ءId منفی غیرقانونیه»
ولی EF میگه:
«من مجبورم موقتاً منفی بذارم»
و این دو تا با هم invariant همدیگه رو میشکنن.
💡نتیجه:
EF مقدار منفی ست میکنه →
Domain validation تریگر میشه →
Exception →
Crash 😬
این دقیقاً چه درسی به ما میده؟ 🎯
این یه مثال عالیه از این اصل مهم در DDD:
ءDomain نباید به مکانیزمهای زیرساختی وابسته باشه
و مهمتر:
ءId دیتابیس نباید identity دامنه باشد
راهحلهای درست (Professional Approaches) 🧩
1️⃣ اصلاً روی Id دیتابیس ولیدیشن نذار 🚫
بهترین و رایجترین راه:
// No validation on Id at all
public int Id { get; private set; }
چون:
ءDomain نباید به persistence concern حساس باشه
ءId دیتابیس یک detail فنیه، نه business rule
2️⃣ از Surrogate Key در دامنه استفاده نکن 🔑
به جای این:
Order.Id (int identity)
از identityهایی استفاده کن که در Domain تولید میشوند
(Guid/Ulid/ValueObject)
یعنی:
ءId دامنه = چیزی که خودت میسازی
ءId دیتابیس = فقط برای storage
3️⃣ اگر خیلی وسواسی هستی: Validation رو بعد از Persist انجام بده
مثلاً:
public bool IsPersisted => Id > 0;
ولی این عملاً یعنی پذیرفتی که:
ءId قبل از persistence معتبر نیست
که باز هم یعنی validation روش منطقی نیست.
نکتهی خیلی عمیق معماری 🧠🔥
این مشکل یه نمونهی کلاسیک از تضاد بین این دو دنیاست:
Domain World ORM World
Invariants Temporary keys
Business meaning Technical identity
Stability Internal mutations
و دقیقاً نشون میده چرا:
نباید مفاهیم دیتابیس رو مستقیم وارد دامنه کنیم
جمعبندی طلایی 🏆
اگر از ValueGeneratedOnAdd یا IDENTITY استفاده میکنید:
❌ هرگز روی Id در دامنه validation نگذارید
❌ هرگز Id دیتابیس رو business concept در نظر نگیرید
در عوض:
✅ دامنه = با مفاهیم بیزینسی (Value Objects)
✅ دیتابیس = فقط ابزار ذخیرهسازی
و این جمله رو همیشه یادت باشه:
ءTemporary keys در EF یک persistence concern هستند،
و نباید وارد invariants دامنه شوند.
این دقیقاً همون جاییه که مهندسی نرمافزار از «کدنویسی» جدا میشه 🚀
🔖هشتگها:
#EFCore #DDD #CleanArchitecture #ORM #DomainModel #SoftwareEngineering #DotNet