استفاده از روالهای مشترک ولی موردی در جداول
نویسنده: هادی مزارعی
تاریخ: ۱۴۰۴/۰۸/۰۳ ۲۰:۳۰
آدرس: www.dntips.ir
null باقی خواهند ماند، یک جدول با نام Comment ایجاد کنم و ستونهای آن بصورت زیر باشد:List از Commentهای متناظر با جدول واکشی شده قرار داد تا برنامه سمت Client از آن استفاده کند. آیا پیاده سازی این روش اشکال فنی دارد؟Product و Order باشند. این مدلها به طور معمول تعریف میشوند و نباید شامل ستون Comment باشند.// مدل جدول Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// (اختیاری) یک Navigation Property برای دسترسی آسان به کامنتها
public ICollection<Comment> Comments { get; set; } = new List<Comment>();
}
// مدل جدول Order
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public int CustomerId { get; set; }
// (اختیاری) یک Navigation Property برای دسترسی آسان به کامنتها
public ICollection<Comment> Comments { get; set; } = new List<Comment>();
}public class Comment
{
public int Id { get; set; }
// ۱. کلید اصلی ردیفی که کامنت برای آن ثبت شده (TableId در طرح شما)
public int EntityId { get; set; }
// ۲. نوع موجودیت (TableName در طرح شما)
// بهتر است از یک Enum یا رشته (string) برای نگهداری نام نوع استفاده شود.
// در اینجا از رشته برای سادگی استفاده شده است.
public string EntityType { get; set; }
// محتوای کامنت
public string Content { get; set; }
// اطلاعات ثبت
public int UserId { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// (اختیاری) Navigation Property برای کاربر ثبت کننده
// این یک Foreign Key سنتی و واقعی است.
public User CreatedBy { get; set; }
}OnModelCreating کلاس DbContext، شما باید EF Core را پیکربندی کنید. از آنجا که نمیتوانید یک Foreign Key سنتی بین Comment.EntityId و 100 جدول دیگر ایجاد کنید، از روشهایی برای تعریف روابط یکطرفه یا استفاده از Property Access و Query Filters استفاده میشود.public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// ۱. تعریف کلید اصلی مرکب غیرسنتی
// اگرچه EF Core از لحاظ فنی یک PK مرکب (EntityType, EntityId) نمیخواهد،
// اما از لحاظ منطقی برای جستجوی سریع به آن نیاز دارید (با ایندکس).
// ۲. تعریف ایندکس حیاتی: این مهمترین بخش برای عملکرد است.
modelBuilder.Entity<Comment>()
.HasIndex(c => new { c.EntityType, c.EntityId })
.IsUnique(false); // Index باید غیر یکتا باشد
// ۳. حذف Navigation Properties (اختیاری)
// برای جلوگیری از خطا، باید به EF Core بگویید که Navigation Properties
// در مدلهای Product و Order را نادیده بگیرد.
modelBuilder.Entity<Product>()
.Ignore(p => p.Comments);
modelBuilder.Entity<Order>()
.Ignore(o => o.Comments);
// توجه: اگرچه در بالا Ignore کردیم، اما میتوانیم در صورت نیاز از
// Shadow Properties و TPH/TPT برای تعریف این روابط استفاده کنیم که پیچیدهتر است.
}
}Include() استاندارد EF Core برای بارگذاری خودکار کامنتها استفاده کنید، بلکه باید از یک فیلتر صریح استفاده کنید:public class DataService
{
private readonly AppDbContext _context;
public DataService(AppDbContext context)
{
_context = context;
}
public async Task<ProductOutputModel> GetProductWithCommentsAsync(int productId)
{
// مرحله ۱: واکشی ردیف اصلی
var product = await _context.Products.FindAsync(productId);
if (product == null) return null;
// مرحله ۲: واکشی کامنتهای مرتبط با استفاده از فیلتر
var comments = await _context.Comments
.Where(c => c.EntityType == nameof(Product) && c.EntityId == productId)
.OrderByDescending(c => c.CreatedAt)
// (اختیاری) اگر نیاز به اطلاعات کاربر ثبت کننده است:
.Include(c => c.CreatedBy)
.ToListAsync();
// مرحله ۳: نگاشت به مدل خروجی (Output Model)
var outputModel = new ProductOutputModel
{
Id = product.Id,
Name = product.Name,
Comments = comments.Select(c => new CommentDto { /* نگاشت ویژگیها */ }).ToList()
};
return outputModel;
}
}(EntityType, EntityId) است تا واکشی کامنتها سریع باقی بماند.