پروژه ASP.Net Core MVC (وب و سی شارپ)
🎯 درس جدید: تنظیم دقیق مدلها با ModelBuilder در EF Core در این مرحله از پروژه مدیریت نویسنده و کتا
الان میخوای بدونی چرا این کوئریها با اینکه شبیه هم هستن، هرکدوم ساختار متفاوتی دارن و چه زمانی باید از هرکدوم استفاده کرد. بیایم با دستهبندی، مثال، و توضیح کامل همه کوئریها رو بررسی کنیم تا هیچ ابهامی باقی نمونه.
---
🧠 دستهبندی کلی کوئریها
تو ۷ کوئری دادی که میتونیم به ۳ دسته تقسیم کنیم:
| دسته | هدف | شروع از |
|------|------|----------|
| ➊ واکشی کتابها با نام نویسنده | نمایش لیست کتابها | Books |
| ➋ واکشی نویسندهها با تعداد یا لیست کتابها | گزارشگیری نویسندهمحور | Authors |
| ➌ واکشی اطلاعات خاص بر اساس authorId | فیلتر شده بر اساس نویسنده | ترکیبی |
---
🔍 بررسی دقیق هر کوئری
---
✅ ➊ کوئری اول: کتابها با نام نویسنده (تو در تو)
csharp
var books = query
.Select(b => new
{
b.Id,
b.Title,
b.IsAvailable,
AuthorName = _context.Authors
.Where(a => a.Id == b.AuthorId)
.Select(a => a.FullName)
.FirstOrDefault()
})
.ToList();
🔹 ویژگیها:
- از Books شروع میکنی
- برای هر کتاب، با AuthorId میری سراغ جدول Authors
- Select تو در تو باعث میشه EF پشتصحنه JOIN بزنه
- مناسب برای لیست کتابها با نام نویسنده
🔸 نکته مهم: اگر b.Author بهصورت Navigation Property تعریف شده باشه، بهتره از b.Author.FullName استفاده کنی (مثل کوئری ➐)
---
✅ ➋ کوئری دوم: نویسندهها با تعداد کتابها
csharp var authors = _context.Authors .Select(a => new { a.Id, a.FullName, BookCount = _context.Books.Count(b => b.AuthorId == a.Id) }) .ToList();🔹 ویژگیها: - از Authors شروع میکنی - برای هر نویسنده، تعداد کتابهاش رو با Count محاسبه میکنی - مناسب برای داشبورد آماری یا گزارشگیری --- ✅ ➌ کوئری سوم و چهارم: واکشی اطلاعات خاص بر اساس authorId
csharp var authorName = _context.Authors .Where(a => a.Id == authorId) .Select(a => a.FullName) .FirstOrDefault(); var books = _context.Books .Where(b => b.AuthorId == authorId) .Select(b => new { b.Id, b.Title, b.IsAvailable }) .ToList();🔹 ویژگیها: - جدا جدا اطلاعات نویسنده و کتابهاش رو میگیری - مناسب برای صفحه جزئیات نویسنده یا فیلتر شده بر اساس انتخاب کاربر --- ✅ ➍ کوئری پنجم: نویسندهها با تعداد و لیست کتابهای اخیر
csharp var authors = _context.Authors .Select(a => new { a.Id, a.FullName, BookCount = _context.Books.Count(b => b.AuthorId == a.Id), Books = _context.Books .Where(b => b.AuthorId == a.Id) .OrderByDescending(b => b.Id) .Take(6) .Select(b => new { b.Id, b.Title, b.IsAvailable }).ToList() }) .ToList();🔹 ویژگیها: - ترکیب Count و Select تو در تو - برای هر نویسنده، ۶ کتاب آخرش رو میگیری - مناسب برای صفحه نویسنده با خلاصه آثار 🔸 نکته مهم: چون از _context.Books جدا استفاده میکنی، EF ممکنه کوئریهای جداگانه بزنه (وابسته به تنظیمات) --- ✅ ➎ کوئری ششم: نویسندهها با لیست عنوان کتابها (با Navigation)
csharp var authors = _context.Authors .Select(a => new { a.FullName, Books = a.Books.Select(b => b.Title).ToList() }) .ToList();🔹 ویژگیها: - از Authors شروع میکنی - از a.Books استفاده میکنی که باید Navigation Property باشه - ساده و بهینه، مناسب برای نمایش آثار نویسنده 🔸 نکته مهم: اگر Books در مدل Author تعریف نشده باشه، این کد خطا میده --- ✅ ➏ کوئری هفتم: کتابها با نام نویسنده (با Navigation)
csharp var books = _context.Books .Select(b => new { b.Title, b.IsAvailable, AuthorName = b.Author.FullName }) .ToList();🔹 ویژگیها: - سادهترین و بهینهترین روش - از b.Author.FullName استفاده میکنی که باید Navigation Property باشه - مناسب برای لیست کتابها با اطلاعات نویسنده --- 🧩 تفاوتهای کلیدی بین کوئریها
پروژه ASP.Net Core MVC (وب و سی شارپ)
🎯 درس جدید: تنظیم دقیق مدلها با ModelBuilder در EF Core در این مرحله از پروژه مدیریت نویسنده و کتا
| کوئری | شروع از | نوع Select | نیاز به Navigation | کاربرد |
|-------|----------|---------------|---------------------|---------|
| ➊ | Books | تو در تو | نه | لیست کتابها با نویسنده |
| ➋ | Authors | Count | نه | تعداد کتابها |
| ➌ | ترکیبی | جداگانه | نه | فیلتر شده بر اساس نویسنده |
| ➍ | Authors | Count + Select تو در تو | نه | لیست محدود کتابها |
| ➎ | Authors | Select ساده | بله | لیست عنوان کتابها |
| ➏ | Books | Select ساده | بله | لیست کتابها با نویسنده |
---
🎯 جملههای یادسپاری برای جلوگیری از اشتباه
- اگر از جدول شروع میکنی، فقط به فیلدهای همون جدول مستقیم دسترسی داری
- اگر میخوای اطلاعات جدول مرتبط رو بیاری، باید از Select تو در تو یا Navigation استفاده کنی
- اگر از ToList() زودتر استفاده کنی، باید Include بزنی تا اطلاعات مرتبط لود بشه
- اگر Navigation Property نداری، نمیتونی از b.Author.FullName استفاده کنی
پروژه ASP.Net Core MVC (وب و سی شارپ)
ویرایش پروژه
این سه بخش از پروژه BookSelect که گفتی، هرکدوم هدف مشخصی دارن و با هم هماهنگ هستن. بیایم دقیق و خلاصه توضیح بدیم:
---
## ✅ 1. فیلتر نمایش ندادن نویسندههای بدون کتاب در صفحه اصلی
در کنترلر
Indexاز این شرط استفاده شده:
.Where(a => _context.Books.Any(b => b.AuthorId == a.Id))این یعنی فقط نویسندههایی واکشی میشن که حداقل یک کتاب دارن. ✅ باعث میشه نویسندههای بدون کتاب اصلاً در لیست صفحه اصلی دیده نشن ✅ تعداد صفحات هم بر اساس همین فیلتر محاسبه میشه --- ## ✅ 2. غیرفعال کردن دکمه "کتابهای بیشتر" برای نویسندههای بدون کتاب در View از شرط
BookCount > 0استفاده میکنی:
@if (author.BookCount > 0) { <a href="..." class="btn btn-outline-primary">کتابهای بیشتر</a> } else { <button class="btn btn-outline-secondary" disabled>کتابی ندارد</button> }✅ وقتی نویسنده کتاب نداره، دکمه لینکدار نمایش داده نمیشه ✅ بهجاش یه دکمه غیرفعال با پیام مناسب نشون داده میشه ✅ تجربه کاربری بهتر و بدون خطا --- ## ✅ 3. استفاده از `AsNoTracking()` در حذف نویسنده در اکشن
DeleteConfirmedاز این خط استفاده شده:
var author = _context.Authors .AsNoTracking() .Include(a => a.Books) .FirstOrDefault(a => a.Id == id);✅
AsNoTracking()یعنی EF Core دادهها رو فقط برای خواندن واکشی میکنه ✅ چون قرار نیست
authorتغییر داده بشه (فقط بررسی و حذف میشه)، ردیابی لازم نیست ✅ باعث افزایش سرعت و کاهش مصرف حافظه میشه ✅ مناسب برای عملیاتهایی مثل حذف، که فقط بررسی و تصمیمگیری انجام میشه --- ## 🎯 جمعبندی: | بخش | هدف | نتیجه | |-----|------|--------| | فیلتر نویسندهها | حذف نویسندههای بدون کتاب از لیست | نمایش دقیق و مرتبط | | دکمه کتاب بیشتر | کنترل نمایش دکمه بر اساس تعداد کتاب | تجربه کاربری بهتر | | AsNoTracking | واکشی سبک و سریع برای حذف | بهینهسازی عملکرد |
پروژه ASP.Net Core MVC (وب و سی شارپ)
پروژه فروشگاه با سفارش
بیایم یک جمعبندی کامل از کارهایی که تا الان روی پروژه انجام دادیم و امکاناتی که پروژه داره رو لیست کنیم تا تصویر روشنی داشته باشی:
---
## ✅ کارهایی که انجام دادیم
1. مدیریت سفارشها (Orders)
- ساخت اکشن
Indexبرای نمایش لیست سفارشها - ساخت اکشن
Detailsبرای نمایش جزئیات یک سفارش - استفاده از
Includeو
ThenIncludeبرای بارگذاری محصولات هر سفارش - مرتبسازی سفارشها بر اساس تاریخ (
OrderByDescending) 2. نمایش وضعیت سفارش (Status) - تعریف
Enumبرای وضعیت سفارش (مثل Pending, Confirmed, Shipped, Canceled) - ساخت کلاس کمکی
EnumHelperبرای گرفتن متن فارسی از
Display(Name = "...")- تبدیل مقدار
Enumبه متن فارسی در کنترلر و ارسال به View - نمایش متن فارسی وضعیت در لیست سفارشها و جزئیات سفارش 3. مدیریت محصولات (Products) - اکشن
Editبرای ویرایش محصول - بررسی تکراری نبودن نام محصول - بررسی تغییر وضعیت موجودی (
IsAvailable) - اگر محصول از «موجود» به «ناموجود» تغییر کند و در سفارشها باشد → نمایش هشدار برای حذف از سفارشها - ساخت اکشن
ConfirmProductRemovalبرای نمایش صفحه تأیید - ساخت اکشن
RemoveProductFromOrdersبرای حذف محصول از سفارشها و غیرفعال کردن آن 4. بهبود تجربه کاربری - نمایش پیامهای موفقیت و خطا با
TempDataدر View - نمایش هشدار قبل از حذف محصول از سفارشها - جلوگیری از حذف ناخواسته با شرط بررسی وضعیت قبلی و جدید محصول --- ## 📦 امکانات فعلی پروژه - مدیریت سفارشها - لیست سفارشها با نام مشتری، تاریخ، وضعیت فارسی، تعداد محصولات و مبلغ کل - جزئیات سفارش شامل اطلاعات مشتری و لیست محصولات سفارش - مدیریت محصولات - افزودن محصول جدید - ویرایش محصول (نام، قیمت، وضعیت موجودی) - بررسی تکراری نبودن نام محصول - هشدار و حذف محصول از سفارشها در صورت غیرفعال شدن - زیرساخت فنی - استفاده از ASP.NET Core MVC - استفاده از Entity Framework Core برای ارتباط با دیتابیس - بارگذاری دادههای مرتبط با
Includeو
ThenInclude- استفاده از
Enumو
DisplayAttributeبرای وضعیتها - استفاده از ViewBag و TempData برای ارسال دادهها و پیامها به View --- ## 🔍 چرا
Shop & Ordersتبدیل شده به `Shop___Orders`؟ - در نامفضا (namespace) یا **اسم پروژه**، کاراکترهایی مثل فاصله (
)، علامت & (
&)، خط تیره (
-) و… مجاز نیستند. - وقتی تو اسم پروژه یا فولدرت رو گذاشتی
Shop & Orders`، کامپایلر یا ابزارهای EF Core و Visual Studio اومدن اون کاراکتر غیرمجاز (`&و فاصله) رو جایگزین کنن. - جایگزینی پیشفرض اینه که هر کاراکتر غیرمجاز → تبدیل به
_(خط زیر). - چون هم فاصله داشتی و هم `&`، چند بار جایگزینی انجام شده و نتیجه شده:
Shop___Ordersیعنی: - `Shop` ✅ - فاصله →
_-
&→
_- فاصله بعدی →
_-
Orders✅
Shop & Orders (2).zip
حجم:
24.9M
این دو قطعه کد با هم کار میکنن تا وقتی کاربر هیچ محصولی انتخاب نکرده، یک پیام خطا بهصورت واضح در صفحه نمایش داده بشه. بیایم مرحلهبهمرحله و ساده بررسی کنیم که هر کدوم چه کاری انجام میدن:
---
## 🧩 بخش اول: کنترلر – اعتبارسنجی انتخاب محصول
if (selectedProductIds == null || !selectedProductIds.Any())
{
ModelState.AddModelError("", "حداقل یک محصول باید انتخاب شود");
}
### ✅ توضیح:
-
selectedProductIds == nullیعنی هیچ لیستی ارسال نشده -
!selectedProductIds.Any()یعنی لیست خالیه (هیچ محصولی انتخاب نشده) - اگر یکی از این دو شرط برقرار باشه، یعنی کاربر هیچ محصولی انتخاب نکرده ### 🔔 نتیجه: با ModelState.AddModelError("", "...") یک خطا به مدل اضافه میشه 🔹 چون کلید خالی (
"") استفاده شده، این خطا به هیچ فیلد خاصی وصل نیست 🔹 پس باید با
ValidationSummaryنمایش داده بشه --- ## 🧩 بخش دوم: View – نمایش پیام خطا @Html.ValidationSummary(true, "", new { @class = "text-danger" }) ### ✅ توضیح: -
trueیعنی خطاهای غیرفیلدی هم نمایش داده بشن (مثل همین مورد که کلیدش
""هست) -
""یعنی هیچ متن اضافی قبل از لیست خطاها نمایش داده نشه -
new { @class = "text-danger" } یعنی خطاها با رنگ قرمز (کلاس Bootstrap) نمایش داده بشن
---
## 🧠 مثال تصویری در صفحه:
اگر کاربر هیچ محصولی انتخاب نکنه، این پیام در بالای فرم نمایش داده میشه:
❌ حداقل یک محصول باید انتخاب شود
و چون text-dangerاستفاده شده، قرمز دیده میشه.
لیستی کامل و دستهبندیشده از مهمترین کدهای Fluent API در Entity Framework Core برات آماده کردم—با توضیح فارسی کنار هر کد، تا هم راحت یاد بگیری، هم اشتباه نکنی.
---
## 🔷 ۱. تنظیم پراپرتیها (Property Configuration)
entity.Property(x => x.Name).IsRequired(); // اجباری بودن فیلد
entity.Property(x => x.Name).HasMaxLength(100); // محدودیت طول
entity.Property(x => x.Price).HasColumnType("decimal(18,2)"); // نوع دیتابیس
entity.Property(x => x.CreatedAt).HasDefaultValueSql("GETDATE()"); // مقدار پیشفرض از SQL
entity.Property(x => x.IsActive).HasDefaultValue(true); // مقدار پیشفرض بولی
entity.Property(x => x.Description).IsUnicode(false); // ذخیره بهصورت غیر یونیکد
---
## 🔷 ۲. تنظیم کلید اصلی و کلید خارجی
entity.HasKey(x => x.Id); // کلید اصلی
entity.HasAlternateKey(x => x.Code); // کلید جایگزین (Unique)
entity.HasIndex(x => x.Email).IsUnique(); // ایندکس یکتا
entity.HasIndex(x => new { x.Title, x.AuthorId }).IsUnique(); // ایندکس ترکیبی یکتا
---
## 🔷 ۳. تنظیم رابطهها (Navigation & Relationships)
### 🔹 یکبهچند (One-to-Many)
entity.HasMany(x => x.Books)
.WithOne(x => x.Author)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.Restrict); // جلوگیری از حذف نویسنده دارای کتاب
### 🔹 یکبهیک (One-to-One)
entity.HasOne(x => x.Profile)
.WithOne(x => x.User)
.HasForeignKey<UserProfile>(x => x.UserId);
### 🔹 چندبهچند (Many-to-Many)
entity.HasMany(x => x.Tags)
.WithMany(x => x.Posts)
.UsingEntity(j => j.ToTable("PostTags")); // جدول واسط
---
## 🔷 ۴. تغییر نام جدول و ستون
entity.ToTable("Authors"); // نام جدول
entity.Property(x => x.FullName).HasColumnName("AuthorName"); // نام ستون
---
## 🔷 ۵. تنظیم رفتار حذف (Delete Behavior)
.OnDelete(DeleteBehavior.Cascade); // حذف همراه با وابستهها .OnDelete(DeleteBehavior.Restrict); // جلوگیری از حذف اگر وابسته وجود دارد .OnDelete(DeleteBehavior.SetNull); // مقدار کلید خارجی null میشود--- ## 🔷 ۶. تنظیم ترتیب و ترتیب بارگذاری entity.HasIndex(x => x.CreatedAt).HasDatabaseName("IX_CreatedAt"); // نام ایندکس entity.HasData(new Author { Id = 1, FullName = "علیرضا" }); // مقدار اولیه (Seed Data) --- ## ✨ جمله یادسپاری: > «Fluent API یعنی کنترل دقیق ساخت جدولها، رابطهها، محدودیتها و رفتارها—با کدهای زنجیرهای و قابل فهم.»
بیایم بررسی کنیم که آیا در این خط ساده:
var author = _context.Authors.Find(id);استفاده از
.AsNoTracking()لازم هست یا نه. --- ## 🧩 اول بفهمیم
.Find(id)چیکار میکنه -
Find(id)از EF Core استفاده میکنه تا رکوردی با کلید اصلی (
Id) رو پیدا کنه - این متد همیشه با Tracking کار میکنه - یعنی EF تغییرات روی
authorرو ردگیری میکنه و اگر بعداً
SaveChanges()بزنی، تغییرات ذخیره میشن --- ## ✅ آیا میتونی
.AsNoTracking()بزنی؟ نه ❌ روی
Find(id)نمیتونی
.AsNoTracking()بزنی چون
Find()از ردگیری داخلی EF استفاده میکنه و اگر قبلاً اون رکورد رو لود کرده باشه، از حافظه خودش برمیداره. --- ## 🔧 اگر بخوای نسخه بدون Tracking داشته باشی: باید از
FirstOrDefault()یا
SingleOrDefault()استفاده کنی:
var author = _context.Authors .AsNoTracking() .FirstOrDefault(a => a.Id == id);✅ این نسخه داده رو بدون ردگیری میاره، مناسب برای نمایش فقط ❌ ولی اگر بخوای داده رو ویرایش کنی، نباید
AsNoTracking()بزنی --- ## ✨ جمله یادسپاری: > «
Find(id)همیشه با Tracking کار میکنه—اگر فقط میخوای نمایش بدی، از
FirstOrDefaultبا
AsNoTrackingاستفاده کن.» ---
سلام دوستان عزیز 🌱
میخواستم یه نکته مهم رو باهاتون در میون بذارم:
من تولیدکننده حرفهای محتوا نیستم، فقط یه علاقهمند به برنامهنویسیام که مثل خیلی از شما، در مسیر یادگیری قدم گذاشته.
با توجه به شرایط کاریام، فقط جمعهها فرصت دارم که تمرین کنم و چیزهایی که یاد میگیرم رو با شما به اشتراک بذارم.
محتواهایی که منتشر میکنم ممکنه پایهای یا برای بعضیها تکراری باشه، اما برای من هر بخش کوچکی از کد یه قدم مهمه برای فهم عمیقتر.
هدفم اینه که با تمرینهای مداوم، حتی اگر تعداد پروژهها زیاد بشه، مفاهیم رو بهخوبی درک کنم و ملکه ذهنم بشه.
اگر محتواها ساده یا ابتدایی هستن، منو ببخشید 🙏
من استاد نیستم، فقط یه یادگیرندهام—مثل شما.
و هر چیزی که یاد میگیرم، با عشق و صداقت منتشر میکنم.
BookSelect (4).zip
حجم:
24.7M
صفحهبندی پیشرفته با Razor و Bootstrap
در این ساختار، فقط چند شماره اطراف صفحه فعلی نمایش داده میشن، با دکمههای «اول»، «قبلی»، «بعدی» و «آخر» برای حرکت سریع. نقطهچینها قبل و بعد از صفحات، به کاربر کمک میکنن مسیر طولانی رو بهتر درک کنه. ظاهر با کلاسهای Bootstrap طراحی شده تا هم زیبا باشه، هم واکنشگرا.
Shop & Orders (3).zip
حجم:
24.9M
ساده کردن کدهای enum helper
سادهسازی نمایش Enum در پروژههای MVC
برای تبدیل مقادیر Enum به متن قابلنمایش (مثل فارسی)، میتونیم از ویژگی
[Display(Name = "...")]استفاده کنیم. بهجای نوشتن کدهای طولانی در کنترلر، با ساخت یک تابع ساده مثل `GetDisplayName()`، هم در کنترلر و هم در View میتونیم متن مناسب رو دریافت کنیم. این کار باعث تمیز شدن کد، کاهش تکرار، و افزایش خوانایی پروژه میشه.