• 1405/03/30

تمرین مگا منو :

سلام استاد عزیز، درود و احترام

داشتم کدهای پروژه رو مرور میکردم که بهتر درکشون کنم و به بحث کش و بهینه‌سازی فکر میکردم. با خودم گفتم این بحث خیلی جالبه که بتونیم سرعت لود منو رو بالا ببریم، مخصوصاً وقتی تعداد دسته‌بندی‌ها خیلی زیاد بشه یا کاربران زیادی همزمان از سایت بازدید کنن. یا اصلا اگر فرضا کش نداشته باشیم چی ؟

 

یه کم تحقیق کردم و به متد  AsNoTracking()  در EF رسیدم. منابع رسمی گفتن که این متد برای سناریوهای فقط خواندنی مثل منوها خیلی بهینه‌ست چون EF دیگه وضعیت آبجکت‌ها رو ردگیری نمیکنه و این کار باعث میشه حافظه و پردازش کمتری مصرف بشه. به نظرم این دقیقاً همون چیزیه که برای مگامنو نیاز داریم چون ما قرار نیست تغییری توی دسته‌بندی‌ها بدیم، فقط می‌خوایم نمایششون بدیم.

 

اما وسط کار به یک مشکل خوردم, وقتی از  AsNoTracking()  استفاده میکنیم، رابطه‌هابه صورت خودکار لود نمیشن. یعنی اگر دسته‌بندی‌ها زیرمجموعه داشته باشن، اونا رو نمیاره. اینجا بود که یاد  Include  افتادم. با  Include  می‌تونیم به EF بگیم که روابط مورد نظرمون رو هم لود کنه.(همونظوری که خودتون در قسمت های قبل استفاده کردید برای یوزر و رول ها)

اینجا بود که فکر کردم اگر بیایم Include  کنیم باز که قبلی میشه تفاوتی ندارند.

بعد به این نتیجه رسیدم که ما یک سری دسته بندی پدر داریم که تعدادشون کمتر هست و با خودم گفتم پس دو کار انجام بدم یکی بیام و فیلتر بزارم رو دسته بندی های پدر که تعداد کمتری در حافظه باشه  سپس یک ایندکس روی ParentId بزارم که بهتر میشه  

در کل دو راهی که من به نظرم میتونیم انجام بدیم:

راه اول: اینه که همه دسته‌بندی‌ها رو از دیتابیس بگیریم، بفرستیمشون به View و اونجا با  Where  فیلترشون کنیم. این روش ساده‌ست ولی وقتی تعداد دسته‌بندی‌ها زیاد بشه، حجم دیتایی که از دیتابیس میاد بالا میره و ممکنه روی سرعت تأثیر بذاره.

راه دوم:  اینه که همون اول توی کوئری دیتابیس، شرط  Where(c => c.ParentId == null)  رو اعمال کنیم تا فقط دسته بندی پدر رو بگیره و بعد با  Include  بیایم زیرمجموعه‌هاشون رو هم لود کنیم. اینطوری دیتای کمتری جابجا میشه و فشار کمتری به دیتابیس و سرور وارد میشه.

 

به نظرم راه دوم منطقی‌تره چون کار فیلتر کردن رو به دیتابیس می‌سپاریم . البته برای اینکه این کوئری همیشه سریع باشه،  روی فیلد ParentId ایندکس هم ایجاد کردم تا جستجوها بهینه‌تر انجام بشن.

 

 

حالا با این تفاسیر، به نظر شما کدوم روش برای پروژه‌های بزرگ و با تعداد دسته‌بندی بالا مناسب‌تره؟ ممنون میشم راهنمایی کنین چون من شک های هم دارم واقعا تفاوتی ایجاد میشه ؟ در روش اول کل دیتا میره بعد تو ویو خودمون فیلتر میکنم در روش دوم فقط دسته بندی پدر با دو سطح زیر مجموعه ها گرفته میشه و retun میشه  اگر سطح دسته بندی ها نامحدود باشه و ما بخوایم مثلا 3 یا 4 سطح فقط لود کنیم بله که تفاوت خواهد داشت اما برای این سه سطح چی؟ آیا باز تفاوتی میکنه ؟ و اینکه این کار رو برای دسترسی نقش ها هم انجام بدیم اگر بهتر هست? 

 

کد های که من نوشتم:

// Category Repository

public async Task<IEnumerable<Category>> GetAllCategoriesForMegaMenu()
{
   return await _context.Categories
   .AsNoTracking()
   .Where(c => c.ParentId == null)
   .Include(c => c.Categories)
       .ThenInclude(sc => sc.Categories)      
.ToListAsync();
   
}

 

 

ویو دسته بندی ها:


<div class="vertical-menu">
   <a href="#" class="vertical-menu-btn">دسته بندی کالاها <i class="ri-apps-fill icon"></i></a>
   <div class="vertical-menu-items">
       <ul>
           @{
               bool isShow = false;
           }

           @foreach (var parent in Model)
           {
               <li class="mega-menu-category @((!isShow)?"show":"")">
                   <a href="/category/@parent.Slug">@parent.Title</a>
                   @if (parent.Categories != null &amp;&amp; parent.Categories.Any())
                   {
                       <ul class="mega-menu">
                           @foreach (var sub1 in parent.Categories)
                           {
                               <li class="parent"><a href="/category/@sub1.Slug">@sub1.Title</a></li>
                               if (sub1.Categories != null &amp;&amp; sub1.Categories.Any())
                               {
                                   @foreach (var sub2 in sub1.Categories)
                                   {
                                       <li><a href="/category/@sub2.Slug">@sub2.Title</a></li>
                                   }

                               }
                           }
                       </ul>
                   }
               </li>
           }
     
       </ul>
   </div>
</div>

تنظیمات دسته بندی و ایندکس گذاری


builder.HasKey(c => c.Id);
builder.Property(r=>r.Title).HasMaxLength(250).IsRequired();
builder.Property(s=>s.Slug).HasMaxLength(300).IsRequired();
builder.Property(r=>r.ImageName).IsRequired();

builder.HasOne(c => c.Parent)
.WithMany(c => c.Categories)
.HasForeignKey(c => c.ParentId)
.OnDelete(DeleteBehavior.Restrict);

builder.HasIndex(c => c.ParentId)
.HasDatabaseName("IX_Categories_ParentId");
builder.HasIndex(c => c.Slug)
.IsUnique();