نظرات مطالب
اثر وجود سشن بر پردازش موازی در ASP.NET
با سلام و ممنون از سایت خیلی خوب شما
سوالم اینه که آیا اگر بخوایم در یک برنامه asp.net از سشن استفاده کنیم و ان را در کل سایت readOnly اعلام کنیم تا بتونیم پردازش موازی داشته باشیم راهی هست که در وب کانفیگ این تنظیم را انجام دهیم ؟یا حتی هنگام تعریف اولیه سشن پس از مقدار دهی آنرا readOnly کنیم؟
مسیرراه‌ها
+AngularJS 2.0
شروع به کار با AngularJS 2.0 و TypeScript
فرم‌های مبتنی بر قالب‌ها در Angular
Angular Animation
آپلود فایل
Angular Material 6x
  • ابزارهای توسعه
بازخوردهای پروژه‌ها
تولید پویای رشته Sql و ارسال پارامتر برای عملگر Like
با سلام
با استفاده از کد زیر که sql آن به صورت پویا تولید شده است :
.MainTableDataSource(dataSource =>
{
dataSource.GenericDataReader(
providerName : "System.Data.SqlServerCe.4.0",
connectionString : @"Data Source=data.sdf;password=******",
sql : "Select [VoucherRows].[Description] AS [Description],[VoucherRows].[Creditor] AS [Creditor],[VoucherRows].[Debtor] AS [Debtor],[Vouchers].[Number] AS [Number],
[Vouchers].[SubmitDate] AS [SubmitDate] 
From [VoucherRows] AS [VoucherRows],[Vouchers] AS [Vouchers]  
Where  [Vouchers].[Id]  =  [VoucherRows].[VoucherId]  And  [Vouchers].[Number]  >=  @p1  And  [Vouchers].[Number]  <=  @p2  And 
 [Vouchers].[Description]  Like  '%' + @p3 + '%'    Order By [Vouchers].[SubmitDate] DESC,[Vouchers].[Number] ASC",
parametersValues : new object[] {50,100,"مقدار"}
);
})
میخواهم گزارشی را تهیه کنم ولی برای پارامتر سوم که از عملگر Like استفاده میکند، خطای Input string was not in correct ... را می‌گیرم. 
مشکل از کجاست؟رشته Sql تولید شده؟ یا نحوه ارسال پارامتر؟
مطالب
نکات مربوط به گرد کردن اعداد در دات نت
متد System.Math.Round که برای گرد کردن اعداد اعشاری به کار می‌رود، دارای 8 نوعoverload می‌باشد، که عدم توجه به موارد مربوط به آن باعث بروز خطا در محاسبات خواهد شد. به طور مثال پیش بینی شما از گرد کردن عدد 3.45 عدد 3.5 است ولی گاهی 3.5 و گاهی 3.4 گرد خواهد شد.
پس بهتر است تا با نکات زیر به شکل دقیق آشنا باشید.
Round(Decimal)
Rounds a decimal value to the nearest integral value.
(ورودی: دسیمال) به نزدیکترین عدد کامل گرد میکند،  4.3 به 4 و 4.8 به 5 گرد می‌شود. ولی در صورتیکه فاصله تا عدد کامل قبل و بعد برابر باشد به نزدیکترین عدد زوج گرد میکند، 4.5 به 4 گرد می‌شود چرا که 5  عددی فرد است.
نکته 1: خروجی تابع از نوع دسیمال است نه عدد کامل.
نکته 2: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest یا banker's roundingنیز گفته می‌شود. نتیجه برای به حداقل رساندن خطا است. نتیجه این حالت از متد با نتیجه Round(Decimal, MidpointRounding.ToEven) برابر است.



Round(Double)
Rounds a double-precision floating-point value to the nearest integral value.
(ورودی: double) به نزدیکترین عدد کامل گرد میکند،  4.3 به 4 و 4.8 به 5 گرد می‌شود. ولی در صورتیکه فاصله تا عدد کامل قبل و بعد برابر باشد به نزدیکترین عدد زوج گرد میکند، 4.5 به 4 گرد می‌شود چرا که 5  عددی فرد است.
نکته 1: خروجی تابع از نوع double است نه عدد کامل.
نکته 2: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest یا banker's roundingنیز گفته می‌شود. نتیجه برای به حداقل رساندن خطا است. نتیجه این حالت از متد با نتیجه Round(Double, MidpointRounding.ToEven) برابر است.
نکته 3: گاهی اوقات به دلیل از دست دادن دقت، ناشی از استفاده از مقادیر دسیمال به جای ممیز شناور و یا انجام محاسبات ریاضی بر روی بخش ممیزی خواهد بود. مثلا: زمانی که 11.5 ماحصل جمع 1. و  11.4 باشد به جای 12 که عدد زوج است به 11 گرد می‌شود!



Round(Decimal, Int32)
Rounds a decimal value to a specified number of fractional digits.
عدد دسیمال ورودی خود را به صورتی گرد میکند که:
1: تعداد ارقام اعشاری بعد از ممیز به اندازه پارامتر دوم این نوع ورودی متد Round باشد (بین صفر تا 28).
2: استفاده از این متد همانند فراخوانی آن با ورودی‌های Round(Decimal, Int32, MidpointRounding.ToEven) است. یعنی اینکه اگر رقم آخر بعد از ممیز دقیقا وسط مقدار قبل و بعد باشد (3.75) در صورتی که رقم ماقبل آخر فرد باشد رو به بالا گرد خواهد شد (مثال: 3.75 به 3.8 گرد خواهد شد) و اگر رقم ما قبل آخر زوج باشد تغییر نخواهد کرد (مثال: 3.45 به 3.4 گرد خواهد شد)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest یا banker's roundingنیز گفته می‌شود.

Math.Round(3.44, 1); //Returns 3.4.
Math.Round(3.45, 1); //Returns 3.4.
Math.Round(3.46, 1); //Returns 3.5.
Math.Round(4.34, 1); // Returns 4.3
Math.Round(4.35, 1); // Returns 4.4
Math.Round(4.36, 1); // Returns 4.4


Round(Decimal, MidpointRounding)
Rounds a decimal value to the nearest integer. 
A parameter specifies how to round the value if it is midway between two other numbers.
عدد دسیمال ورودی خود را به نزدیک‌ترین عدد integer گرد میکند، پارامتر اول عدد گرد نشده و پارامتر دوم مشخص میکند که در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و .... باشد) چگونه این گرد کردن صورت گیرد.

حالت‌های MidpointRounding:
MidpointRounding.ToEven: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، و رقم ماقبل آخر اعشار فرد باشد، رو به بالا گرد خواهد شد و در صورتیکه رقم ماقبل آخر اعشار زوج باشد، بدون تغییر باقی خواهد ماند (3.75 به 3.8 و 3.65 به 3.6 گرد می‌شود.)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest  یا  banker's roundingنیز گفته می‌شود.
MidpointRounding.AwayFromZero: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، در این حالت همواره عمل گرد کردن رو به رقم بعدی خواهد بود. این رایج‌ترین حالت گرد کردن است که به symmetric arithmetic rounding شناخته می‌شود.



Round(Double, Int32)
Rounds a double-precision floating-point value to a specified number of fractional digits.
یک عدد اعشاری از نوع Double (با دقت مضاعف) که تعداد مشخصی رقم بعد از ممیز دارد (به طور مثال 10 رقم اعشار)، به تعداد رقم اعشاری که کاربر به عنوان پارامتر دوم ذکر میکند(بین صفر تا 15 رقم)، (مثلا  4 رقم)گرد میکند. اگر تعداد رقم اعشار بیش از 15 تعیین شود، عدد 15 جایگزین خواهد شد. استفاده از این متد همانند فراخوانی آن به صورت Round(Double, Int32, MidpointRounding.ToEven) می‌باشد. یعنی اینکه اگر رقم آخر بعد از ممیز دقیقا وسط مقدار قبل و بعد باشد (3.75) در صورتی که رقم ماقبل آخر فرد باشد رو به بالا گرد خواهد شد (مثال: 3.75 به 3.8 گرد خواهد شد) و اگر رقم ما قبل آخر زوج باشد تغییر نخواهد کرد (مثال: 3.45 به 3.4 گرد خواهد شد)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest یا banker's roundingنیز گفته می‌شود.



Round(Double, MidpointRounding)
Rounds a double-precision floating-point value to the nearest integer.
A parameter specifies how to round the value if it is midway between two other numbers.
(عدد اعشاری ورودی: Double) عدد با دقت مضاعف ورودی خود را به نزدیک‌ترین عدد integer گرد میکند، پارامتر اول عدد گرد نشده و پارامتر دوم مشخص میکند در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و .... باشد) چگونه این گرد کردن صورت گیرد.

