ثبت و نگهداری تاریخ خورشیدی در SQL Server از دیرباز یکی از نگرانیهای برنامهنویسان و
طراحان پایگاه دادهها بوده است. در این نوشتار، راهکار تعریف یک DataType در SQL Server 2012 به روش CLR آموزش داده
خواهد شد.
در ویژوال استودیو یک پروژهی جدید از نوع SQL Server Database Project
به شکل زیر ایجاد کنید:
متن موجود در صفحهی بازشده را کاملاً حذف کرده و با کد زیر جایگزین کنید.
(در کد زیر همهی توابع لازم برای مقداردهی به سال، ماه، روز، ساعت، دقیقه و ثانیه و البته گرفتن مقدار از آنها، تبدیل تاریخ خورشیدی به میلادی، گرفتن تاریخ به تنهایی، گرفتن زمان به تنهایی، افزایش یا کاهش زمان برپایهی یکی از متغیرهای زمان و بررسی و اعتبارسنجی انواع بخشهای زمان گنجانده شده است. در صورت پرسش یا پیشنهاد روی هر کدام در قسمت نظرات، پیام خود را بنویسید.)
using System; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; [Serializable()] [SqlUserDefinedType(Format.Native)] public struct JalaliDate : INullable { private Int16 m_Year; private byte m_Month; private byte m_Day; private byte m_Hour; private byte m_Minute; private byte m_Second; private bool is_Null; public Int16 Year { get { return (this.m_Year); } set { m_Year = value; } } public byte Month { get { return (this.m_Month); } set { m_Month = value; } } public byte Day { get { return (this.m_Day); } set { m_Day = value; } } public byte Hour { get { return (this.m_Hour); } set { m_Hour = value; } } public byte Minute { get { return (this.m_Minute); } set { m_Minute = value; } } public byte Second { get { return (this.m_Second); } set { m_Second = value; } } public bool IsNull { get { return is_Null; } } public static JalaliDate Null { get { JalaliDate jl = new JalaliDate(); jl.is_Null = true; return (jl); } } public override string ToString() { if (this.IsNull) { return "NULL"; } else { return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2"); } } public static JalaliDate Parse(SqlString s) { if (s.IsNull) { return Null; } System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar(); string str = Convert.ToString(s); string[] JDate = str.Split(' ')[0].Split('/'); JalaliDate jl = new JalaliDate(); jl.Year = Convert.ToInt16(JDate[0]); byte MonthsInYear = (byte)pers.GetMonthsInYear(jl.Year); jl.Month = (byte.Parse(JDate[1]) <= MonthsInYear ? (byte.Parse(JDate[1]) > 0 ? byte.Parse(JDate[1]) : (byte)1) : MonthsInYear); byte DaysInMonth = (byte)pers.GetDaysInMonth(jl.Year, jl.Month); ; jl.Day = (byte.Parse(JDate[2]) <= DaysInMonth ? (byte.Parse(JDate[2]) > 0 ? byte.Parse(JDate[2]) : (byte)1) : DaysInMonth); if (str.Split(' ').Length > 1) { string[] JTime = str.Split(' ')[1].Split(':'); jl.Hour = (JTime.Length >= 1 ? (byte.Parse(JTime[0]) < 23 && byte.Parse(JTime[0]) >= (byte)0 ? byte.Parse(JTime[0]) : (byte)0) : (byte)0); jl.Minute = (JTime.Length >= 2 ? (byte.Parse(JTime[1]) < 59 && byte.Parse(JTime[1]) >= (byte)0 ? byte.Parse(JTime[1]) : (byte)0) : (byte)0); jl.Second = (JTime.Length >= 3 ? (byte.Parse(JTime[2]) < 59 && byte.Parse(JTime[2]) >= (byte)0 ? byte.Parse(JTime[2]) : (byte)0) : (byte)0); } else { jl.Hour = 0; jl.Minute = 0; jl.Second = 0; } return (jl); } public SqlString GetDate() { return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2"); } public SqlString GetTime() { return this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2"); } public SqlDateTime ToGregorianTime() { System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar(); return SqlDateTime.Parse(pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0).ToString()); } public SqlString JalaliDateAdd(SqlString interval, int increment) { System.Globalization.PersianCalendar pers = new System.Globalization.PersianCalendar(); DateTime dt = pers.ToDateTime(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0); string CInterval = interval.ToString(); bool isConvert = true; switch (CInterval) { case "Year": dt = pers.AddYears(dt, increment); break; case "Month": dt = pers.AddMonths(dt, increment); break; case "Day": dt = pers.AddDays(dt, increment); break; case "Hour": dt = pers.AddHours(dt, increment); break; case "Minute": dt = pers.AddMinutes(dt, increment); break; case "Second": dt = pers.AddSeconds(dt, increment); break; default: isConvert = false; break; } if (isConvert == true) { this.Year = (Int16)pers.GetYear(dt); this.Month = (byte)pers.GetMonth(dt); this.Day = (byte)pers.GetDayOfMonth(dt); this.Hour = (byte)pers.GetHour(dt); this.Minute = (byte)pers.GetMinute(dt); this.Second = (byte)pers.GetSecond(dt); } return this.m_Year.ToString("D4") + "/" + this.m_Month.ToString("D2") + "/" + this.m_Day.ToString("D2") + " " + this.Hour.ToString("D2") + ":" + this.Minute.ToString("D2") + ":" + this.Second.ToString("D2"); } }
از منوهای بالا روی منوی Bulild و سپس گزینهی Publish prgJalaliDate کلیک کتید:
در پنجرهی بازشده روی دکمهی Edit کلیک کنید سپس تنظیمات مربوط به اتصال به پایگاه داده را انجام دهید.
روی دکمهی OK کلیک کنید و سپس در پنجرهی اولیه، روی دکمهی Publish کلیک کتید:
به همین سادگی، DataType مربوطه در SQL Server 2012 ساخته میشود. خبر خوش اینکه شما میتوانید با راستکلیک روی نام پروژه و انتخاب گزینهی Properties در قسمت Project Setting تنظیمات مربوط به نگارش SQL Server را انجام دهید. (از نگارش 2005 به بعد در VS 2012 پشتیبانی میشود.)
اکنون زمان آن رسیده است که DataType ایجادشده را در SQL Server 2012 بیازماییم. SQL Server را باز کنید و دستور زیر را در آن اجرا کتید.
USE Northwind GO CREATE TABLE dbo.TestTable ( Id int NOT NULL IDENTITY (1, 1), TestDate dbo.JalaliDate NULL ) ON [PRIMARY] GO
اکنون چند رکورد درون این جدول درج میکنیم:
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
این خطا به این خاطر است که CLR را در SQL Server فعال نکرده ایم. جهت فعالکردن CLR دستور زیر را اجرا کنید:
sp_configure 'clr enabled', 1 Reconfigure
Insert into TestTable (TestDate) Values ('1392/02/09'),('1392/02/09 22:40'),('1392/12/30 22:40')
اکنون زمان آن رسیده است که توسط یک پرسوجو، همهی توابعی که در سیشارپ برای این نوع داده نوشتیم، بیازماییم. پرسوجوی زیر را اجرا کنید:
Select TestDate.ToString() as JalaliDateTime, TestDate.GetDate() as JalaliDate, TestDate.GetTime() as JalaliTime, TestDate.ToGregorianTime() as GregorianTime, TestDate.JalaliDateAdd('Day',1) JalaliTomorrow, TestDate.Month as JalaliMonth from TestTable
نیازی به گفتن نیست که میتوانید به سادگی از توابع مربوط به DateTime در SQL Server بهره ببرید. برای مثال برای به دست آوردن فاصلهی میان دو روز از پرسوجوی زیر استفاده کنید:
Declare @a JalaliDate = '1392/02/07 00:00:00' Declare @b JalaliDate = '1392/02/05 00:00:00' SELECT DATEDIFF("DAY",@b.ToGregorianTime(),@a.ToGregorianTime()) AS DiffDate
شاد و پیروز باشید.
ساختار استکی
IL از ساختار استک استفاده میکند. به این معنی که تمامی دستور العملها داخل آن push شده و نتیجهی اجرای آنها pop میشوند. از آنجا که IL به طور مستقیم ارتباطی با ثباتها ندارد، ایجاد زبانهای برنامه نویسی جدید بر اساس CLR بسیار راحتتر هست و عمل کامپایل، تبدیل کردن به کدهای IL میباشد.
بدون نوع بودن(Typeless)
از دیگر مزیتهای آن این است که کدهای IL بدون نوع هستند. به این معنی که موقع افزودن دستورالعملی به داخل استک، دو عملگر وارد میشوند و هیچ جداسازی در رابطه با سیستمهای 32 یا 64 بیت صورت نمیگیرد و موقع اجرای برنامه است که تصمیم میگیرد از چه عملگرهایی باید استفاده شود.
Virtual Address Space
بزرگترین مزیت این سیستمها امنیت و مقاومت آن هاست. موقعی که تبدیل کد IL به سمت کد بومی صورت میگیرد، CLR فرآیندی را با نام verification یا تاییدیه، اجرا میکند. این فرآیند تمامی کدهای IL را بررسی میکند تا از امنیت کدها اطمینان کسب کند. برای مثال بررسی میکند که هر متدی صدا زده میشود با تعدادی پارامترهای صحیح صدا زده شود و به هر پارامتر آن نوع صحیحش پاس شود و مقدار بازگشتی هر متد به درستی استفاده شود. متادیتا شامل اطلاعات تمامی پیاده سازیها و متدها و نوع هاست که در انجام تاییدیه مورد استفاده قرار میگیرد.
در ویندوز هر پروسه، یک آدرس مجازی در حافظه دارد و این جدا سازی حافظه و ایجاد یک حافظه مجازی کاری لازم اجراست. شما نمیتوانید به کد یک برنامه اعتماد داشته باشید که از حد خود تخطی نخواهد کرد و فرآیند برنامهی دیگر را مختل نخواهد کرد. با خواندن و نوشتن در یک آدرس نامعتبر حافظه، ما این اطمینان را کسب میکنیم که هیچ گاه تخطی در حافظه صورت نمیگیرد.
قبلا به طور مفصل در این مورد ذخیره سازی در حافظه صحبت کرده ایم.
Hosting
از آنجا که پروسههای ویندوزی به مقدار زیادی از منابع سیستم عامل نیاز دارند که باعث کاهش منابع و محدودیت در آن میشوند و نهایت کارآیی سیستم را پایین میآورد، ولی با کاهش تعدادی برنامههای در حال اجرا به یک پروسهی واحد میتوان کارآیی سیستم را بهبود بخشید و منابع کمتری مورد استفاده قرار میگیرند که این یکی دیگر از مزایای کدهای managed نسبت به unmanaged است. CLR در حقیقت این قابلیت را به شما میدهد تا چند برنامهی مدیریت شده را در قالب یک پروسه به اجرا درآورید. هر برنامهی مدیریت شده به طور پیش فرض بر روی یک appDomain اجرا میگردد و هر فایل EXE روی حافظهی مجازی مختص خودش اجرا میشود. هر چند پروسههایی از قبیل IIS و SQL Server که پروسههای CLR را پشتیبانی یا هاست میکنند میتوانند تصمیم بگیرند که آیا appDomainها را در یک پروسهی واحد اجرا کنند یا خیر که در مقالههای آتی آن را بررسی میکنیم.
کد ناامن یا غیر ایمن UnSafe Code
به طور پیش فرض سی شارپ کدهای ایمنی را تولید میکند، ولی این اجازه را میدهد که اگر برنامه نویس بخواهد کدهای ناامن بزند، قادر به انجام آن باشد. این کدهای ناامن دسترسی مستقیم به خانههای حافظه و دستکاری بایت هاست. این مورد قابلیت قدرتمندی است که به توسعه دهنده اجازه میدهد که با کدهای مدیریت نشده ارتباط برقرار کند یا یک الگوریتم با اهمیت زمانی بالا را جهت بهبود کارآیی، اجرا کند.
هر چند یک کد ناامن سبب ریسک بزرگی میشود و میتواند وضعیت بسیاری از ساختارهای ذخیره شده در حافظه را به هم بزند و امنیت برنامه را تا حد زیادی کاهش دهد. به همین دلیل سی شارپ نیاز دارد تا تمامی متدهایی که شامل کد unsafe هستند را با کلمه کلیدی unsafe علامت گذاری کند. همچنین کمپایلر سی شارپ نیاز دارد تا شما این کدها را با سوئیچ unsafe/ کامپایل کنید.
پی نوشت: سیستم به اسمبلی هایی که از روی ماشین یا از طریق شبکه به اشتراک گذاشته میشوند اعتماد کامل میکند که این اعتماد شامل کدهای ناامن هم میشود ولی به طور پیش فرض به اسمبلی هایی از طریق اینترنت اجرا میشوند اجازه اجرای کدهای ناامن را نمیدهد و اگر شامل کدهای ناامن شود یکی از خطاهایی که در بالا به آن اشاره کردیم را صادر میکنند. در صورتی که مدیر یا کاربر سیستم اجازه اجرای آن را بدهد تمامی مسئولیتهای این اجرا بر گردن اوست.
IL و حقوق حق تالیف آن
اگر علاقمند هستید این عیب را برطرف کنید، میتوانید از ابزارهای ثالث که به ابزارهای obfuscator (یک نمونه سورس باز ) معروف هستند استفاده کنید تا با کمی پیچیدگی در متادیتاها، این مشکل را تا حدی برطرف کنند. ولی این ابزارها خیلی کامل نیستند، چرا که نباید به کامپایل کردن کار لطمه بزنند. پس اگر باز خیلی نگران این مورد هستید میتوانید الگوریتمهای حساس و اساسی خود را در قالب unmanaged code ارائه کنید که در بالا اشاراتی به آن کردهایم.
برنامههای تحت وب به دلیل عدم دسترسی دیگران از امنیت کاملتری برخوردار هستند.
منابع مطالعاتی مرتبط
این موضوع اینقدر مهم است که تابحال چندین کتاب در مورد آن تالیف شده است:
Temporal Data & the Relational Model
Trees and Hierarchies in SQL
Developing Time-Oriented Database Applications in SQL
Temporal Data: Time and Relational Databases
Temporal Database Entries for the Springer Encyclopedia of Database Systems
Temporal Database Management
نکته مهمی که در این مآخذ وجود دارند، واژه کلیدی «Temporal data » است که میتواند در جستجوهای اینترنتی بسیار مفید واقع شود.
بررسی ابعاد زمان
فرض کنید کارمندی را استخدام کردهاید که ساعتی 2000 تومان از ابتدای فروردین ماه حقوق دریافت میکند. حقوق این شخص از ابتدای مهرماه قرار است به ساعتی 2400 تومان افزایش یابد. اگر مامور مالیات در بهمن ماه در مورد حقوق این شخص سؤال پرسید، ما چه پاسخی را باید ارائه دهیم؟ قطعا در بهمن ماه عنوان میکنیم که حقوقش ساعتی 2400 تومان است؛ اما واقعیت این است که این عدد از ابتدای استخدام او ثابت نبوده است و باید تاریخچه تغییرات آن، در نحوه محاسبه مالیات سال جاری لحاظ شود.
بنابراین در مدل سازی این سیستم به دو زمان نیاز داریم:
الف) actual time یا زمان رخ دادن واقعهای. برای مثال حقوق شخصی در تاریخ ابتدای مهر ماه تغییر کرده است. به این تاریخ در منابع مختلف Valid time نیز گفته میشود.
ب) record time یا زمان ثبت یک واقعه؛ مثلا زمان پرداخت حقوق. به آن Transaction time هم گفته شده است.
یک مثال:
record date actual date حقوق دریافتی 1392/01/01 1392/01/01 2000/روز 1392/02/01 1392/01/01 2000/روز ... 1392/07/01 1392/07/01 2400/روز ... 1392/17/01 1392/07/01 2400/روز
به ترکیب Valid Time و Transaction Time، اصطلاحا Bitemporal data میگویند.
مشکلات طراحیهای متداول اطلاعات وابسته به زمان
در طراحیهای متداول، عموما یک جدول کارمندان وجود دارد و یک جدول لیست حقوقهای پرداختی. رکوردهای لیست حقوقهای پرداختی نیز توسط یک کلید خارجی به اطلاعات هر کارمند متصل است؛ از این جهت که نمیخواهیم اطلاعاتی تکراری را در جدول لیست حقوقی ثبت کنیم و طراحی نرمال سازی شدهای مدنظر میباشد.
خوب؛ اول مهرماه حقوق شخصی تغییر کرده است. بنابراین کارمند بخش مالی اطلاعات شخص را به روز میکند. با این کار، کل سابقه حقوقهای پرداختی شخص نیز از بین خواهد رفت. چون وجود این کلید خارجی به معنای استفاده از آخرین اطلاعات به روز شده یک کارمند در جدول لیست حقوقی است. الان اگر از جدول لیست حقوقی گزارش بگیریم، کارمندان همواره از آخرین حقوق به روز شده خودشان استفاده خواهند کرد.
راه حلهای متفاوت مدل سازی اطلاعات وابسته به زمان
برای رفع این مشکل مهم، راه حلهای متفاوتی وجود دارند که در ادامه آنها را بررسی خواهیم کرد.
الف) نگهداری اطلاعات وابسته به زمان در جداول نهایی مرتبط
اگر حقوق پایه شخص در زمانهای مختلف تغییر میکند، بهتر است عدد نهایی این حقوق پرداختی نیز در یک فیلد مشخص، در همان جدول لیست حقوقی ثبت شود. این مورد به معنای داشتن «دادهای تکراری» نیست. از این جهت که دادهای تکراری است که اطلاعات آن در تمام زمانها، دارای یک مقدار و مفهوم باشد و اطلاعات حقوق یک شخص اینچنین نیست.
ب) نگهداری اطلاعات تغییرات حقوقی در یک جداول جداگانه
یک جدول ثانویه حقوق جاری کارمندان مرتبط با جدول اصلی کارمندان باید ایجاد شود. در این جدول هر رکورد آن باید دارای بازه زمانی (valid_start_time و valid_end_time) مشخصی باشد. مثلا از تاریخ X تا تاریخ Y، حقوق کارمند شماره 11 ، 2000 تومان در ساعت بوده است. از تاریخ H تا تاریخ Z اطلاعات دیگری ثبت خواهند شد. به این ترتیب با گزارشگیری از جدول لیست حقوقهای پرداخت شده، سابقه گذشته اشخاص محو نشده و هر رکورد بر اساس قرارگیری در یک بازه زمانی ثبت شده در جدول ثانویه حقوق جاری کارمندان تفسیر میشود.
در این حالت باید دقت داشت که بازههای زمانی تعریف شده، با هم تداخل نکنند و برنامه ثبت کننده اطلاعات باید این مساله را به ازای هر کارمند کنترل کند و یا با ثبت record_date، اجازه ثبت بازههای تکراری را نیز بدهد (توضیحات در قسمت بعد).
به این جدول، یک Temporal table نیز گفته میشود. نمونه دیگر آن، نگهداری قیمت یک کالا است از یک تاریخ تا تاریخی مشخص. به این ترتیب میتوان کوئری گرفت که بستنی مگنوم فروخته شده در ماه آبان سال قبل، بر مبنای قیمت آن زمان، دقیقا چقدر فروش کرده است و نه اینکه صرفا بر اساس آخرین قیمت روز این کالا گزارشگیری کنیم که در این حالت اطلاعات نهایی استخراج شده صحیح نیستند.
حال اگر به این طراحی در جدولی دیگر Transaction time یا زمان ثبت یک رکورد یا زمان ثبت یک فروش را هم اضافه کنیم، به جداول حاصل Bitemporal Tables میگویند.
مدیریت به روز رسانیها در جداول Temporal
در جداول Temporal، حذف فیزیکی اطلاعات مطلقا ممنوع است؛ چون سابقه سیستم را تخریب میکند. اگر اطلاعاتی در این جداول دیگر معتبر نیست باید تنها تاریخ پایان دوره آن به روز شوند یا یک رکورد جدید بر اساس بازهای جدید ثبت گردد.
همچنین به روز رسانیها در این جداول نیز معادل هستند با یک Insert جدید به همراه فیلد record_date و نه به روز رسانی واقعی یک رکورد قبلی (شبیه به سیستمهای حسابداری باید عمل کرد).
یک مثال:
فرض کنید حقوق کارمندی که مثال زده شد، در مهرماه به ساعتی 2400 تومان افزایش یافته است و حقوق نهایی نیز پرداخته شده است. بعد از یک ماه مشخص میشود که مدیر عامل سیستم گفته بوده است که ساعتی 2500 تومان و نه ساعتی 2400 تومان! (از این نوع مسایل در دنیای واقعی زیاد رخ میدهند!) خوب؛ اکنون چه باید کرد؟ آیا باید رفت و رکورد ساعتی 2400 تومان را به روز کرد؟ خیر. چون سابقه پرداخت واقعی صورت گرفته را تخریب میکند. به روز رسانی شما ابدا به این معنا نخواهد بود که دریافتی واقعی شخص در آن تاریخ خاص، ساعتی 2500 بوده است.
بنابراین در جداول Temporal، تنها «تغییرات افزودنی» مجاز هستند و این تغییرات همواره به عنوان آخرین رکورد جدول ثبت میشوند. به این ترتیب میتوان اصطلاحا «مابه التفاوت» حقوق پرداخت نشده را به شخص خاصی، محاسبه و پرداخت کرد (میدانیم در یک بازه زمانی خاص به او چقد حقوق دادهایم. همچنین میدانیم که این بازه در یک record_date دیگر لغو و با عددی دیگر، جایگزین شدهاست).
برای مطالعه بیشتر
Bitemporal Database Table Design - The Basics
Temporal Data Techniques in SQL
Database Design: A Point in Time Architecture
Temporal database
Temporal Patterns
راه حلی دیگر؛ استفاده از بانکهای اطلاعاتی NoSQL
بانکهای اطلاعاتی NoSQL برخلاف بانکهای اطلاعاتی رابطهای برای اعمال Read بهینه سازی میشوند و نه برای Write. در چند دهه قبل که بانکهای اطلاعاتی رابطهای پدیدار شدند، یک سخت دیسک 10 مگابایتی حدود 4000 دلار قیمت داشته است. به همین جهت مباحث نرمال سازی اطلاعات و ذخیره نکردن اطلاعات تکراری تا این حد در این نوع بانکهای اطلاعاتی مهم بوده است. اما در بانکهای اطلاعاتی NoSQL امروزی، اگر قرار است فیش حقوقی شخصی ثبت شود، میتوان کل اطلاعات جاری او را یکجا داخل یک سند ثبت کرد (از اطلاعات شخص در آن تاریخ تا اطلاعات تمام اجزای فیش حقوقی در قالب یک شیء تو در توی JSON). به همین جهت بسیار سریع هستند برای اعمال Read و گزارشگیری. همچنین این نوع سیستمها برای نگهداری نگارشهای مختلف یک سند بهینه سازی شدهاند و جزو ساختار توکار آنها است. بنابراین در این نوع سیستمها اگر نیاز است از یک سند خاصی گزارش بگیریم، دقیقا اطلاعات همان تاریخ خاص را دارا است و اگر اطلاعات پایه سیستم را به روز کنیم، از امروز به بعد در سندهای جدید ثبت خواهد شد. این نوع سیستمها رابطهای نیستند و بسیاری از مباحث نرمال سازی اطلاعات در آنها ضرورتی ندارد. قرار است یک فیش حقوقی شخص را نمایش دهیم؟ خوب، چرا تمام اطلاعات مورد نیاز او را در قالب یک شیء JSON تو در توی حاضر و آماده نداشته باشیم؟
معرفی کتابخانهی DNTPersianUtils.Core
تاریخ شمسی برای blogger !
واقعاً خسته نباشید
خیلی عالی و کاربردیه
ببخشید
فقط من یه مشکلی داشتم
برای من زمان ثبت پست ها هنوز به صورت میلادی هستش
یعنی اشکال کار کجاست ؟؟؟
البته این 2 نکته رو اضافه کنم :
1 - من از قالب فارسی سازی شده خود بلاگر فارسی استفاده می کنم
2 - من کد ها رو درست در بخش انتهایی کد های قالب و قبل از html آخر وارد کردم
آدرس من اینه :
http://persianblackrose.blogspot.com/
ممکنه مشکل از این 2 تا باشه ؟؟؟
.
.
.
و یک سوال هم از خدمتتون داشتم
آیا این امکان وجود داره که بشه قسمتی به ساید بار اضافه کرد که در اون بازدید کنندگاه امکان گذاشتن کامنت رو داشته باشند ؟
تقریباً یه چیز تو مایه های بخش کامنت 360 یاهو ؟؟؟
.
.
.
پیشاپیش از لطفتون ممنونم
همه کنترلها در Xamarin Forms دارای Property ای با نام FlowDirection هستند که مقادیر RightToLeft، LeftToRight و MatchParent را میپذیرد. MatchParent که مقدار پیش فرض است، به این معنی است که مثلا اگر در ContentPage، مقدار FlowDirection را RightToLeft دهیم، تمامی کنترلهای داخل آن صفحه RightToLeft باشند و بالعکس.
مجددا یک Resources file را با نام Strings.fa.resx و یکی دیگر را با نام Strings.en.resx اضافه کنید. برای درک بهتر وضعیت نهایی، پروژه XamApp را Clone/Pull کنید و آن را بررسی کنید.
در فایل Strings.resx یک ردیف جدید اضافه کنید که Name آن برابر با HelloWorld باشد و Value آن خالی است. این نام، در کد نویسی ما استفاده میشود و مثلا نباید شامل Space، علامت ! و ... باشد. در فایل Strings.fa.resx یک ردیف جدید اضافه کنید که Name آن برابر با همان HelloWorld باشد و Value آن برابر با سلام دنیا! در نهایت در فایل Strings.en.resx یک ردیف جدید را اضافه کنید که Name آن HelloWorld بوده و Value آن ! Hello world باشد.
سپس در فایل App.xaml.cs میتوانید قبل از اولین NavigationService.NavigateAsync، از کد زیر را استفاده کنید:
Strings.Culture = CultureInfo.CurrentUICulture = new CultureInfo("en"); // or new CultureInfo("fa");
برای نمایش پیام در View Model با استفاده از IUserDialogs نیز میتوانید به شکل زیر عمل کنید:
await UserDialogs.AlertAsync(message: Strings.HelloWorld);
در صورتیکه بخواهید پارامتری را در stringهای چند زبانه خود داشته باشید نیز میتوانید به شکل زیر عمل کنید:
Name | En Value | Fa Value |
ButtonTappedCount | Button tapped {0} times! | دکمه {0} کلیک شده است |
<Label Text="{Binding StepsCount, StringFormat={x:Static resx:Strings.ButtonTappedCount}}" />
namespace مربوطه یعنی resx هم در بالای فایل Xaml باید قرار داده شود، که میشود:
xmlns:resx="clr-namespace:XamApp.Resources"
await UserDialogs.AlertAsync(string.Format(Strings.ButtonTappedCount, StepsCount));
الف) طراحی گردش کاری یک سیستم ردیابی خطاها (Bug tracking system)
در ادامه رویدادها، حالات و انتقالات یک ماشین حالت ردیابی خطاها را مشاهده میکنید:
<statemachine xmlns="http://statelessdesigner.codeplex.com/Schema"> <settings> <itemname>BugTrackingStateMachine</itemname> <namespace>StatelessTests</namespace> <class>public</class> </settings> <triggers> <trigger>Assign</trigger> <trigger>Defer</trigger> <trigger>Resolve</trigger> <trigger>Close</trigger> </triggers> <states> <state start="yes">Open</state> <state>Assigned</state> <state>Deferred</state> <state>Resolved</state> <state>Closed</state> </states> <transitions> <transition trigger="Assign" from="Open" to="Assigned" /> <transition trigger="Assign" from="Assigned" to="Assigned" /> <transition trigger="Close" from="Assigned" to="Closed" /> <transition trigger="Defer" from="Assigned" to="Deferred" /> <transition trigger="Assign" from="Deferred" to="Assigned" /> <transition trigger="Resolve" from="Assigned" to="Resolved" /> </transitions> </statemachine>
توضیحات:
یک گزارش خطا حداقل پنج حالت آغاز (Open)، انتساب به شخص، جهت رفع مشکل (Assign)، به تاخیر افتادن/درحال بررسی (Deffered)، برطرف شده (Resolved) و خاتمه یافته/برطرف نخواهد شد (Closed) را میتواند داشته باشد.
برای حرکت (Transition) از هر حالت به حالتی دیگر نیاز به یک سری رویداد (Trigger) است که لیست آنها را در بالا مشاهده میکنید.
در ابتدا سیستم در حالت انتساب به شخص قرار میگیرد. سپس در همین حالت شخص میتواند یکی از سه حالت رفع شده، بستن موضوع و یا ارجاع به زمانی دیگر را انتخاب کند. حتی در حالت ارجاع به شخص، شخص میتواند مساله را به شخصی دیگر ارجاع دهد. یا در حالت به تاخیر افتادن حل مساله، میتوان مشکل را به شخصی دیگر انتساب داد.
ب) طراحی گردش کاری درخواست ارتقاء در یک شرکت
مراحل درخواست ارتقاء شغلی را در یک سازمان فرضی، در ذیل مشاهده میکنید:
<statemachine xmlns="http://statelessdesigner.codeplex.com/Schema"> <settings> <itemname>RequestPromotionStateMachine</itemname> <namespace>StatelessTests</namespace> <class>public</class> </settings> <triggers> <trigger>Complete</trigger> <trigger>RequestInfo</trigger> <trigger>Deny</trigger> <trigger>Approve</trigger> <trigger>ManagerJustify</trigger> </triggers> <states> <state start="yes">RequestPromotionForm</state> <state>ManagerReview</state> <state>PromotionDenied</state> <state>VicePresidentApprove</state> <state>Promoted</state> </states> <transitions> <transition trigger="Complete" from="RequestPromotionForm" to="ManagerReview" /> <transition trigger="RequestInfo" from="ManagerReview" to="RequestPromotionForm" /> <transition trigger="Deny" from="ManagerReview" to="PromotionDenied" /> <transition trigger="Approve" from="ManagerReview" to="VicePresidentApprove" /> <transition trigger="ManagerJustify" from="VicePresidentApprove" to="ManagerReview" /> <transition trigger="Deny" from="VicePresidentApprove" to="PromotionDenied" /> <transition trigger="Approve" from="VicePresidentApprove" to="Promoted" /> </transitions> </statemachine>
توضیحات:
کارمند فرم درخواست ارتقاء را تکمیل میکند. این فرم به مسئول او ارسال میشود. مسئول میتواند درخواست را یک ضرب رد کند؛ یا تائید کند که سپس برای مدیرعامل شرکت ارسال میشود و یا مجددا به شخص برای تکمیل نواقص فرم ارجاع دهد. مدیرعامل شرکت میتواند درخواست را تائید کند که در اینجا کار خاتمه مییابد و شخص ارتقاء خواهد یافت. یا میتواند درخواست را رد کند و یا برای بررسی بیشتر مجددا به مسئول شخص ارجاع دهد.
تمرین! توضیحات زیر را تبدیل به یک State machine کنید!
چند سال قبل به ادارهی بیمه تامین اجتماعی منطقه مراجعه کردم. جهت دریافت ریز سوابق و انتقال آنها به این مرکز ابتدا یک برگه دریافت شد. پر شد، بعد به صورت دستی (توسط بنده) به یک نفر دیگر ارجاع شد تا امضاء کند. سپس به صورت دستی به مسئول قسمت ارجاع شد تا امضاء کند. مجددا به صورت دستی به مدیر کل مجموعه ارجاع شد تا امضاء کند. سپس به صورت دستی به دبیرخانه برای پیگیری ارجاع شد. قرار است ظرف یک ماه تا 45 روز این سوابق از یک واحد دیگر به این واحد منتقل شوند!
بعد از 45 روز:
مراجعه به دبیرخانه: دریافت شماره پرونده رسیده.
گفته شد که به قسمت دریافت شماره مراجعه کنید. مراجعه شد، گفتند برو پروندهات را بگیر بیار. رفتم زیر زمین، گفت که ما اینطوری پرونده نمیدیم! برو فرمش رو هم پر کن بیار. مراجعه شد به کارمند مربوطه، ایشان پس از مشورت با سایر همکاران به این نتیجه رسیدند که در این مرحله نیازی به مراجعه به زیر زمین نبوده! و باید به قسمت ثبت نام مجدد مراجعه کنید! چشم!
اینجا هم مجددا فرم پر شد،ارجاع داده شده به معاون قسمت، امضاء کرد گفت برو دبیرخانه شماره بگیر. شماره گرفته شده بعد مجددا به همان قسمت ثبت نام مراجعه کردم، گفتند برو پروندهات را از زیر زمین بگیر بیار! بعد از آوردن پرونده، ارجاع شد به صورت دستی به یک قسمت دیگر که سوابق وارد سیستم شود (هنوز نشده بود!). بعد از ثبت (نیم ساعت یا بیشتر ...)، مجددا به همان قسمت ثبت نام مراجعه کردم، گفت حالا برو یک شماره بگیر بیار. شماره گرفته شد از قسمتی دیگر و مراجعه مجدد به قسمت ثبت نام، یک نامه دیگر تهیه کرد، به سه نفر دیگر + دبیرخانه برای امضاء و شماره گرفتن ارجاع داده شد. اینجا تمام شد. بعد این موارد ارجاع شد به قسمت دیگری از شهر برای دریافت قبض پرداخت بیمه.
مطلب مرتبط
بنده نیاز به یک تابع جاوا اسکریپتی ترجیحاً با Jquery دارم که بتونه تاریخ شمسی مثلا 1392/01/11 را بگیره و بگه که تا امروز چند سال و چند ماه گذشته. یعنی یک جور محاسبه سایقه کار برحسب سال و ماه را میخواهم روز و ساعتش مهم نیست. با تشکر