در Entity Framework هر زمان که بخواهیم یک جدول جدید اضافه کنیم باید یک پراپرتی از نوع DbSet به کلاس کانتکست اضافه کنیم تا با استفاده از آن به محتوای جدول مورد نظر دسترسی داشته باشیم. به عنوان مثال در کد زیر سه جدول Post,Tags,PostTags را به کلاس کانتکست اضافه کرده ایم
public class ApplicationDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<PostTag> PostTags { get; set; }
}
اگر تعداد جداول پروژه به 100 یا 200 عدد برسد چه؟ باید برای تک تک آنها یک پراپرتی در کلاس کانتکست ایجاد کنیم و این کار مقداری زمان گیر و حوصله بر است.
برای حل این مشکل میتوان با استفاده از Reflection عملیات افزودن جداول را خودکار کرد. در این مثال ما از یک Interface به نام IBaseEntity برای نشانه گذاری مدل ها ( جداول دیتابیس ) استفاده میکنیم و هر مدلی که میسازیم از این Interface ارث بری میکند. چون همه ی جداول ما باید یک کلید منحصر به فرد داشته باشند یک کلاس به اسم BaseEntity ایجاد میکنیم و پراپرتی Id را در آن قرار میدهیم همچنین از اینترفیس IBaseEntity ارث بری میکند. سپس همه ی جداول دیتابیس باید از این کلاس ( BaseEntity ) ارث بری کنند. با ارث بری کردن سایر مدل ها ( جداول دیتابیس ) از این کلاس هم نشانه IBaseEntity را دارند و هم کلید منحصر به فرد به نام Id.
ساختار کلاس BaseEntity به صورت زیر است
public interface IBaseEntity
{
}
public abstract class BaseEntity<T> : IBaseEntity
{
public T Id { get; set; }
}
public abstract class BaseEntity : BaseEntity<int>
{
}
کلاس BaseEntity یک پارامتر به عنوان تایپ قبول میکند که مشخص کننده تایپ کلید جدول است. به عنوان مثال اگر خواستید که کلید یکی از جداول شما از نوع GUID باشد میتواند به صورت زیر استفاده کنید
public class Tag : BaseEntity<Guid>
{
public string Title { get; set; }
}
در ادامه یک اکستنشن متد برای ثبت خودکار مدل ها ایجاد میکنیم
public static void RegisterAllEntities<BaseType>(this ModelBuilder modelBuilder, params Assembly[] assemblies)
{
IEnumerable<Type> types = assemblies.SelectMany(a => a.GetExportedTypes())
.Where(c => c.IsClass && !c.IsAbstract && c.IsPublic && typeof(BaseType).IsAssignableFrom(c));
foreach (Type type in types)
modelBuilder.Entity(type);
}
متد RegisterAllEntities یک پارارمتر جنریک ( BaseType ) دریافت میکند که IBaseEntity را هنگام فراخوانی متد به آن پاس میدهیم.
این متد در ورودی اسمبلی پروژه هایی که مدل های نشانه گذاری شده در آن اسمبلی ها قرار دارند را دریافت میکند و سپس آن مدل هایی که Class هستند, Abstract نیستند, Public هستند و از BaseType ارث بری کرده اند را دریافت میکند و تک تک آنهارا با متد Entity ثبت میکند.
در ادامه متد OnModelCreating را به صورت زیر بازنویسی میکنیم.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var assembly = typeof(IBaseEntity).Assembly;
modelBuilder.RegisterAllEntities<IBaseEntity>(assembly);
}
در نهایت کلاس کانتکست به صورت زیر خواهد شد.
public class ApplicationDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var assembly = typeof(IBaseEntity).Assembly;
modelBuilder.RegisterAllEntities<IBaseEntity>(assembly);
}
}
اگر بخواهیم کلاس هارا از طریق Fluent Api کانفیگ کنیم باید برای هر جدول دیتابیس یک کلاس پیکربندی مربوط به آن بسازیم. به عنوان مثال در زیر عملیات پیکربندی برای جدول Tag ها را نوشته ایم.
public class TagConfig : IEntityTypeConfiguration<Tags>
{
public void Configure(EntityTypeBuilder<Tags> builder)
{
builder.Property(a => a.TagTitle).HasMaxLength(250);
}
}
کلاس های پیکربندی جداول باید از اینترفیس IEntityTypeConfiguration ارث بری کنند و متد Configure آنرا پیاده سازی کنند. به عنوان مثال در کد بالا برای جدول Tags و فیلد TagTitle حداکثر طول رشته ای که میتواند قرار بگیرد 250 کاراکتر است.
سپس برای ثبت این کلاس باید کد زیر را در ادامه کدهای متد OnModelCreating بنویسید.
modelBuilder.ApplyConfiguration(new TagConfig());
برای رفع این مشکل میتوانیم مانند ثبت خودکار جداول دیتابیس, با استفاده از رفلکشن عملیات ثبت کلاس های پیکربندی را خودکار کنیم. برای این کار باید لیست کلاس هایی که از IEntityTypeConfiguration ارث بری کرده اند را دریافت کنیم و آنهارا به کانتکست اضافه کنیم.
public static void RegisterEntityTypeConfiguration(this ModelBuilder modelBuilder, params Assembly[] assemblies)
{
MethodInfo applyGenericMethod = typeof(ModelBuilder).GetMethods().First(m => m.Name == nameof(ModelBuilder.ApplyConfiguration));
IEnumerable<Type> types = assemblies.SelectMany(a => a.GetExportedTypes())
.Where(c => c.IsClass && !c.IsAbstract && c.IsPublic);
foreach (Type type in types)
{
foreach (Type iface in type.GetInterfaces())
{
if (iface.IsConstructedGenericType && iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))
{
MethodInfo applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]);
applyConcreteMethod.Invoke(modelBuilder, new object[] { Activator.CreateInstance(type) });
}
}
}
}
در این اکستنشن متد با استفاده از رفلکشن لیست تایپ هایی را که class هستند, abstract نیستند و public هستند را گرفته ایم و آنهارا در یک حلقه foreach قرار داده ایم و سپس عملیات foreach را مجددا بر روی لیست اینترفیس های هر تایپ انجام داده ایم و اگر اینترفیسی که از آن ارث بری کرده است از نوع جنریک باشد و از نوع IEntityTypeConfiguration باشد عملیات ثبت پیکربندی را با استفاده از Reflection انجام میدهیم.
در نهایت کد کانتکست به صورت زیر خواهد بود.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var assembly = typeof(IBaseEntity).Assembly;
modelBuilder.RegisterAllEntities<IBaseEntity>(assembly);
modelBuilder.RegisterEntityTypeConfiguration(assembly);
}
;)
Powered by Froala Editor