حالت‌های MidpointRounding:
MidpointRounding.ToEven: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، و رقم ماقبل آخر اعشار فرد باشد، رو به بالا گرد خواهد شد و در صورتیکه رقم ماقبل آخر اعشار زوج باشد، بدون تغییر باقی خواهد ماند (3.75 به 3.8 و 3.65 به 3.6 گرد می‌شود.)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest  یا  banker's roundingنیز گفته می‌شود.
MidpointRounding.AwayFromZero: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، در این حالت همواره عمل گرد کردن رو به رقم بعدی خواهد بود. این رایج‌ترین حالت گرد کردن است که به symmetric arithmetic rounding شناخته می‌شود.



Round(Decimal, Int32, MidpointRounding)
Rounds a decimal value to a specified number of fractional digits. 
A parameter specifies how to round the value if it is midway between two other numbers.
عدد دسیمال ورودی خود را با تعداد اعشار اعلام شده و به صورتی گرد میکند که:
1: تعداد ارقام اعشاری بعد از ممیز به اندازه پارامتر دوم این نوع ورودی متد Round باشد (بین صفر تا 28).

حالت‌های MidpointRounding:
MidpointRounding.ToEven: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، و رقم ماقبل آخر اعشار فرد باشد، رو به بالا گرد خواهد شد و در صورتیکه رقم ماقبل آخر اعشار زوج باشد، بدون تغییر باقی خواهد ماند (3.75 به 3.8 و 3.65 به 3.6 گرد می‌شود.)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest  یا  banker's roundingنیز گفته می‌شود.
MidpointRounding.AwayFromZero: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، در این حالت همواره عمل گرد کردن به سمت رقم بعدی خواهد بود. این رایج‌ترین حالت گرد کردن است که به symmetric arithmetic rounding شناخته می‌شود. 
3.4 = Math.Round( 3.45, 1, MidpointRounding.ToEven)
3.5 = Math.Round( 3.45, 1, MidpointRounding.AwayFromZero)

-3.4 = Math.Round(-3.45, 1, MidpointRounding.ToEven)
-3.5 = Math.Round(-3.45, 1, MidpointRounding.AwayFromZero)


Round(Double, Int32, MidpointRounding)
Rounds a double-precision floating-point value to the specified number of fractional digits. 
A parameter specifies how to round the value if it is midway between two other numbers.
(عدد اعشاری ورودی: Double) عدد با دقت مضاعف ورودی خود را به نزدیک‌ترین عدد با تعداد رقم اعشار مشخص شده گرد میکند، پارامتر اول متد، عدد گرد نشده و پارامتر دوم تعداد رقم اعشار (بین صفر تا 15 رقم) تعیین شده گرد می‌کند. اگر تعداد رقم اعشار بیش از 15 تعیین شود، عدد 15 جایگزین خواهد شد. و پارامتر سوم مشخص میکند که در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و .... باشد) چگونه این گرد کردن صورت گیرد.

حالت‌های MidpointRounding:
MidpointRounding.ToEven: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، و رقم ماقبل آخر اعشار فرد باشد، رو به بالا گرد خواهد شد و در صورتیکه رقم ماقبل آخر اعشار زوج باشد، بدون تغییر باقی خواهد ماند (3.75 به 3.8 و 3.65 به 3.6 گرد می‌شود.)
نکته 1: این تابع بر طبق استاندارد IEEE Standard 754, section 4 پیاده سازی شده است که در اصطلاح rounding to nearest  یا  banker's roundingنیز گفته می‌شود.

MidpointRounding.AwayFromZero: در صورتیکه مقدار اعشاری عددی میانی (5و 50و 500و 5000و ....) باشد، در این حالت همواره عمل گرد کردن به سمت رقم بعدی خواهد بود. این رایج‌ترین حالت گرد کردن است که به symmetric arithmetic rounding شناخته می‌شود.

 // The example displays the following output:
// 2.125 --> 2.13
// 2.135 --> 2.13
// 2.145 --> 2.15
// 3.125 --> 3.13
// 3.135 --> 3.14
// 3.145 --> 3.15
This code example produces the following results:

 3.4 = Math.Round( 3.45, 1)
-3.4 = Math.Round(-3.45, 1)

 3.4 = Math.Round( 3.45, 1, MidpointRounding.ToEven)
 3.5 = Math.Round( 3.45, 1, MidpointRounding.AwayFromZero)

-3.4 = Math.Round(-3.45, 1, MidpointRounding.ToEven)
-3.5 = Math.Round(-3.45, 1, MidpointRounding.AwayFromZero)

مطالب
توسعه سیستم مدیریت محتوای DNTCms - قسمت ششم
در این قسمت مدل‌های باقی مانده‌ی از بخش‌هایی را که در مقاله اول مطرح شدند، به اتمام می‌رسانیم. همچنین با بازخوردهایی که در مقالات قبل گرفتیم، در این قسمت تغییرات ایجاد شده‌ی در مدل‌های قسمت‌های قبل را نیز مطرح خواهیم کرد.

مدل‌های AuditLog (اصلاحیه)و ActivityLog

باید توجه داشت که اگر سیستم AuditLog، جزئیات بیشتری را در بر بگیرد، می‌توان از آن به عنوان History هم یاد کرد. در قسمت چهارم برای پست‌های انجمن یک جدول جدا هم به منظور ذخیره سازی تاریخچه‌ی تغییرات، در نظر گرفتیم. فرض کنید که یک سری از جداول دیگر هم نیازمند این امکان باشند! راه حل چیست؟
  1. استفاده از جداول جدا برای هر کدام از جداول به صورتیکه یک ارتباط یک به چند مابین آنها برقرار است. از این جداول تحت عنوان HistoryTable یاد می‌شود.
  2. استفاده از یک جدول برای نگهداری تاریخچه‌ی تغییرات جداولی که نیازمند این امکان هستند. 
