در دنیای برنامهنویسی، مدیریت تاریخ و زمان یکی از آن چالشهایی است که اگر از ابتدا خشت اولش را کج بگذارید، در آینده با کوهی از مشکلات (به خصوص در محاسبات و مرتبسازی) روبرو میشوید.
در ادامه بهترین رویه (Best Practice) برای مدیریت تاریخ را با هم بررسی میکنیم:
۱. ثبت در دیتابیس: فقط میلادی!
بدون هیچ شک و تردیدی، تاریخ را در دیتابیس به صورت میلادی (ترجیحاً در فرمت UTC) ذخیره کنید. دلایل این کار بسیار حیاتی هستند:
استاندارد جهانی: تمام توابع داخلی دیتابیسها (مثل MySQL، PostgreSQL، SQL Server) برای کار با تاریخ میلادی بهینه شدهاند.
مرتبسازی (Sorting): مرتبسازی تاریخهای میلادی بسیار دقیق و سریع انجام میشود.
محاسبات: اگر بخواهید "دو هفته بعد" یا "فاصله بین دو تاریخ" را حساب کنید، توابع دیتابیس روی تاریخ میلادی به درستی کار میکنند، در حالی که برای شمسی به مشکل برمیخورید.
یکپارچگی: اگر روزی تصمیم بگیرید اپلیکیشن خود را چندزبانه کنید یا از سروری در خارج از ایران استفاده کنید، استفاده از UTC شما را نجات میدهد.
۲. نمایش به کاربر: شمسی
فقط در لحظه نمایش (View)، تاریخ میلادی را از دیتابیس بگیرید و با استفاده از کتابخانههای تبدیل تاریخ، آن را به شمسی تبدیل کرده و به کاربر نشان دهید.
۳. در مورد Input Date چه باید کرد؟
برای ورودیها دو راهکار رایج وجود دارد:
راهکار حرفهای (استفاده از DatePicker):
از کتابخانههای "تقویم شمسی" (مثل
persian-datepickerبرای وب یا کتابخانههای مشابه در اندروید و iOS) استفاده کنید. کاربر تاریخ را شمسی انتخاب میکند، اما شما در پسزمینه (سمت کلاینت یا قبل از ذخیره در دیتابیس)، آن را به میلادی تبدیل میکنید و سپس به سمت سرور میفرستید. استفاده از Input معمولی: اگر از
inputDate پیشفرض مرورگرها استفاده کنید، این ورودی معمولاً تقویم میلادی نشان میدهد. اگر اصرار دارید کاربر شمسی وارد کند، باید ورودی را به صورت متن (Text) بگیرید، با کدهای برنامهنویسی آن را اعتبارسنجی (Validation) کنید، به میلادی تبدیل کنید و سپس ذخیره نمایید. خلاصه استراتژی پیشنهادی: ۱. در دیتابیس فیلد را از نوع
DateTimeیا
Timestampو به صورت میلادی بگذارید. ۲. در فرانتاند از یک کتابخانه تقویم شمسی استفاده کنید. ۳. هنگام ارسال فرم، تاریخ شمسی را به میلادی تبدیل کرده و به دیتابیس بفرستید. ۴. هنگام دریافت داده از دیتابیس، آن را به شمسی تبدیل کرده و به کاربر نمایش دهید. برای زبانهای مختلف کتابخانههای بسیار خوبی وجود دارد؛ مثلاً در جاوااسکریپت
moment-jalaaliیا
date-fns-jalaliو در PHP کتابخانه
vertaیا
jdateاز بهترین گزینهها هستند.
Filter App (3).zip
حجم:
23.4M
در ویرایش از کتابخانه persian-datepicker استفاده شده است تقویم شمسی برای اینکه راحت تاریخ را انتخاب کنم
persian-datepicker.zip
حجم:
199.7K
کتابخانه جاوا اسکریپت
در دنیای EF Core، این دو عبارت از نظر عملکرد نهایی تقریباً یکسان هستند، اما از نظر ساختار کدنویسی و انعطافپذیری تفاوتهای ظریفی دارند که در ادامه با زبانی ساده بررسی میکنیم.
*۱. مفهوم کلی*
هر دو عبارت در حال ساخت یک *Query* هستند. در این مرحله هیچ دادهای از دایتابیس فراخوانی نمیشود (تکنیک Deferred Execution). کد شما فقط دارد به EF میگوید: "من قرار است روی این جدول کارهایی انجام دهم، اما فعلاً صبر کن تا دستور نهایی (مثل
ToList) را بدهم." *۲. بررسی عبارت اول:
()AsQueryable.* csharp var query = _db.Product.AsQueryable(); زمانی که روی یک
DbSet(مثل
Product) متد
AsQueryableرا صدا میزنید، در واقع دارید به صراحت اعلام میکنید که میخواهید با این مجموعه به عنوان یک منبع داده قابل پرسوجو رفتار شود. * *مزیت:* اگر بخواهید این کوئری را به متدهای دیگر پاس بدهید یا از Unit Testing (با دادههای Mock) استفاده کنید، این روش استانداردتر است. * *تغییر نوع:* اگر به اشتباه متدی را صدا بزنید که خروجی را به
IEnumerableتبدیل کند،
AsQueryableمیتواند دوباره آن را به جریان IQueryable برگرداند تا فیلترها همچنان در سمت دایتابیس اجرا شوند (نه در RAM). *۳. بررسی عبارت دوم: انتساب مستقیم* csharp IQueryable<Product> query = _db.Products; در اینجا شما از ویژگی "چندریختی" (Polymorphism) استفاده کردهاید. چون کلاس
DbSetخودش رابط
IQueryableرا پیادهسازی کرده است، میتوانید مستقیماً آن را درون یک متغیر از این نوع بریزید. * *سادگی:* این روش کوتاهتر است و در ۹۰ درصد کدهای معمولی استفاده میشود. * *وضوح:* با نوشتن صریح نوع داده (
IQueryable<Product>) چشمان برنامهنویس دیگر بلافاصله متوجه میشود که قرار است فیلترهای بیشتری روی این متغیر اعمال شود. *۴. تفاوت در کجاست؟* تفاوت اصلی زمانی مشخص میشود که بخواهید کدی بنویسید که هم با دیتابیس کار کند و هم با لیستهای معمولی در حافظه (برای تست واحد یا Unit Test). * *متد
()AsQueryable:* این متد مثل یک مبدل عمل میکند. اگر ورودی آن یک لیست معمولی (In-Memory) باشد، آن را شبیهسازی میکند تا مثل یک کوئری دیتابیس رفتار کند. این کار باعث میشود کدهای لایه سرویس شما بدون تغییر، هم برای دیتابیس واقعی و هم برای تستهای شما کار کنند. * *انتساب مستقیم:* بیشتر برای زمانی است که مستقیماً با خود
DbContextدر لایه Repository یا Controller کار میکنید. *خلاصه کلام* اگر در حال نوشتن یک برنامه معمولی هستید، هر دو روش خروجی یکسانی (یک دستور SQL بهینه) تولید میکنند. اما اگر به دنبال نوشتن کدی هستید که قابلیت تستگرفتن بالایی داشته باشد یا میخواهید از متغیرهای
varاستفاده کنید و در عین حال مطمئن باشید که نوع آن
IQueryableباقی میماند، استفاده از
()AsQueryableانتخاب حرفهایتری است.
Filter App (4).zip
حجم:
23.6M
پروژه را بروز کردم
که دارای انواع فیلتر و جستجو و input type range قیمت و وضعیت انتظار ، ثبت شده ، لغو شده
پروژه یک «سیستم مدیریت و فیلترینگ هوشمند پروژهها» است که با معماری ASP.NET Core MVC توسعه یافته است. این پروژه نمونهای استاندارد از یک پنل مدیریتی (Admin Panel) است که روی تعامل بهینه با دیتابیس و تجربه کاربری (UX) تمرکز دارد.
خلاصهی ویژگیهای کلیدی جهت ارائه یا انتشار:
۱. معماری و تکنولوژیها
Back-End:
* استفاده از .NET 8 (یا نسخه متناسب) و زبان C#.
Database:
* بهرهگیری از Entity Framework Core و رویکرد Code-First.
Front-End:
* طراحی واکنشگرا (Responsive) با Bootstrap 5 و شخصیسازی المانهای فرم.
Data Type:
* استفاده از Enumها برای مدیریت وضعیتها که باعث پایداری و خوانایی بالای کد شده است.
۲. قابلیتهای اصلی (Features)
جستجوی پیشرفته (IQueryable Filtering):* پیادهسازی فیلترینگ در سمت دیتابیس (Server-side) که باعث میشود حتی با وجود هزاران رکورد، سرعت سیستم حفظ شود.
فیلتر قیمت با Slider:* استفاده از ورودی
rangeبه همراه جاوااسکریپت برای نمایش لحظهای قیمت، جهت سهولت کار با فرم. مدیریت وضعیت دوگانه:* سیستم فیلتر همزمان بر اساس «وضعیت پروژه» (در انتظار، ثبت شده و...) و «وضعیت نمایش» (فعال/غیرفعال). فیلتر زمانی:* قابلیت محدود کردن نمایش خروجیها در بازههای تاریخی مشخص. ۳. نقاط قوت فنی Clean Code: * تبدیل وضعیتهای عددی به متنهای فارسی و نشانهای (Badges) رنگی در رابط کاربری برای درک بهتر کاربر. Performance: * استفاده از متدهای زنجیرهای در LINQ برای جلوگیری از بارگذاری دادههای اضافی در RAM. UX/UI: * طراحی کارتمحور (Card-based Layout) و چیدمان منظم ستونها در بخش فیلترینگ برای جلوگیری از شلوغی رابط کاربری. ۴. هدف پروژه این سیستم بستر مناسبی برای مدیریت هر نوع دادهای (از پروژههای ساختمانی گرفته تا سفارشات فروشگاه) است که نیاز به دستهبندی دقیق، جستجوی سریع و مدیریت وضعیتهای مختلف دارد. این خلاصه، هم جنبههای فنی (برای برنامهنویسان) و هم جنبههای کاربردی (برای کارفرمایان) را به خوبی پوشش میدهد.
Filter App (5).zip
حجم:
23.6M
بروزرسانی پروژه
با هر تغییر رنج قیمت غلتک رنج تغییر نمیکند
با این کار: وقتی شما قیمت را مثلاً روی ۷۰۰,۰۰۰ قرار میدهید و دکمه را میزنید، بعد از لود شدن صفحه، اسلایدر دقیقاً روی ۷۰۰,۰۰۰ باقی میماند و عدد بالای آن هم به جای ۵۰۰,۰۰۰، همان ۷۰۰,۰۰۰ را نشان میدهد.
Filter App (6).zip
حجم:
23.6M
این دفعه کامل ترین پروژه از نظر فیلتر و صفحه بندی
این هم بخشی از کد
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
@* وقتی فیلتر اعمال شود و وارد صفحه بعدی شود، فیلتر حذف نشود *@
@for (int i = 1; i <= ViewBag.TotalPages; i++)
{
<li class="page-item @(i == ViewBag.CurrentPage ? "active" : "")">
<a class="page-link"
asp-action="Index"
asp-route-page="@i"
asp-route-searchString="@ViewBag.SearchString"
asp-route-status="@ViewBag.Status"
asp-route-isActive="@ViewBag.IsActive"
asp-route-maxPrice="@ViewBag.MaxPrice"
asp-route-startDate="@ViewBag.StartDate"
asp-route-endDate="@ViewBag.EndDate">
@i
</a>
</li>
}
</ul>
</nav>
Filter App (7).zip
حجم:
23.6M
تقویم شمسی به فیلتر اضافه شد
نکته : فقط از طریق تقویم تاریخ عوض می شود
بررسی کنید این پروژه 7 می خواهید یا پروژه 6 که input text تاریخ را وارد میکنید
Filter App (8).zip
حجم:
23.6M
ببخشید این دفعه همه کد های جاوااسکریپت را دیدم و خلاصه کردم کد جاوااسکریپت در صفحه اصلی کنترلر ProductController کوتاه تر شده
RAST.zip
حجم:
16M
پروژه راست که با کمک هوش مصنوعی نوشتم