به نام خدا
سلام به دوستان عزیزم ، وقت بخیر
خب امروز تصمیم گرفتم راجع به یک موضوعی بنویسم که شاید به ظاهر خیلی ساده باشه ولی سوال و مشکل خیلی از بچه های برنامه نویس هست.
موضوع از اونجایی شروع شد که بعضی از دوستان از من سوالهایی رو راجع به نحوه ی ذخیره ی تاریخ در بانک اطلاعاتی پرسیدن و به خطاها و مشکلات زیادی برخورد کرده بودند که می خواهم کمی راجع به این موضوع صحبت کنم که ممکنه بتونه به افراد دیگری کمک کنه تا راحت تر و بهتر نرم افزار های خودشون رو طراحی کنند.
خب اولین اقدام برای ذخیره سازی تاریخ در بانک اطلاعاتی اینه که شما تایپ یا به اصطلاح DataType درستی رو برای فیلد تاریخ در نظر بگیرید. این موضوعی که مطرح می کنم وابستگی به زبان برنامه نویسی و یا دیتابیس خاصی نداره ولی من مثالها رو در SQL-Server و زبان #C می زنم .
خب قطعاً همه ی شما می دونید که برای ذخیره ی تاریخ توصیه می شه که نوع فیلد تاریخ رو DateTime در نظر بگیرید. ولی خیلی از برنامه نویسان از تایپ های رشته ای مانند nvarchar ، nchar و char برای نگهداری تاریخ استفاده می کنند که خب البته غلط هم نیست .
من با سالها تجربه ی برنامه نویسی در پروژه های مختلف و حتی دیدن فیلمهای آموزشی و مقاله های مختلف به این نتیجه رسیدم که برای پروژه های فارسی که بیشتر هم مورد بحث ما این نوع برنامه هاست ، بهتره که از هر دو نوع تایپ در جدول خودتون استفاده کنید. یعنی هم یک فیلد تعریف کنید که از جنس DateTime باشد که در آن تاریخ را بصورت میلادی و یک فیلد هم تعریف کنید که از جنس رشته ای باشد و در آن تاریخ را به شمسی ذخیره کنید.
نکته : خیلی از برنامه نویسان بر سر این موضوع که حالا نوع فیلدی که قرار است تاریخ را بصورت شمسی ذخیره کنیم ، چه تایپی قرار دهیم ، اختلاف نظر دارند و هر کسی نظر خودش رو مطرح می کنه که البته یک سری از اونها هیچ دلیل و توجیه منطقی برای ادعایی که مطرح می کنند ندارند و فقط عادت کردند که یک تایپ ثابتی رو برای اکثر فیلدهای رشته ای قرار دهند.
خب همه ی شما عزیزان می دونید که تفاوتهایی در انواع نوع داده های رشته ای مثلا در SQL-Server وجود داره که باید با دقت اونها را انتخاب کرد. خب برای ذخیره ی تاریخ به دلیل اینکه مقدار اون همیشه ثابت و به طول 10 کاراکتر می باشد(1401/01/01) و بحث فارسی یا انگلیسی بودن در آن مطرح نیست که بخواهیم از natinality استفاده کنیم ، پس بهتره که نوع فیلد رو از جنس char(10) در نظر بگیریم.
و فیلد دیگری که تاریخ را به میلادی ذخیره می کند از جنس datetime در نظر بگیریم. (شکل بالا)
حالا شاید بپرسید که چه نیازی به اینکه دو تا فیلد در نظر بگیریم هست؟
واقعیت اینه که ما معمولا برای جستجو و محاسباتی که نیاز داریم با تاریخ کار کنیم ، از فیلد تاریخ میلادی استفاده می کنیم که راحت تر و بی دردسر تره ولی در جاهایی که نیاز به نمایش تاریخ داریم ، از فیلد شمسی استفاده می کنیم و دیگه نیازی به تبدیل تاریخ نخواهیم داشت.
در ضمن خیلی از وقتها پیش میاد که ما مستقیم با داده کار می کنیم ، یعنی با SQL اطلاعات را بررسی و مشاهده و .... انجام می دهیم ، خب در چنین مواقعی فیلد تاریخ شمسی می تونه برای ما نجات دهنده باشه که بتونیم با سرعت بیشتری و بدون نیاز به تبدیل تاریخ با رکوردهای ذخیره شده کار کنیم .
همینطور که در شکل بالا ملاحظه می کنید من در فیلد Date تاریخ را بصورت شمسی ذخیره کردم و در فیلد InsertDateTime تاریخ بصورت میلادی ذخیره شده که البته زمان هم در کنار آن قرار می گیرد.
خب حالا که فلسفه و نحوه ی تعریف فیلدهای تاریخ را در دیتابیس بررسی کردیم ، می خواهیم نحوه ی فیلتر کردن و جستجو را در یک بازه ی تاریخی در زبان #C بررسی کنیم که این موضوع هم مشکل خیلی از عزیران هست.
بحثی که می خواهم مطرح کنم در Entity Framework Core هست که با استفاده از Linq To Entity
می خواهیم تاریخ ها را از طریق برنامه از کاربر بگیریم و نتیجه رو نمایش دهیم و یا هر کار دیگه ....
هدف از طرح این موضوع اینکه که با هر دو نوع فیلدی که در دیتابیس برای تاریخ ایجاد کردیم ، اینکار را انجام دهیم که شما هم ببینید که از هر دو روش امکان پذیر هست و کار می کنه.
مهم نیست که برنامه ای که می نویسید وب باشه یا ویندوزی و یا حتی وب سرویس ، در هر حالتی می توانید از این روش استفاده کنید.
DateTime from_date = GetValidDate(fromDate);
DateTime to_date = GetValidDate(toDate);
var test = DatabaseContext.FeatureValueLogs
.Where(current => current.ShopId == shopId)
.Where(current =>
current.InsertDateTime >= from_date)
.Where(current =>
current.InsertDateTime <= to_date.AddDays(1))
.ToList()
;
private DateTime GetValidDate(string date)
{
string date2 =
Regex.Replace(date, "[۰-۹]", x => ((char)(x.Value[0] - '۰' + '0'))
.ToString());
DateTime result =
DateTime.ParseExact(
date2,
"yyyy/MM/dd",
CultureInfo.GetCultureInfo("fa-IR"));
return result;
}
در کد بالا مشاهده می کنید که سه شرط همزمان بررسی شده که دو تا مربوط به بازه ی تاریخ هست که از متغیرهای fromDate و toDate استفاده شده که از کاربر گرفته می شود و به دلیل اینکه تاریخی که کاربر وارد می کند شمسی است و برای مقایسه و استفاده در دستور where باید به میلادی تبدیل شود ، لذا یک متدی نیز به نام GetValidDate نوشته شده که عملیات تبدیل را انجام می دهد.
نکته ای که باید دقت شود این است که قبل از اینکه تاریخ وارد شده را در دستور where استفاده کنید باید حتما توسط تابع فوق ، به میلادی تبدیل شود تا همه چیز درست کار کند.
اما اگر بخواهیم مستقیم در SQL-Server هم عملیات فیلتر کردن را انجام دهیم ، آنگاه می توانیم از روشی که در تصویر بالا مشاهده می کنید استفاده کنیم.
این روش هم همانند روش قبل عمل می کنیم ولی تفاوتهای جزئی دارد که در کد زیر مشاهده می کنید :
var test = DatabaseContext.FeatureValueLogs
.AsEnumerable()
.Where(current =>
GetValidDate(current.Date) >= GetValidDate(fromDate))
.Where(current =>
GetValidDate(current.Date) <= GetValidDate(toDate).AddDays(1))
.ToList()
;
همانطور که ملاحظه می کنید باید مقدار هر دو طرف را در دستور where به متد تبدیل ارسال کنیم تا فیلتر بروی تاریخ شمسی هم درست عمل کند. در این حالت هم خود فیلد Date و هم مقادیری که کاربر وارد کرده یعنی fromDate و toDate را به تابع GetvalidDate ارسال کردم تا عملیات تبدیل به میلادی انجام شود و سپس مقایسه انجام شود چون ذات مقایسه تاریخ در #C بر حسب تاریخ میلادی است.
اما نکته ی مهمی که باید حتماً به آن توجه داشته باشید استفاده از از متد () AsEnumerable است که باید قبل از دستورات where نوشته شود تا امکان مقایسه تاریخ های شمسی نیز وجود داشته باشد. اگر فراموش کنید که این دستور را بنویسید ، قطعاً سیستم به شما در RunTime خطا خواهد داد.
ضمناً شما می توانید در SQL-Server نیز با استفاده از دستور Select بروی تاریخهای شمسی هم که در جدول ذخیره کردید عملیات فیلتر کردن را مستقیم و بصورت رشته ای انجام دهید و کاملاً هم درست نتیجه را برای شما نشان خواهد داد.
نکته ی بسیار بسیار مهم :
این نکته ای که توضیح می دهم ، مشکل بسیاری از دوستانم بوده که مدتها درگیر کرده بود و نتیجه ی درستی نمی توانستند بگیرند.
نکته اینه که در #C زمانی که می خواهید بازه ی تاریخی را کنترل کنید که مثلاً بین دو تاریخی که کاربر وارد کرده فیلتر انجام شود ، حتماً و حتماً و حتماً باید به تاریخ دوم یک روز با استفاده از تابع AddDays(1) ، اضافه کنید تا نتیجه ی درستی حاصل بشه ، در غیر اینصورت درست کار نمی کند.
کلام آخر :
و کلام آخر اینکه در این مقاله سعی کردم که مشکلاتی که در این بخش برای همکاران خودم (Developer) بوجود آمده بود و تجربیاتی که دیگران تجربه کرده بودند را برای شما عنوان کنم تا شاید این مشکل برای شما هم پیش آمده باشد و بتوانید استفاده کنید.
امیدوارم که لذت برده باشید و ممنون که با من همراه بودید و برای خواندن این مقاله وقت گذاشتید.
در ضمن من آموزشهایی را بصورت فیلم و رایگان در کانال تلگرام به آدرس Academy_Csharp@ در خصوص برنامه نویسی #C ارائه می کنم و خوشحال می شوم که شما عزیزان را در آنجا هم ببینم .
مثل همیشه منتظر نظرات ، پیشنهادات و انتقادات شما سروران گرامی هستم.
موفق باشید.
علی عظیم زاده
آدرس Id من در تلگرام :
@Ali_Azimzadeh55
آدرس کانال من در تلگرام :
@Academy_Csharp
آدرس page من در اینستاگرام :
@Academy_of_computer