در زیر پیاده سازی از روش دوم رو مشاهده میکنید.
  /// <summary>
    /// Represent The Operation's log
    /// </summary>
    public class AuditLog
    {
        #region Ctor
        /// <summary>
        /// Create One Instance Of <see cref="AuditLog"/>
        /// </summary>
        public AuditLog()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            OperatedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets identifier of AuditLog
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets Type of  Modification(create,softDelet,Delete,update)
        /// </summary>
        public virtual AuditAction Action { get; set; }
        /// <summary>
        /// sets or gets description of Log
        /// </summary>
        public virtual string Description { get; set; }
        /// <summary>
        /// sets or gets when log is operated
        /// </summary>
        public virtual DateTime OperatedOn { get; set; }
        /// <summary>
        /// sets or gets Type Of Entity 
        /// </summary>
        public virtual string Entity { get; set; }
        /// <summary>
        /// gets or sets  Old value of  Properties before modification
        /// </summary>
        public virtual string XmlOldValue { get; set; }
        /// <summary>
        /// gets or sets XML Base OldValue of Properties (NotMapped)
        /// </summary>
        public virtual XElement XmlOldValueWrapper
        {
            get { return XElement.Parse(XmlOldValue); }
            set { XmlOldValue = value.ToString(); }
        }
        /// <summary>
        /// gets or sets new value of  Properties after modification
        /// </summary>
        public virtual string XmlNewValue { get; set; }
        /// <summary>
        /// gets or sets XML Base NewValue of Properties (NotMapped)
        /// </summary>
        public virtual XElement XmlNewValueWrapper
        {
            get { return XElement.Parse(XmlNewValue); }
            set { XmlNewValue = value.ToString(); }
        }
        /// <summary>
        /// gets or sets Identifier Of Entity
        /// </summary>
        public virtual string EntityId { get; set; }
        /// <summary>
        /// gets or sets user agent information
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets user's ip address
        /// </summary>
        public virtual string OperantIp { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets log's creator
        /// </summary>
        public virtual User Operant { get; set; }
        /// <summary>
        /// sets or gets identifier of log's creator
        /// </summary>
        public virtual long OperantId { get; set; }
        #endregion
    }
  public enum AuditAction
    {
        Create,
        Update,
        Delete,
        SoftDelete,
    }

خصوصیاتی که نیاز به توضیح خواهند داشت:
  • Action : از نوع AdutiAction است و برای مشخص کردن نوع عملیاتی که انجام شده است، می‌باشد.
  • Description : اگر نیاز باشد توضیحاتی اضافی ثبت شوند، از این خصوصیت استفاده می‌شود.
  • Entity : مشخص کننده‌ی نام مدل خواهد بود. شاید بهتر بود از یک Enum استفاده می‌شد. ولی این سیستم به احتمال زیاد قرار است افزونه پذیر باشد و استفاده از Enum، یعنی محدودیت و این امکان وجود نخواهد داشت که سایر افزونه‌ها بتوانند از مدل بالا استفاده کنند. برا ی مثال BlogPost , NewsItem , ForumPost , ...
  • EntitytId : آی دی رکوردی است که تاریخچه‌ی آن ثبت شده است. از آنجائیکه بعضی از موجودیت‌ها دارای آی دی از نوع long و برخی دیگر Guid ، لذا ذخیره‌ی رشته‌ای آن مفید خواهد بود.
  • XmlOldValue : در برگیرنده‌ی مقدار (قبل از اعمال تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشته‌ای ذخیره شوند.
  • XmlNewValue : در برگیرنده‌ی مقدار (بعد از تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشته‌ای ذخیره شوند.
  • Operant, OperantId: برای برقراری ارتباط یک به چند مابین مدل کاربر و مدل بالا در نظر گرفته شده‌اند که به عنوان انجام دهنده‌ی این تغییرات بوده است.
با استفاده از مدل بالا می‌توان متوجه شد که کاربر x چه خصوصیاتی از  موجودیت y را تغییر داده است و این خصوصیات قبل از تغییر چه مقدارهایی داشته‌اند.
  /// <summary>
    /// Represents Activity Log record
    /// </summary>
    public class ActivityLog
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="ActivityLog"/>
        /// </summary>
        public ActivityLog()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            OperatedOn=DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier 
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets the comment of this activity
        /// </summary>
        public virtual string Comment { get; set; }
        /// <summary>
        /// gets or sets the date that this activity was done
        /// </summary>
        public virtual DateTime OperatedOn { get; set; }
        /// <summary>
        /// gets or sets the page url . 
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or sets the title of page if Url is Not null
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets user agent information
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets user's ip address
        /// </summary>
        public virtual string OperantIp { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets the type of this activity
        /// </summary>
        public virtual ActivityLogType Type{ get; set; }
        /// <summary>
        /// gets or sets the  type's id of this activity
        /// </summary>
        public virtual Guid TypeId { get; set; }
        /// <summary>
        /// gets or sets User that done this activity
        /// </summary>
        public virtual User Operant { get; set; }
        /// <summary>
        /// gets or sets Id of User that done this activity
        /// </summary>
        public virtual long OperantId { get; set; }
        #endregion
    }

   /// <summary>
    /// Represents Activity Log Type Record
    /// </summary>
    public class ActivityLogType
    {
        #region Ctor
        /// <summary>
        /// Create one Instance of <see cref="ActivityLogType"/>
        /// </summary>
        public ActivityLogType()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier 
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets the system name
        /// </summary>
        public virtual string Name{ get; set; }
        /// <summary>
        /// gets or sets the display name
        /// </summary>
        public virtual string DisplayName { get; set; }
        /// <summary>
        /// gets or sets the description 
        /// </summary>
        public virtual string Description { get; set; }
        /// <summary>
        /// indicate this log type is enable for logging
        /// </summary>
        public virtual bool IsEnabled { get; set; }
        #endregion
    }
مدل‌های بالا هم برای ثبت لاگ فعالیت‌های کاربران در سیستم در نظر گرفته شده است . برای مثال اگر بخش آخرین تغییرات سایت جاری را هم مشاهده کنید، یک همچین سیستمی را هم دارد. این لاگ‌ها برای ردیابی عملکرد کاربران در سیستم مفید خواهد بود.
  • Comment : توضیحات کوتاهی از اکشنی که کاربر انجام داده است.
  • Url : آدرس صفحه‌ای که این عملیات در آنجا انجام شده است. این خصوصیت نال‌پذیر می‌باشد.
  • Title : عنوان صفحه‌ای که این عملیات در آنجا انجام شده است؛ اگر Url نال نباشد.
  • Operant , OperantId : برای برقراری ارتباط یک به چند بین کاربر و مدل فعالیت‌ها در نظر گرفته شده‌اند.
  • Type : از نوع ActivityLogType پیاده سازی شده در بالا می‌باشد. با استفاده از مدل ActivityLogType می‌توان مثلا لاگ فعالیت مربوط به بخش اخبار را غیر فعال کند یا بالعکس و از این موارد.
خصوصیات مدل ActivityLogType :
  • Name : نام سیستمی آن است. برای مثال : Login ، NewsComment ، NewsItem و ...
  • IsEnabled : نشان دهنده‌ی این است که این نوع لاگ فعال است یا خیر.

کلاس پایه تمام مدل‌ها (اصلاحیه)

/// <summary>
    /// Represents the  entity
    /// </summary>
    /// <typeparam name="TForeignKey">type of user's Id that can be long or long? </typeparam>
    public abstract class Entity<TForeignKey>
    {
        #region Properties
        /// <summary>
        /// gets or sets date that this entity was created
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets Date that this entity was updated
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets IP Address of Creator
        /// </summary>
        public virtual string CreatorIp { get; set; }
        /// <summary>
        /// gets or set IP Address of Modifier
        /// </summary>
        public virtual string ModifierIp { get; set; }
        /// <summary>
        /// indicate this entity is Locked for Modify
        /// </summary>
        public virtual bool ModifyLocked { get; set; }
        /// <summary>
        /// indicate this entity is deleted softly
        /// </summary>
        public virtual bool IsDeleted { get; set; }
        /// <summary>
        /// gets or sets information of user agent of modifier
        /// </summary>
        public virtual string ModifierAgent { get; set; }
        /// <summary>
        /// gets or sets information of user agent of Creator
        /// </summary>
        public virtual string CreatorAgent { get; set; }
        /// <summary>
        /// gets or sets date that this entity repoted last time
        /// </summary>
        public virtual DateTime? ReportedOn { get; set; }
        /// <summary>
        /// gets or sets counter for Content's report
        /// </summary>
        public virtual int ReportsCount { get; set; }
        /// <summary>
        /// gets or sets count of Modification Default is 1
        /// </summary>
        public virtual int Version { get; set; }
        /// <summary>
        /// gets or sets action (create,update,softDelete) 
        /// </summary>
        public virtual AuditAction Action { get; set; }
        /// <summary>
        /// gets or sets TimeStamp for prevent concurrency Problems
        /// </summary>
        public virtual byte[] RowVersion { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets ro sets User that Modify this entity
        /// </summary>
        public virtual User ModifiedBy { get; set; }
        /// <summary>
        /// gets ro sets Id of  User that modify this entity
        /// </summary>
        public virtual TForeignKey ModifiedById { get; set; }
        /// <summary>
        /// gets ro sets User that Create this entity
        /// </summary>
        public virtual User CreatedBy { get; set; }
        /// <summary>
        /// gets ro sets User that Create this entity
        /// </summary>
        public virtual TForeignKey CreatedById { get; set; }
        #endregion
    }

  /// <summary>
    /// Represents the base Entity
    /// </summary>
    /// <typeparam name="TKey">type of Id</typeparam>
    /// <typeparam name="TForeignKey">type of User's Id that can be long or long?</typeparam>
    public abstract class BaseEntity<TKey,TForeignKey> : Entity<TForeignKey>
    {
        #region Properties
        /// <summary>
        /// gets or sets Identifier of this Entity
        /// </summary>
        public virtual TKey Id { get; set; }
        #endregion
    }
از دو کلاس معرفی شده‌ی در بالا برای کپسوله کردن یکسری خصوصیات تکراری استفاده شده است. البته با بهبودهایی نسبت به مقاله‌ی قبل که با مشاهده‌ی خصوصیات آنها قابل فهم خواهد بود. در برخی از مدل‌ها، برای مثال نظرات وبلاگ امکان ارسال نظر برای افراد Anonymous هم وجود داشت؛ لذا CreatedById امکان نال بودن را هم داشت. به همین دلیل برای کاهش کدها، کلاس‌های بالا را به صورت جنریک تعریف کردیم. فایل EDMX نهایی که در انتهای مقاله ضمیمه شده، برای درک تغییرات اعمال شده مفید خواهد.

مدل سیستم آگاه سازی

/// <summary>
    /// Represents the Notification Record
    /// </summary>
    public class Notification
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="Notification"/>
        /// </summary>
        public Notification()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            ReceivedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// indicate that this notification is read by owner
        /// </summary>
        public virtual bool IsRead { get; set; }
        /// <summary>
        /// gets or sets notification's text body
        /// </summary>
        public virtual string Message { get; set; }
        /// <summary>
        /// gets or sets page url that this notification is related with it
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or sets date that this Notification Received
        /// </summary>
        public virtual DateTime ReceivedOn { get; set; }
        /// <summary>
        /// gets or sets the type of notification
        /// </summary>
        public virtual NotificationType Type { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets the id of user that is owner of this notification
        /// </summary>
        public virtual long OwnerId { get; set; }
        /// <summary>
        /// gets or sets the user that is owner of this notification
        /// </summary>
        public virtual User Owner { get; set; }
        #endregion
    }

    public enum  NotificationType
    {
        NewConversation,
        NewConversationReply,
        ...
    }
در این سیستم برای اطلاع رسانی کاربر، علاوه بر ارسال ایمیل، بحث اطلاع رسانی RealTime را هم خواهیم داشت. اطلاع رسانی‌هایی که توسط کاربر خوانده نشده باشند، در جدول حاصل از مدل Notification ذخیره خواهند شد. خصوصیاتی که نیاز به توضیح دارند:
  • IsRead : مشخص کننده‌ی این است که یک اطلاع رسانی خوانده شده است یا خیر. در آخر هر روز اطلاع رسانی‌هایی که دارای خصوصیت IsRead با مقدار true هستند، حذف خواهند شد.
  • Url : برای مواردی که لازم است کاربر با کلیک بر روی آن به صفحه‌ی خاصی هدایت شود.
  • OwnerId, Owner : برای برقراری ارتباط یک به چند بین کاربر و مدل Notification در نظر گرفته شده‌اند.
  • Type : از نوع NotificationType و مشخص کننده‌ی نوع اطلاع رسانی می‌باشد .

مدل Observation

 public class Observation
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="Observation"/>
        /// </summary>
        public Observation()
        {
            LastObservedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets datetime of last visit 
        /// </summary>
        public virtual DateTime LastObservedOn { get; set; }
        /// <summary>
        /// gets or sets Id Of section That user is  observing the entity
        /// </summary>
        public virtual string SectionId { get; set; }
        /// <summary>
        /// gets or sets  section That user is  observing in it
        /// </summary>
        public virtual string Section { get; set; }
        #endregion

        #region NavigationProperites
        /// <summary>
        /// gets or sets user that observed the entity
        /// </summary>
        public virtual User Observer { get; set; }
        /// <summary>
        /// gets or sets identifier of user that observed the entity
        /// </summary>
        public virtual long ObserverId { get; set; }
        #endregion
    }
این مدل برای ایجاد امکانی به منظور واکشی لیست افردای که در حال مشاهده‌ی یک بخش خاص هستند، مفید است. فرض کنید در یک انجمن قصد دارید لیست افردای را که در حال مشاهده‌ی آن هستند، در پایین صفحه نمایش دهید. برای تاپیک‌ها هم همین امکان لازم است. لذا مدل بالا مختص مدل خاصی نیست و برای هر بخشی می‌توان از آن استفاده کرد. 
فرض کنیم کاربری قصد هدایت به یک تاپیک را دارد. لذا هنگام هدایت شدن لازم است رکوردی در جدول حاصل از مدل بالا ثبت شود که کاربر x در بخش Topic، در تاریخ d، تاپیک به شماره‌ی y را مشاهده کرد. در صفحه‌ی مشاهده‌ی تاپیک می‌توان لیست افرادی را که قبل از مدت زمان مشخصی تاپیک را مشاهده کرده اند، نمایش داد. این رکورد‌ها را هم با تعریف یک Task می‌توان در بازه‌های زمانی مشخصی حذف کرد.

مدل صفحات داینامیک

/// <summary>
    /// represents one custom page
    /// </summary>
    public class Page : BaseEntity<long, long>
    {
        #region Properties
        /// <summary>
        /// gets or sets the blog pot body
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets the content title
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets value  indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets meta title for seo
        /// </summary>
        public virtual string MetaTitle { get; set; }
        /// <summary>
        /// gets or sets meta keywords for seo
        /// </summary>
        public virtual string MetaKeywords { get; set; }
        /// <summary>
        /// gets or sets meta description of the content
        /// </summary>
        public virtual string MetaDescription { get; set; }
        /// <summary>
        /// gets or sets 
        /// </summary>
        public virtual string FocusKeyword { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content use CanonicalUrl
        /// </summary>
        public virtual bool UseCanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets CanonicalUrl That the Post Point to it
        /// </summary>
        public virtual string CanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Follow for Seo
        /// </summary>
        public virtual bool UseNoFollow { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Index for Seo
        /// </summary>
        public virtual bool UseNoIndex { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content in sitemap
        /// </summary>
        public virtual bool IsInSitemap { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets section's type that this page show on
        /// </summary>
        public virtual ShowPageSection Section { get; set; }
        /// <summary>
        /// indicate this page has not any body
        /// </summary>
        public virtual bool IsCategory { get; set; }
        /// <summary>
        /// gets or sets order for display forum
        /// </summary>
        public virtual int DisplayOrder { get; set; }

        #endregion

        #region NavigationProeprties
        /// <summary>
        /// gets or sets Parent of this page
        /// </summary>
        public virtual Page Parent { get; set; }
        /// <summary>
        /// gets or sets parent'id of this page
        /// </summary>
        public virtual long? ParentId { get; set; }
        /// <summary>
        /// get or set collection of page that they are children of this page
        /// </summary>
        public virtual ICollection<Page> Children { get; set; }
        #endregion
    }

  public enum ShowPageSection
    {
        Menu,
        Footer,
        SideBar
    }
مدل بالا مشخص کننده‌ی صفحاتی است که مدیر می‌تواند در پنل مدیریتی آنها را برای استفاده‌های خاصی تعریف کند. حالت درختی آن مشخص است. یکسری از خصوصیات مربوط به محتوای صفحه و همچنین تنظیمات سئو برای آن در نظر گرفته شده است که بیشتر آنها در مقالات قبل توضیح داده شده‌اند. خصوصیت Section از نوع ShowPageSection و برای مشخص کردن امکان نمایش صفحه‌ی مورد نظر در نظر گرفته شده‌است. همچنین این مدل بالا از کلاس پایه‌ی مطرح شده‌ی در اول مقاله، ارث بری کرده است که امکان ردیابی تغییرات آن را مهیا می‌کند.
  خوب! حجم مقاله زیاد شده است و تا اینجا کافی خواهد بود ؛ بر خلاف تصور بنده، یک مقاله‌ی دیگر نیز برای اتمام بحث لازم میباشد.

نتیجه‌ی تا این قسمت

مطالب
امکان یافتن پیش از موعد مشکلات قالب‌های Angular در نگارش 5 آن
مشکلات کامپوننت‌های Angular را چون با زبان TypeScript تهیه می‌شوند، می‌توان بلافاصله در ادیتور مورد استفاده و یا در حین کامپایل برنامه مشاهده کرد؛ اما یک چنین بررسی در مورد قالب‌های HTML ایی آن در زمان کامپایل انجام نمی‌شود و اگر مشکلی وجود داشته باشد، این مشکلات را صرفا در زمان اجرای برنامه در مرورگر می‌توان مشاهده کرد. برای رفع این مشکل و بهبود این وضعیت، در نگارش 5.2.0 فریم ورک Angular (و همچنین Angular CLI 1.7 به بعد)، پرچم جدیدی به تنظیمات کامپایلر آن اضافه شده‌است که با فعالسازی آن، مشکلات binding احتمالی در قالب‌های کامپوننت‌ها را می‌توان یافت. زمانیکه توسط Angular CLI یک برنامه‌ی Angular را در حالت AoT کامپایل می‌کنیم، کامپایلر مراحلی را طی می‌کند که توسط آن کدهای یک قالب کامپوننت، تبدیل به دستور العمل‌هایی قابل اجرای در مرورگر می‌شوند. در طی یکی از این مراحل، کامپایلر قالب‌های Angular، از کامپایلر TypeScript برای اعتبارسنجی عبارت‌های binding استفاده می‌کند. اکنون می‌توان خروجی این مرحله را نیز در حین کار با Angular CLI، مشاهده و مشکلات گزارش شده‌ی توسط آن‌را برطرف کرد.


فعالسازی بررسی مشکلات قالب‌های کامپوننت‌ها

برای فعالسازی بررسی مشکلات قالب‌های کامپوننت‌ها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{
  "compilerOptions": {
    "experimentalDecorators": true,
    ...
   },
   "angularCompilerOptions": {
     "fullTemplateTypeCheck": true,
     "preserveWhiteSpace": false,
     ...
   }
 }
- در اینجا با معرفی خاصیت fullTemplateTypeCheck و تنظیم آن به true، مشکلات موجود در قالب‌ها را در زمان کامپایل برنامه می‌توانید مشاهده کنید.
- البته این خاصیت در حین استفاده‌ی از یکی از دستورات ng serve --aot  و یا  ng build --prod انتخاب می‌شود.
- مقدار این پرچم در نگارش‌های 5x به صورت پیش‌فرض به false تنظیم شده‌است؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آن‌را شروع کنید.


یک مثال: بررسی خاصیت fullTemplateTypeCheck

فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کرده‌اید که فقط دارای خاصیت name است:
export interface PonyModel {
   name: string;
}
سپس یک خاصیت عمومی را بر همین مبنا در کامپوننتی، تعریف و مقدار دهی اولیه کرده‌اید:
import { PonyModel } from "./pony";

@Component({
  selector: "app-detect-common-errors-test",
  templateUrl: "./detect-common-errors-test.component.html",
  styleUrls: ["./detect-common-errors-test.component.css"]
})
export class DetectCommonErrorsTestComponent implements OnInit {

  ponyModel: PonyModel = { name: "Pony1" };
اکنون در قالب این کامپوننت، به شکل زیر از این وهله استفاده شده‌است:
 <p>Hello {{ponyModel.age}}

در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
 \detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
همانطور که ملاحظه می‌کنید اینبار خطاهای کامپایل فایل html نیز در خروجی کامپایلر ظاهر شده‌است و عنوان می‌کند خاصیت age در اینترفیس PonyModel وجود خارجی ندارد.

برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینه‌ی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوع‌های نال نپذیر عادت دهید. به این ترتیب می‌توانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آن‌ها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.


یک نکته‌ی تکمیلی
افزونه‌ی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارش‌های پیش از 5 نیز قابل استفاده است).
مطالب
ایجاد اشیاء دفاعی با ES 6 Proxy
ممکن است برای شما نیز پیش آمده باشد که به یک خصوصیت از یک شیء که وجود ندارد، ارجاع داده باشید و متوجه علت خطای رخ داده نشده و مدتی را به دنبال علت خطا صرف کرده باشید. بعضی از افراد به همین علت از جاوااسکریپت متنفر هستند و می‌گویند اگر از یک زبان type-safe استفاده می‌کردیم آنگاه در صورتیکه به خصوصیتی ارجاع می‌دادیم که وجود ندارد، نبودن خصوصیت ارجاع داده شده را اعلام می‌کرد. این مشکل وجود داشت تا وقتی که ECMAScript 6 ارائه شد.

ECMAScript 5

در حالیکه ECMAScript 5 قابلیت‌های فوق العاده‌ای را برای کنترل کردن خصوصیات موجود در اشیاء، در اختیار شما قرار می‌دهد، اما هیچ راه کاری را برای خصوصیاتی که موجود نیستند، ندارد. شما می‌توانید برای خواص موجود، از رونویسی (تنظیم writable برابر false) و یا حذف شدن (تنظیم configurable برابر false) جلوگیری کنید. شما می‌توانید از اختصاص خصوصیات جدید به اشیاء با استفاده از ()Object.preventExtensions و یا تنظیم تمام خصوصیات به صورت فقط خواندنی و یا غیرقابل حذف ()Object.freeze جلوگیری کنید.

اگر شما نمی‌خواهید تمام خصوصیات را فقط خواندنی کنید می‌توانید از ()Object.seal استفاده کنید. این‌ها مانع از اضافه کردن خصوصیات و یا حذف کردن خصوصیات موجود می‌شوند. اگر به یک شیء مهر و موم شده (sealed)، زمانی که از strict mode استفاده می‌کنید، یک خصوصیت جدید اضافه کنید باعث ایجاد خطا می‌شود:

"use strict";

var person = {
    name: "Vahid Mohammad Taheri"
};

Object.seal(person);
person.age = 27;    // Error!
این کار باعث اطلاع شما می‌شود که در حال تلاش برای تغییر اینترفیس یک شیء، با استفاده از اضافه کردن یک ویژگی به آن هستید. هنگامیکه سعی در خواندن ویژگی از یک شیء که جزئی از اینترفیس آن نیست، دارید نیز با خطا مواجه می‌شوید.

نجات با Proxyها

پروکسی‌ها، دارای سابقه طولانی و پیچیده ای در ECMAScript 6 است. طرح اولیه آن توسط Firefox و Chrome قبل از تصمیم TC-39 به تغییر پروکسی‌ها، اجرا شده است. این تغییرات، برای بهتر و روان‌تر شدن پروکسی‌ها از طرح اولیه پروکسی‌ها انجام گرفت.

بزرگترین تغییر صورت گرفته، معرفی شیء هدف که می‌خواست با پروکسی در تعامل باشد، بود. به جای تعریف دام برای نوع‌های مختلفی از عملیات، با ایجاد پروکسی، مستقیما برای عملیات‌ی از قبل تعیین شده بر روی اشیاء هدف، این کار را انجام می‌دهیم.

این کار از طریق یک سری از روش‌هایی که به مخفی کردن عملیات در ECMAScript مطابقت دارند، انجام می‌شود. به عنوان مثال زمانیکه بر روی یک ویژگی از یک شیء، عمل خواندن انجام می‌شود، عملیات  [[Get]] در موتور جاوااسکریپت انجام می‌گیرد. نحوه‌ی رفتار [[Get]] را نمی‌توان تغییر داد؛ با این حال، با استفاده از پروکسی‌ها می‌توان دامی برای زمان فراخوانی [[Get]]  قرار داد و عملیات خاص مورد نظر خود را اعمال کرد. به مثال زیر توجه کنید:

var proxy = new Proxy({ name: "Vahid" }, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return 13;
        }
    }
});

console.log(proxy.time);        // 13
console.log(proxy.name);        // "Vahid"
console.log(proxy.title);       // 13
این پروکسی از یک شیء ساخته شده به عنوان هدف (آرگومان اول به ()Proxy) استفاده می‌کند. آرگومان دوم دامی را که می‌خواهید برای این شیء بسازید، تعریف می‌کند. با استفاده از متد get عملیات مربوط به [[Get]] به دام افتاده و تابع تعریف شده‌ی ما اجرا می‌شود (باقی عملیات به صورت عادی اجرا می‌شوند). دامی که برای شیء مورد نظر تعریف کرده‌ایم دو پارامتر دریافت می‌کند (اول شی هدف، دوم ویژگی مورد نظر). با استفاده از کد نوشته شده در آن ابتدا بررسی می‌شود که شیء مورد نظر دارای ویژگی ارسال شده است یا خیر؟ در صورتی که وجود داشته باشد، مقدار آن بازگشت داده می‌شود و در غیر اینصورت به صورت ثابت مقدار 13 برگشت داده می‌شود.

برای ایجاد اشیاء دفاعی لازم است چگونگی رهگیری عملیات [[Get]] را درک کنید و هدف از این کار صدور خطا در زمان دستیابی به ویژگی ای از شیءایی که وجود ندارد است.
function createDefensiveObject(target) {

    return new Proxy(target, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
            }
        }
    });
}

تابع ()createDefensiveObject  یک شیء را به عنوان هدف می‌پذیرد و یک شیء دفاعی برای آن ایجاد می‌کند. پروکسی یک دام به نام get دارد؛ برای زمانی که عمل خواندن انجام می‌شود. اگر ویژگی خوانده شده در شیء وجود داشت، مقدار آن برگشت داده می‌شود و از سوی دیگر، وقتی ویژگی خوانده شده در شیء وجود نداشته باشد، سبب بروز خطا می‌شود. به مثال زیر توجه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"
console.log(defensivePerson.age);         // Error!
در اینجا ویژگی name به طور معمول کار خواهد کرد؛ ولی ویژگی age باعث صدور خطا می‌شود.
اشیاء دفاعی باعث می‌شوند تا بر روی ویژگی‌هایی که در شیء وجود دارند، بتوان عمل خواندن را انجام داد و در ویژگی‌هایی که موجود نیستند در هنگام خواندن، باعث صدور پیام خطا می‌شوند. با این حال هنوز هم شما می‌توانید ویژگی‌های جدید را بدون خطا اضافه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"

defensivePerson.age = 13;
console.log(defensivePerson.age);         // 13
بنابراین اشیاء توانایی خود را برای جهش و تغییر حفظ می‌کنند. در صورتی که شما چیزی را برای تغییر آنها انجام دهید، همیشه می‌توانید ویژگی‌هایی را به اشیاء اضافه کنید ولی عمل خواندن بر روی ویژگی‌های غیرموجود همیشه باعث صدور خطا و بازگشت مقدار undefined می‌شود.
روش‌های تشخیص ویژگی‌های استاندارد هنوز هم به طور معمول و بدون خطا کار می‌کنند.
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log("name" in defensivePerson);               // true
console.log(defensivePerson.hasOwnProperty("name"));  // true

console.log("age" in defensivePerson);                // false
console.log(defensivePerson.hasOwnProperty("age"));   // false
شما می‌توانید از اینترفیس یک شیء، زمانیکه دسترسی به یک ویژگی آن وجود ندارد، صورت می‌گیرد، با رد کردن اضافات و صدور پیام‌های خطا، دفاع کنید.
var person = {
    name: "Vahid"
};

Object.preventExtensions(person);

var defensivePerson = createDefensiveObject(person);


defensivePerson.age = 27;                 // Error!
console.log(defensivePerson.age);         // Error!
در این مورد، defensivePerson برای هر دو حالت خواندن و نوشتن ویژگی‌هایی که وجود ندارند، خطا صادر می‌کند.
شاید مفیدترین زمان برای استفاده از اشیاء دفاعی، در هنگام تعریف یک سازنده باشد و شما می‌توانید این کار را به عنوان یک قرارداد در نوشتن اشیاء حفظ کنید.
برای مثال:
function Person(name) {
    this.name = name;

    return createDefensiveObject(this);
}

var person = new Person("Vahid");
console.log(person.age);         // Error!
به وسیله فراخوانی تابع ()createDefensiveObject درون سازنده، می‌توانید اطمینان کامل داشته باشید که همه‌ی نمونه‌های ساخته شده‌ی از شیء Person، دارای حالت دفاعی می‌باشند.

مطالب
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
قصد داریم در طی چند پست متوالی، یک پروژه Paint را به صورت شی گرا پیاده سازی کنیم. خوب، پروژه ای که می‌خواهیم پیاده سازی کنیم باید دارای این امکانات باشه که مرحله به مرحله پیش میریم و پروزه کامل در نهایت در قسمت پروژه‌ها ی همین سایت قرار خواهد گرفت.

  1. قابلیت ترسیم اشیا روی بوم گرافیکی دلخواه
  2. قابلیت جابجایی اشیا
  3. قابلیت تغییر رنگ اشیا
  4. ترسیم اشیا توپر و تو خالی
  5. تعیین پهنای خط شی ترسیم شده
  6. تعیین رنگ پس زمینه در صورت تو پر بودن شی
  7. قابلیت پیش نمایش رسم شکل در زمان ترسیم اشیا
  8. توانایی انتخاب اشیا
  9. تعیین عمق شی روی بوم گرافیکی مورد نظر
  10. ترسیم اشیایی مانند خط، دایره، بیضی، مربع، مستطیل، لوزی، مثلث 
  11. قابلیت تغییر اندازه اشیا ترسیم شده

خوب برای شروع ابتدا یک پروژه از نوع Windows Application ایجاد می‌کنیم (البته برای این قسمت می‌توانیم یک پروژه Class Library ایجاد کنیم)

سپس یک پوشه به نام Models به پروزه اضافه نمایید.

خوب در این پروژه یک کلاس پایه به نام Shape در نظر می‌گیریم.

همه اشیا ما دارای نقطه شروع، نقطه پایان، رنگ قلم، حالت انتخاب، رنگ پس زمینه، نوع شی، .... می‌باشند که بعضی از خصوصیات را توسط خصوصیات دیگر محاسبه می‌کنیم. مثلا خاصیت Width و Height و X و Y توسط خصوصیات نقطه شروع و پایان می‌توانند محاسبه شوند.

ساختار کلاس‌های پروزه ما به صورت زیر است که مرحله به مرحله کلاس‌ها پیاده سازی خواهند شد.

با توجه به تصویر بالا (البته این تجزیه تحلیل شخصی من بوده و دوستان به سلیقه خود ممکن است این ساختار را تغییر دهند)

نوع شمارشی ShapeType: در این نوع شمارشی انواع شی‌های موجود در پروژه معرفی شده است

محتوای این نوع به صورت زیر تعریف شده است:

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Shape Type in Paint
    /// </summary>
    public enum ShapeType
    {
        /// <summary>
        /// هیچ
        /// </summary>
        None = 0,
        /// <summary>
        /// خط
        /// </summary>
        Line = 1,
        /// <summary>
        /// مربع
        /// </summary>
        Square = 2,
        /// <summary>
        /// مستطیل
        /// </summary>
        Rectangle = 3,
        /// <summary>
        /// بیضی
        /// </summary>
        Ellipse = 4,
        /// <summary>
        /// دایره
        /// </summary>
        Circle = 5,
        /// <summary>
        /// لوزی
        /// </summary>
        Diamond = 6,
        /// <summary>
        /// مثلث
        /// </summary>
        Triangle = 7,
    }
}
انشاالله در پست‌های بعدی ما بقی کلاس‌ها به مرور پیاده سازی خواهند شد.

نظرات مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
راه حل: نباید این‌کار را انجام دهید.
علت:
- اگر افزونه‌ای قرار هست برنامه‌ی اصلی را تغدیه کند - مثلا اعتبارسنجی - نام آن افزونه نیست و نباید به صورت افزونه تعریف شود. برنامه‌ی اصلی بجز بارگذاری افزونه‌ها هیچ کار دیگری قرار نیست با جزئیات آن‌ها به صورت مستقیم انجام دهد.
- اگر افزونه‌ای وابسته‌است به افزونه‌ی دیگر، نام اینکار افزونه نویسی نیست.
- شما قبل از اینکه بخواهید وارد این مبحث شوید، نیاز است کمی در مورد برنامه‌های افزونه پذیر موجود (در حالت کلی) مطالعه کنید و بررسی کنید که مثلا اگر یک برنامه‌ی پخش music افزونه پذیر است، افزونه‌ی A آن که توسط فرد X تهیه شده، آیا قرار است از امکانات افزونه‌ی B که توسط فرد Y تهیه شده‌است، استفاده کند؟ چنین کاری اساسا بی‌مفهوم است و طراحی افزونه پذیر نام ندارد. آیا افزونه‌ی A فایرفاکس از افزونه‌ی B آن استفاده می‌کند و به آن وابسته‌‌است؟ خیر.
- اگر قرار هست افزونه‌ها به یک سری اطلاعات مشترک دسترسی پیدا کنند، این اطلاعات باید مشترک باشند و مستقل از هر کدام از افزونه‌ها.

در مثالی که ارائه شد، اگر هدف کوئری گرفتن از لیست خبرهای یک کاربر است، این کار فقط باید در افزونه‌ی News انجام شود (چون اگر قرار باشد سایر افزونه‌ها به ریز اطلاعات news دسترسی داشته باشند که ضرورتی به افزونه تعریف کردن آن نبود) و به این صورت:
 var userNewsList =  _news.Include(x=>x.User).Where(x=>x.UserId == 1).ToList();
مطالب
تشخیص اصالت ردیف‌های یک بانک اطلاعاتی در EF Core
همیشه فرض بر این است که مدیر سیستم، فردی است امین و درستکار. این شخص/اشخاص کارهای شبکه، پشتیبان‌گیری، نگهداری و امثال آن‌را انجام داده و از سیستم‌ها محافظت می‌کنند. اکنون این سناریوهای واقعی را درنظر بگیرید:
- پس از خداحافظی با شرکتی که در آن کار می‌کردی، شخصی با پوزخند به شما می‌گوید که «می‌دونستی در برنامه‌ی حق و دستمزد شما، بچه‌های ادمین شبکه، دیتابیس برنامه رو مستقیما دستکاری می‌کردند و تعداد ساعات کاری بیشتری رو وارد می‌کردند»؟!
- مسئول فروشی/مسئول پذیرشی که یاد گرفته چطور به صورت مستقیم به بانک اطلاعاتی دسترسی پیدا کند و آمار فروش/پذیرش روز خودش را در بانک اطلاعاتی، با دستکاری مستقیم و خارج از برنامه، کمتر از مقدار واقعی نمایش دهد.
- باز هم مدیر سیستمی/شبکه‌ای که دسترسی مستقیم به بانک اطلاعاتی دارد، در ساعاتی مشخص، کلمه‌ی عبور هش شده‌ی خودش را مستقیما، بجای کلمه‌ی عبور ادمین برنامه در بانک اطلاعاتی وارد کرده و پس از آن ...

این موارد متاسفانه واقعی هستند! اکنون سؤال اینجا است که آیا برنامه‌ی شما قادر است تشخیص دهد رکوردهایی که هم اکنون در بانک اطلاعاتی ثبت شده‌اند، واقعا توسط برنامه و تمام سطوح دسترسی که برای آن طراحی کرده‌اید، به این شکل درآمده‌اند، یا اینکه توسط اشخاصی به صورت مستقیم و با دور زدن کامل برنامه، از طریق management studioهای مختلف، در سیستم وارد و دستکاری شده‌اند؟! در ادامه راه حلی را برای بررسی این مشکل مهم، مرور خواهیم کرد.


چگونه تغییرات رکوردها را در بانک‌های اطلاعاتی ردیابی کنیم؟

روش متداولی که برای بررسی تغییرات رکوردها مورد استفاده قرار می‌گیرد، هش کردن تمام اطلاعات یک ردیف از جدول است و سپس مقایسه‌ی این هش‌ها با هم. علت استفاده‌ی از الگوریتم‌های هش نیز، حداقل به دو علت است:
- با تغییر حتی یک بیت از اطلاعات، مقدار هش تولید شده تغییر می‌کند.
- طول نهایی مقدار هش شده‌ی اطلاعاتی حجیم، بسیار کم است و به راحتی توسط بانک‌های اطلاعاتی، قابل مدیریت و جستجو است.

اگر از SQL Server استفاده می‌کنید، یک چنین قابلیتی را به صورت توکار به همراه دارد:
SELECT
    [Id], 
    (SELECT top 1  * FROM  [AppUsers] FOR XML auto),
    HASHBYTES ('SHA2_256', (SELECT top 1  * FROM  [AppUsers] FOR XML auto)) AS [hash] -- varbinary(n), since 2012
FROM
    [AppUsers]
با این خروجی


کاری که این کوئری انجام می‌دهد شامل دو مرحله است:
الف) کوئری "SELECT top 1 * FROM [AppUsers] FOR XML auto" کاری شبیه به serialization را انجام می‌دهد. همانطور که مشاهده می‌کنید، نام و مقادیر تمام فیلدهای یک ردیف را به صورت یک خروجی XML در می‌آورد. بنابراین دیگر نیازی نیست تا کار تبدیل مقادیر تمام ستون‌های یک ردیف را به عبارتی قابل هش، به صورت دستی انجام دهیم؛ رشته‌ی XML ای آن هم اکنون آماده‌است.
ب) متد HASHBYTES، این خروجی serialized را با الگوریتم SHA2_256، هش می‌کند. الگوریتم‌های SHA2_256 و همچنین SHA2_512، از سال 2012 به بعد به SQL Server اضافه شده‌اند.

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


