تسلط بر EF Core: راهنمای جامع پیکربندی مدلها با Fluent API
چرا Fluent API؟
قبل از اینکه وارد کدنویسی شویم، باید بدانیم چرا اغلب توسعهدهندگان حرفهای Fluent API را به Data Annotations ترجیح میدهند:
-
Separation of Concerns (جداسازی دغدغهها): با Fluent API، کلاسهای موجودیت (Entity) شما "تمیز" (POCO) باقی میمانند. هیچ وابستگی به System.ComponentModel.DataAnnotations در لایه Domain شما وجود نخواهد داشت.
-
قدرت بیشتر: برخی تنظیمات (مانند کلیدهای ترکیبی یا Composite Keys) فقط از طریق Fluent API امکانپذیر هستند.
-
انعطافپذیری: شما میتوانید تنظیمات را بر اساس شرایط خاص تغییر دهید یا آنها را در فایلهای جداگانه مدیریت کنید.
شروع کار: متد OnModelCreating
قلب تپنده Fluent API در کلاس DbContext و متد OnModelCreating قرار دارد. زمانی که EF Core در حال ساخت مدل است، این متد را صدا میزند.
public class MyDbContext : DbContext
{
public DbSet Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// پیکربندیها اینجا نوشته میشوند
base.OnModelCreating(modelBuilder);
}
}
تمام دستورات ما با شیء modelBuilder شروع میشوند.
پیکربندیهای سطح جدول (Table Configuration)
اولین قدم، تعیین نام جدول و Schema است. اگر این کار را نکنید، EF Core نام پراپرتی DbSet را به عنوان نام جدول در نظر میگیرد.
تغییر نام جدول و شما (Schema)
modelBuilder.Entity()
.ToTable("tbl_Blogs", schema: "blogging");
تعیین کلید اصلی (Primary Key)
به طور پیشفرض، EF Core دنبال پراپرتی Id یا TypeNameId میگردد. اما اگر نام دیگری دارید یا نیاز به کلید ترکیبی دارید:
// کلید ساده
modelBuilder.Entity()
.HasKey(b => b.BlogId);
// کلید ترکیبی (Composite Key) - بسیار مهم
// مثال: جدول واسط کاربر و نقش
modelBuilder.Entity()
.HasKey(ur => new { ur.UserId, ur.RoleId });
نکته: کلیدهای ترکیبی با Data Annotations قابل پیادهسازی نیستند و حتماً باید از Fluent API استفاده کنید.
پیکربندی پراپرتیها (Property Configuration)
اینجا جایی است که جزئیات ستونها را مشخص میکنید.
۱. الزامی بودن و طول رشته (Required & MaxLength)
modelBuilder.Entity()
.Property(b => b.Url)
.IsRequired() // Not Null
.HasMaxLength(200); // nvarchar(200)
۲. نوع داده ستون (Column Type)
گاهی اوقات میخواهید نوع دقیق SQL را مشخص کنید (مثلاً برای قیمتها یا تاریخ):
modelBuilder.Entity()
.Property(p => p.Price)
.HasColumnType("decimal(18,2)");
۳. مقادیر پیشفرض (Default Values)
اگر میخواهید وقتی رکوردی درج میشود و مقداری برای ستون ارسال نمیشود، دیتابیس خودکار مقدار دهد:
modelBuilder.Entity()
.Property(b => b.CreatedDate)
.HasDefaultValueSql("GETDATE()");
۴. نادیده گرفتن پراپرتی (Ignoring Properties)
اگر پراپرتی در کلاس دارید که نمیخواهید در دیتابیس ذخیره شود (مثلاً یک پراپرتی محاسباتی):
modelBuilder.Entity()
.Ignore(b => b.ComputedScore);
مدیریت روابط (Relationships
پیچیدهترین و در عین حال قدرتمندترین بخش Fluent API، تعریف روابط بین جداول است.
۱. رابطه یکبهچند (One-to-Many)
این رایجترین نوع رابطه است. مثلاً یک Blog چندین Post دارد.
modelBuilder.Entity()
.HasOne(p => p.Blog) // هر پست یک بلاگ دارد
.WithMany(b => b.Posts) // هر بلاگ چندین پست دارد
.HasForeignKey(p => p.BlogId) // کلید خارجی
.OnDelete(DeleteBehavior.Cascade); // رفتار هنگام حذف
۲. رابطه یکبهیک (One-to-One)
مثلاً User و UserProfile.
modelBuilder.Entity()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey(p => p.UserId);
۳. رابطه چندبهچند (Many-to-Many)
در نسخههای قدیمی (EF6)، نیاز به ایجاد کلاس واسط به صورت دستی بود. در EF Core 5.0 به بعد، این کار بسیار ساده شده است:
// مثال: دانشجو و درس
modelBuilder.Entity()
.HasMany(s => s.Courses)
.WithMany(c => c.Students)
.UsingEntity(j => j.ToTable("StudentCourses")); // نام جدول واسط
ایندکسها (Indexes)
برای افزایش سرعت کوئریها، تعریف ایندکس حیاتی است.
// ایندکس ساده
modelBuilder.Entity()
.HasIndex(u => u.Email);
// ایندکس یکتا (Unique Index) - جلوگیری از ایمیل تکراری
modelBuilder.Entity()
.HasIndex(u => u.Email)
.IsUnique();
// ایندکس ترکیبی
modelBuilder.Entity()
.HasIndex(p => new { p.FirstName, p.LastName });
سازماندهی کد: IEntityTypeConfiguration
نوشتن تمام کدهای بالا داخل متد OnModelCreating باعث میشود فایل DbContext شما هزاران خط کد داشته باشد و غیرقابل نگهداری شود.
راه حل حرفهای، استفاده از اینترفیس IEntityTypeConfiguration است. با این کار، پیکربندی هر جدول را در یک کلاس جداگانه قرار میدهید.
گام ۱: ایجاد کلاس کانفیگ
public class BlogConfiguration : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder builder)
{
builder.ToTable("Blogs");
builder.HasKey(b => b.Id);
builder.Property(b => b.Url).IsRequired().HasMaxLength(200);
// تعریف روابط همینجا انجام میشود
builder.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId);
}
}
گام ۲: ثبت در DbContext
حالا OnModelCreating بسیار خلوت میشود:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// روش دستی
// modelBuilder.ApplyConfiguration(new BlogConfiguration());
// روش اتوماتیک (پیشنهادی)
// تمام کانفیگهای موجود در اسمبلی جاری را پیدا و اعمال میکند
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
این روش استاندارد صنعتی برای پروژههای متوسط تا بزرگ است.
ویژگیهای پیشرفته
Shadow Properties
گاهی نیاز دارید ستونی در دیتابیس باشد (مثل LastUpdated) اما نمیخواهید آن را در کلاس C# خود داشته باشید.
modelBuilder.Entity()
.Property("LastUpdated");
شما میتوانید در کدهای خود با استفاده از entry.Property("LastUpdated").CurrentValue به آن دسترسی داشته باشید.
Data Seeding (دادهگذاری اولیه)
میتوانید مقادیر اولیه را هنگام ساخت دیتابیس تزریق کنید:
modelBuilder.Entity().HasData(
new Blog { BlogId = 1, Url = "http://sample.com" }
);
مقایسه سریع: Fluent API vs Data Annotations
| ویژگی | Data Annotations | Fluent API |
| سادگی | بسیار ساده | نیاز به یادگیری سینتکس دارد |
| تمیزی کد (Clean Code) | مدلها را شلوغ میکند | مدلها کاملاً تمیز میمانند |
| قابلیتها | محدود (پوشش حدود ۶۰-۷۰٪) | کامل (۱۰۰٪ قابلیتهای EF) |
| کلید ترکیبی | خیر | بله |
| اولویت اعمال | پایینتر | بالاترین اولویت (Override میکند) |
نتیجهگیری
استفاده از Fluent API ممکن است در نگاه اول پیچیدهتر از افزودن چند اتریبیوت بالای کلاسها به نظر برسد، اما سرمایهگذاری ارزشمندی است. این روش به شما اجازه میدهد تا معماری نرمافزار خود را تمیز نگه دارید، کنترل دقیقی بر پایگاه داده داشته باشید و از تمام پتانسیل EF Core استفاده کنید.
برای پروژههای واقعی و Enterprise، استفاده از رویکرد IEntityTypeConfiguration و جداسازی کانفیگها، بهترین روش (Best Practice) محسوب میشود که نگهداری کد را در درازمدت تضمین میکند.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.