چگونه تغییرات رکوردها را در بانک‌های اطلاعاتی، توسط EF Core ردیابی کنیم؟

مزیت روش فوق، توکار بودن آن است که کارآیی فوق العاده‌ای را نیز به همراه دارد. اما چون در ادامه قصد داریم از یک ORM استفاده کنیم و ORMها نیز قرار است توانایی کار کردن با انواع و اقسام بانک‌های اطلاعاتی را داشته باشند، دو مرحله‌ی serialization و هش کردن را در کدهای برنامه و با مدیریت EF Core، مستقل از بانک اطلاعاتی خاصی، انجام خواهیم داد.


معرفی موجودیت‌های برنامه

در مثالی که بررسی خواهیم کرد، دو موجودیت Blog و Post تعریف شده‌اند:
using System.Collections.Generic;

namespace EFCoreRowIntegrity
{
    public interface IAuditableEntity
    {
        string Hash { set; get; }
    }

    public static class AuditableShadowProperties
    {
        public static readonly string CreatedDateTime = nameof(CreatedDateTime);
        public static readonly string ModifiedDateTime = nameof(ModifiedDateTime);
    }

    public class Blog : IAuditableEntity
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        public List<Post> Posts { get; set; }

        public string Hash { get; set; }
    }

    public class Post : IAuditableEntity
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }

        public string Hash { get; set; }
    }
}
- در اینجا اینترفیس IAuditableEntity را نیز مشاهده می‌کنید که دارای یک خاصیت Hash است. تمام موجودیت‌هایی که قرار است دارای فیلد هش باشند، نیاز است این اینترفیس را پیاده سازی کنند؛ مانند دو موجودیت Blog و Post. در ادامه مقدار خاصیت هش را به صورت خودکار توسط سیستم Tracking، محاسبه و به روز رسانی می‌کنیم.
- به علاوه جهت تکمیل بحث، دو خاصیت سایه‌ای نیز تعریف شده‌اند تا بررسی کنیم که آیا هش این‌ها نیز درست محاسبه می‌شود یا خیر.
- علت اینکه خاصیت Hash، سایه‌ای تعریف نشد، سهولت دسترسی و بالا بردن کارآیی آن بود.



معرفی ظرفی برای نگهداری نام خواص و مقادیر متناظر با یک موجودیت

در ادامه دو کلاس AuditEntry و AuditProperty را مشاهده می‌کنید:
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace EFCoreRowIntegrity
{
    public class AuditEntry
    {
        public EntityEntry EntityEntry { set; get; }
        public IList<AuditProperty> AuditProperties { set; get; } = new List<AuditProperty>();

        public AuditEntry() { }

        public AuditEntry(EntityEntry entry)
        {
            EntityEntry = entry;
        }
    }

    public class AuditProperty
    {
        public string Name { set; get; }
        public object Value { set; get; }

        public bool IsTemporary { set; get; }
        public PropertyEntry PropertyEntry { set; get; }

        public AuditProperty() { }

        public AuditProperty(string name, object value, bool isTemporary, PropertyEntry property)
        {
            Name = name;
            Value = value;
            IsTemporary = isTemporary;
            PropertyEntry = property;
        }
    }
}
زمانیکه توسط سیستم Tracking، موجودیت‌های اضافه شده و یا ویرایش شده را استخراج می‌کنیم، AuditEntry همان موجودیت در حال بررسی است که دارای تعدادی خاصیت یا AuditProperty می‌باشد. این‌ها را توسط دو کلاس فوق برای عملیات بعدی، ذخیره و نگهداری می‌کنیم.


معرفی روشی برای هش کردن مقادیر یک شیء

زمانیکه توسط سیستم Tracking، در حال کاربر بر روی موجودیت‌های اضافه شده و یا ویرایش شده هستیم، می‌خواهیم فیلد هش آن‌ها را نیز به صورت خودکار ویرایش و مقدار دهی کنیم. کلاس زیر، منطق ارائه دهنده‌ی این مقدار هش را بیان می‌کند:
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Newtonsoft.Json;

namespace EFCoreRowIntegrity
{
    public static class HashingExtensions
    {
        public static string GenerateObjectHash(this object @object)
        {
            if (@object == null)
            {
                return string.Empty;
            }

            var jsonData = JsonConvert.SerializeObject(@object, Formatting.Indented);
            using (var hashAlgorithm = new SHA256CryptoServiceProvider())
            {
                var byteValue = Encoding.UTF8.GetBytes(jsonData);
                var byteHash = hashAlgorithm.ComputeHash(byteValue);
                return Convert.ToBase64String(byteHash);
            }
        }

        public static string GenerateEntityEntryHash(this EntityEntry entry, string propertyToIgnore)
        {
            var auditEntry = new Dictionary<string, object>();
            foreach (var property in entry.Properties)
            {
                var propertyName = property.Metadata.Name;
                if (propertyName == propertyToIgnore)
                {
                    continue;
                }
                auditEntry[propertyName] = property.CurrentValue;
            }
            return auditEntry.GenerateObjectHash();
        }

        public static string GenerateEntityHash<TEntity>(this DbContext context, TEntity entity, string propertyToIgnore)
        {
            return context.Entry(entity).GenerateEntityEntryHash(propertyToIgnore);
        }
    }
}
- در اینجا توسط متد JsonConvert.SerializeObject کتابخانه‌ی Newtonsoft.Json، شیء موجودیت را تبدیل به یک رشته‌ی JSON کرده و توسط الگوریتم SHA256 هش می‌کنیم. در آخر هم این مقدار را به صورت Base64 ارائه می‌دهیم.
- نکته‌ی مهم: ما نمی‌خواهیم تمام خواص یک موجودیت را هش کنیم. برای مثال اگر موجودیتی دارای چندین رابطه با جداول دیگری بود، ما مقادیر این‌ها را هش نمی‌کنیم (چون رکوردهای متناظر با آن‌ها در جداول خودشان می‌توانند دارای فیلد هش مخصوصی باشند). بنابراین یک Dictionary را از خواص و مقادیر متناظر با آن‌ها تشکیل داده و این Dictionary را تبدیل به JSON می‌کنیم.
- همچنین در این بین، مقدار خود فیلد Hash یک شیء نیز نباید در هش محاسبه شده، حضور داشته باشد. به همین جهت پارامتر propertyToIgnore را مشاهده می‌کنید.


معرفی Context برنامه که کار هش کردن خودکار موجودیت‌ها را انجام می‌دهد

اکنون نوبت استفاده از تنظیمات انجام شده‌ی تا این مرحله‌است:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;

namespace EFCoreRowIntegrity
{
    public class BloggingContext : DbContext
    {
        public BloggingContext()
        { }

        public BloggingContext(DbContextOptions options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.EnableSensitiveDataLogging();
                var path = Path.Combine(Directory.GetCurrentDirectory(), "app_data", "EFCore.RowIntegrity.mdf");
                optionsBuilder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=EFCore.RowIntegrity;AttachDbFilename={path};Trusted_Connection=True;");
                optionsBuilder.UseLoggerFactory(new LoggerFactory().AddConsole((message, logLevel) =>
                logLevel == LogLevel.Debug &&
                           message.StartsWith("Microsoft.EntityFrameworkCore.Database.Command")));
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            foreach (var entityType in modelBuilder.Model
                                                   .GetEntityTypes()
                                                   .Where(e => typeof(IAuditableEntity)
                                                   .IsAssignableFrom(e.ClrType)))
            {
                modelBuilder.Entity(entityType.ClrType)
                            .Property<DateTimeOffset?>(AuditableShadowProperties.CreatedDateTime);
                modelBuilder.Entity(entityType.ClrType)
                            .Property<DateTimeOffset?>(AuditableShadowProperties.ModifiedDateTime);
            }
        }

        public override int SaveChanges()
        {
            var auditEntries = OnBeforeSaveChanges();
            var result = base.SaveChanges();
            OnAfterSaveChanges(auditEntries);
            return result;
        }

        private IList<AuditEntry> OnBeforeSaveChanges()
        {
            var auditEntries = new List<AuditEntry>();

            foreach (var entry in ChangeTracker.Entries<IAuditableEntity>())
            {
                if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                {
                    continue;
                }

                var auditEntry = new AuditEntry(entry);
                auditEntries.Add(auditEntry);

                var now = DateTimeOffset.UtcNow;

                foreach (var property in entry.Properties)
                {
                    var propertyName = property.Metadata.Name;
                    if (propertyName == nameof(IAuditableEntity.Hash))
                    {
                        continue;
                    }

                    if (property.IsTemporary)
                    {
                        // It's an auto-generated value and should be retrieved from the DB after calling the base.SaveChanges().
                        auditEntry.AuditProperties.Add(new AuditProperty(propertyName, null, true, property));
                        continue;
                    }

                    switch (entry.State)
                    {
                        case EntityState.Added:
                            entry.Property(AuditableShadowProperties.CreatedDateTime).CurrentValue = now;
                            auditEntry.AuditProperties.Add(new AuditProperty(propertyName, property.CurrentValue, false, property));
                            break;
                        case EntityState.Modified:
                            auditEntry.AuditProperties.Add(new AuditProperty(propertyName, property.CurrentValue, false, property));
                            entry.Property(AuditableShadowProperties.ModifiedDateTime).CurrentValue = now;
                            break;
                    }
                }
            }

            return auditEntries;
        }

        private void OnAfterSaveChanges(IList<AuditEntry> auditEntries)
        {
            foreach (var auditEntry in auditEntries)
            {
                foreach (var auditProperty in auditEntry.AuditProperties.Where(x => x.IsTemporary))
                {
                    // Now we have the auto-generated value from the DB.
                    auditProperty.Value = auditProperty.PropertyEntry.CurrentValue;
                    auditProperty.IsTemporary = false;
                }
                auditEntry.EntityEntry.Property(nameof(IAuditableEntity.Hash)).CurrentValue =
                    auditEntry.AuditProperties.ToDictionary(x => x.Name, x => x.Value).GenerateObjectHash();
            }
            base.SaveChanges();
        }
    }
}
در اینجا اصل کار، در متد بازنویسی شده‌ی SaveChanges انجام می‌شود:
public override int SaveChanges()
{
    var auditEntries = OnBeforeSaveChanges();
    var result = base.SaveChanges();
    OnAfterSaveChanges(auditEntries);
    return result;
}
در متد OnBeforeSaveChanges، تمام موجودیت‌های تغییر کرده‌ی از نوع IAuditableEntity را که دارای فیلد هش هستند، یافته و نام خاصیت و مقدار متناظر با آن‌ها را در ظرف‌های AuditEntry که پیشتر معرفی شدند، ذخیره می‌کنیم. هنوز در این مرحله کار هش کردن را انجام نخواهیم داد. علت را می‌توانید در بررسی خواص موقتی مشاهده کنید:
if (property.IsTemporary)
{
   // It's an auto-generated value and should be retrieved from the DB after calling the base.SaveChanges().
   auditEntry.AuditProperties.Add(new AuditProperty(propertyName, null, true, property));
   continue;
}
خواص موقتی، عموما تولید شده‌ی توسط دیتابیس هستند. برای مثال زمانیکه یک Id عددی خود افزاینده را به عنوان کلید اصلی جدول معرفی می‌کنید، مقدار آن پس از فراخوانی متد base.SaveChanges، از بانک اطلاعاتی دریافت شده و در اختیار برنامه قرار می‌گیرد. به همین جهت است که نیاز داریم لیست این خواص و مقادیر را یکبار پیش از base.SaveChanges ذخیره کنیم و پس از آن، خواص موقتی را که اکنون دارای مقدار هستند، مقدار دهی کرده و سپس هش نهایی شیء را محاسبه کنیم. اگر پیش از base.SaveChanges این هش را محاسبه کنیم، برای مثال حاوی مقدار Id شیء، نخواهد بود.

همین مقدار تنظیم، برای محاسبه و به روز رسانی خودکار فیلد هش، کفایت می‌کند.


روش بررسی اصالت یک موجودیت

در متد زیر، روش محاسبه‌ی هش واقعی یک موجودیت دریافت شده‌ی از بانک اطلاعاتی را توسط متد الحاقی GenerateEntityHash مشاهده می‌کنید. اگر این هش واقعی (بر اساس مقادیر فعلی این ردیف که حتی ممکن است به صورت دستی و خارج از برنامه تغییر کرده باشد)، با مقدار Hash ثبت شده‌ی پیشین در آن ردیف یکی بود، اصالت این ردیف تائید خواهد شد:
private static void CheckRow1IsAuthentic()
{
    using (var context = new BloggingContext())
    {
        var blog1 = context.Blogs.Single(x => x.BlogId == 1);
        var entityHash = context.GenerateEntityHash(blog1, propertyToIgnore: nameof(IAuditableEntity.Hash));
        var dbRowHash = blog1.Hash;
        Console.WriteLine($"entityHash: {entityHash}\ndbRowHash:  {dbRowHash}");
        if (entityHash == dbRowHash)
        {
            Console.WriteLine("This row is authentic!");
        }
        else
        {
            Console.WriteLine("This row is tampered outside of the application!");
        }
    }
}
یک نمونه خروجی آن به صورت زیر است:
entityHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
dbRowHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
This row is authentic!

اکنون بانک اطلاعاتی را خارج از برنامه، مستقیما دستکاری می‌کنیم و برای مثال Url اولین ردیف را تغییر می‌دهیم:


در ادامه یکبار دیگر برنامه را اجرا خواهیم کرد:
entityHash: tdiZhKMJRnROGLLam1WpldA0fy/CbjJaR2Y2jNU9izk=
dbRowHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
This row is tampered outside of the application!
همانطور که مشاهده می‌کنید، هش واقعی جدید، با هش ثبت شده‌ی در ردیف، یکی نیست؛ که بیانگر ویرایش مستقیم این ردیف می‌باشد.
به علاوه باید درنظر داشت، محاسبه‌ی این هش بدون خود برنامه، کار ساده‌ای نیست. به همین جهت به روز رسانی دستی آن تقریبا غیرممکن است؛ خصوصا اگر متد GenerateObjectHash، کمی با پیچ و تاب بیشتری نیز تهیه شود.


چگونه وضعیت اصالت تعدادی ردیف را بررسی کنیم؟

مثال قبل، در مورد روش بررسی اصالت یک تک ردیف بود. کوئری زیر روش محاسبه‌ی فیلد جدید IsAuthentic را در بین لیستی از ردیف‌ها نمایش می‌دهد:
var blogs = (from blog in context.Blogs.ToList() // Note: this `ToList()` is necessary here for having Shadow properties values, otherwise they will considered `null`.
             let computedHash = context.GenerateEntityHash(blog, nameof(IAuditableEntity.Hash))
             select new
             {
               blog.BlogId,
               blog.Url,
               RowHash = blog.Hash,
               ComputedHash = computedHash,
               IsAuthentic = blog.Hash == computedHash
             }).ToList();


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: EFCoreRowIntegrity.zip