یک دست سازی ی و ک در برنامه‌های Entity framework 6
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

تا قبل از EF 6 برای طراحی یک سیستم عمومی تغییر مقادیر ثبت شده در بانک اطلاعاتی، می‌شد با استفاده از امکانات توکار Tracking آن، مقادیر تغییر کرده را یافت و برای مثال ی و ک آن‌ها را پیش از درج در بانک اطلاعاتی، یک دست کرد. در EF 6 با معرفی یک سری interceptor می‌توان به مراحل پیش و پس از اجرای کوئری‌ها دسترسی پیدا کرد. عمده‌ترین کاربرد آن، لاگ کردن SQLهای تولیدی و نوشتن برنامه‌هایی شبیه به EF Profiler است. اما ... استفاده‌ی دیگری را نیز می‌توان از IDbCommandInterceptor جدید آن تدارک دید: دستکاری SQL تولیدی توسط آن پیش از اعمال به بانک اطلاعاتی.

طراحی یک Interceptor برای یک دست سازی ی و ک

در اینجا کدهای کلاس YeKeInterceptor را ملاحظه می‌کنید. در متدهایی که به کلمه‌ی Executing ختم می‌شوند، می‌توان به دستورات SQL تولید شده توسط EF، پیش از اعمال بر روی بانک اطلاعاتی دسترسی داشت:
    public class YeKeInterceptor : IDbCommandInterceptor
    {
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }

        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }

        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }
    }
DbCommand، حاوی تمام اطلاعاتی است که به آن نیاز داریم؛ شامل CommandText یا همان SQL تولید شده و همچنین command.Parameters برای دسترسی به مقادیر پارامترهای کوئری. نکته‌ی مهم تمام این موارد، قابل ویرایش بودن آن‌ها است.
    public static class YeKe
    {
        public const char ArabicYeChar = (char)1610;
        public const char PersianYeChar = (char)1740;

        public const char ArabicKeChar = (char)1603;
        public const char PersianKeChar = (char)1705;

        public static string ApplyCorrectYeKe(this object data)
        {
            return data == null ? null : ApplyCorrectYeKe(data.ToString());
        }

        public static string ApplyCorrectYeKe(this string data)
        {
            return string.IsNullOrWhiteSpace(data) ?
                        string.Empty :
                        data.Replace(ArabicYeChar, PersianYeChar).Replace(ArabicKeChar, PersianKeChar).Trim();
        }

        public static void ApplyCorrectYeKe(this DbCommand command)
        {
            command.CommandText = command.CommandText.ApplyCorrectYeKe();

            foreach (DbParameter parameter in command.Parameters)
            {
                switch (parameter.DbType)
                {
                    case DbType.AnsiString:
                    case DbType.AnsiStringFixedLength:
                    case DbType.String:
                    case DbType.StringFixedLength:
                    case DbType.Xml:
                        parameter.Value =   parameter.Value is DBNull ? parameter.Value : parameter.Value.ApplyCorrectYeKe();
                        break;
                }
            }
        }
    }
در اینجا پیاده سازی متد الحاقی ApplyCorrectYeKe را که در کلاس YeKeInterceptor مورد استفاده قرار گرفت، ملاحظه می‌کنید.
در آن، CommandText و همچنین parameter.Valueها در صورت رشته‌ای بودن، اصلاح می‌شوند.
سربار این روش نسبت به روش‌های پیشین استفاده از Reflection کمتر است. همچنین اشیاء پیچیده و تو در تو را نیز بهتر پشتیبانی می‌کند؛ چون در مرحله Executing، کار پردازش این اشیاء پایان یافته و SQL خام نهایی آن در اختیار ما است.


نحوه‌ی استفاده از YeKeInterceptor

در آغاز برنامه (برای مثال متد Application_Start فایل Global.asax.cs برنامه‌های MVC )، سطر زیر را فراخوانی کنید:
 DbInterception.Add(new YeKeInterceptor());

یک مثال کامل برای دریافت
Sample32.cs
  • #
    ‫۱۰ سال و ۶ ماه قبل، دوشنبه ۱۸ فروردین ۱۳۹۳، ساعت ۱۷:۵۹

    بر روی SqlServer درست کارمی کند ولی بر روی کانکشن SQL CE پیغام زیر را می‌دهد:

    System.NotSupportedException was unhandled by user code
      HResult=-2146233067
      Message=DesignTimeVisible
      Source=EntityFramework.SqlServerCompact
      StackTrace:
           at System.Data.Entity.SqlServerCompact.SqlCeMultiCommand.set_CommandText(String value)
           at EfExt.YeKe.ApplyCorrectYeKe(DbCommand command) in e:\test\EfExt\YeKe.cs:line 33
           at EfExt.YeKeInterceptor.ReaderExecuting(DbCommand command, DbCommandInterceptionContext`1 interceptionContext) in e:\MyFilesAndPrograms\WebIO\EfExt\YeKeInterceptor.cs:line 15
           at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<Reader>b__d(IDbCommandInterceptor i, DbCommand t, DbCommandInterceptionContext`1 c)
           at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
           at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext)
           at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteDbDataReader(CommandBehavior behavior)
           at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
           at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
      InnerException:
    • #
      ‫۱۰ سال و ۶ ماه قبل، دوشنبه ۱۸ فروردین ۱۳۹۳، ساعت ۱۹:۰۲
      برای SQL CE سطر زیر را حذف کنید:
              command.CommandText = command.CommandText.ApplyCorrectYeKe();
      در اصل نیازی به آن نیست؛ چون مقادیر ارسالی توسط پارامترها جابجا می‌شوند و در CommandText به صورت مستقیم حضور ندارند.
  • #
    ‫۱۰ سال و ۶ ماه قبل، دوشنبه ۱۸ فروردین ۱۳۹۳، ساعت ۲۱:۱۸
    سلام. چطور میتونم نسخه‌ی EF پروژه ام رو به EF6 ارتقاء بدم؟
    اگر این ارتقاء رو انجام بدم به مشکلی در پروژه ام برنمیخورم؟
  • #
    ‫۱۰ سال و ۵ ماه قبل، سه‌شنبه ۲۶ فروردین ۱۳۹۳، ساعت ۱۶:۰۲
    با تشکر از پست مفید جناب نصیری
    من هم این کار را انجام داده ام در دو سطح برنامه و دیتابیس ...
    ابتدا باید دیتا بیس را یکسان سازی کرد (یعنی ی و ک‌ها فقط از یک مدل باشند)
    CREATE PROCEDURE [dbo].[spr_Admin_Replace_Ye_Ke_InAllTables]
    AS
    BEGIN
    BEGIN TRAN
    --ی--%u06CC
    --ی--%u064A
    --ک--%u06A9
    --ک--%u0643
    DECLARE @Ye_Farsi NCHAR(1),  @Ye_Arabi NCHAR(1), @Ke_Farsi NCHAR(1), @Ke_Arabi NCHAR(1)
    SET @Ye_Farsi = NCHAR(0X06CC) 
    SET @Ye_Arabi = NCHAR(0X064A)
    SET @Ke_Farsi = NCHAR(0X06A9)
    SET @Ke_Arabi = NCHAR(0X0643)
    --SELECT @Ye_Farsi, UNICODE(@Ye_Farsi) AS Ye_Farsi_Code, @Ye_Arabi, UNICODE(@Ye_Arabi) AS Ye_Arabi_Code,@Ke_Farsi, UNICODE(@Ke_Farsi) AS Ke_Farsi_Code, @Ke_Arabi, UNICODE(@Ke_Arabi) AS Ke_Arabi_Code
    
    --SELECT * FROM sys.types 
    DECLARE xcur CURSOR FOR -- a cursor for string columns 
    SELECT sys.tables.name AS TableName, sys.columns.name AS ColumnName
    FROM sys.tables INNER JOIN sys.columns ON sys.tables.object_id = sys.columns.object_id
    WHERE sys.columns.system_type_id IN (35, 99, 167, 175, 231, 239)
    
    OPEN xcur
    
    DECLARE @SqlString nvarchar(1000), @TName nvarchar(255), @CName nvarchar(255), @ret int
    
    FETCH NEXT FROM xcur INTO @TName, @CName
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
    BEGIN TRY
    SET @SqlString = N'UPDATE ' + @TName + ' SET ' + @CName + ' = REPLACE( REPLACE(' + @CName + ',''' +  @Ye_Farsi + ''',''' + @Ye_Arabi + ''') ,''' +  @Ke_Farsi + ''',''' + @Ke_Arabi + ''')';
    EXEC @ret = sp_executesql @SqlString
    PRINT @ret
    END TRY
    BEGIN CATCH
    PRINT @SqlString
    PRINT ERROR_MESSAGE()
    END CATCH
    
    FETCH NEXT FROM xcur INTO @TName, @CName
    END 
    
    CLOSE xcur
    DEALLOCATE xcur
    
    ROLLBACK TRAN
    END
    سپس داخل کد برنامه و هنگام ثبت، ویرایش و جستجو
    ی و ک موجود در کلمات ورودی توسط کاربر را با ی و ک درست(همان‌ها که در دیتابیس هستند)، جایگزین کنیم، و بعد عمل مورد نظر را انجام دهیم.
        public class YeKeLetters
        {
            public static char Ye_Farsi = '\x06CC'; // ی %u06CC
            public static char Ye_Arabi = '\x064A'; // ی %u064A
            public static char Ke_Farsi = '\x06A9'; // ک %u06A9
            public static char Ke_Arabi = '\x0643'; // ک %u0643
        }

  • #
    ‫۱۰ سال و ۵ ماه قبل، دوشنبه ۱۵ اردیبهشت ۱۳۹۳، ساعت ۱۲:۴۲
    دلیل تغییراتی که در رشته رو میدید متوجه نشدم! استفاده از trim و بازگردادن رشته خالی
    به نظرم اینطوری بهتره:
    public static string ApplyCorrectYeKe(this string data)
    {
    return string.IsNullOrWhiteSpace(data)
    ? data
    : data.Replace(ArabicYeChar, PersianYeChar).Replace(ArabicKeChar, PersianKeChar);
    }
    • #
      ‫۱۰ سال و ۵ ماه قبل، دوشنبه ۱۵ اردیبهشت ۱۳۹۳، ساعت ۱۴:۳۵
      - بازگشت رشته خالی بجای نال: آشنایی با Defensive programming  قسمت اول و دوم.
      - حذف فواصل خالی: فواصل خالی ابتدا و انتهای رشته در خیلی از موارد نباید حضور داشته باشند. مثلا در ثبت نام فرق است بین «سالار» و «  سالار     ».
  • #
    ‫۹ سال و ۱۰ ماه قبل، دوشنبه ۳ آذر ۱۳۹۳، ساعت ۱۹:۵۰
    سلام.
    آیا اینجا می‌توان  تمیزسازی HTML و مقابله با XSS  را انجام داد؟
    • #
      ‫۹ سال و ۱۰ ماه قبل، دوشنبه ۳ آذر ۱۳۹۳، ساعت ۲۲:۰۶
      می‌توان.
  • #
    ‫۹ سال و ۱ ماه قبل، شنبه ۲۴ مرداد ۱۳۹۴، ساعت ۲۳:۴۵
    با سلام؛ در معماری این مثال شما ، در کدام لایه باید این کلاس YeKeInterceptor  و YeKe تعریف شوند ؟ با توجه به تعاریف شما در دوره آموزشی ED ،  قطعا نباید این کلاس‌ها در لایه Domain Classes و Service تعریف شوند. آیا با این تفاسیر باید در لایه Data تعریف کنیم این کلاس هارو ؟
    در این مثال ، نویسنده در لایه Data این عملات را انجام داده ولی با روشی متفاوت تر. که با توجه به توضیحات شما ، روش این مثال برای Select گزینه‌ی مناسبی نیست. میشه لطف کنید و بفرمایید کلاس‌ها رو کجا تعریف کنیم و دلیل تعریف چیست و اینکه چگونه در Context آن را فراخوانی کنیم که برای عملایت CRUD عملیات ApplyCorrectYeKe رعایت شود
    با تشکر
    • #
      ‫۹ سال و ۱ ماه قبل، شنبه ۲۴ مرداد ۱۳۹۴، ساعت ۲۳:۵۷
      - این کلاس‌ها صرفا یک سری کلاس کمکی هستند که هیچ وابستگی به قسمت خاصی از برنامه ندارند و برعکس. به همین جهت یک اسمبلی Common ایجاد کرده و آن‌ها را در این اسمبلی مشترک که یک سری utility عمومی در آن تعریف خواهید کرد، قرار دهید.
      + در انتهای مطلب ذکر شد: «... در آغاز برنامه، سطر زیر را فراخوانی کنید ...». آغاز برنامه‌های وب، در متد Application_Start است و در برنامه‌های دسکتاپ، متد Main یا شبیه به آن.
      + Interceptorها به صورت یک لایه‌ی نامرئی توسط EF اعمال می‌شوند. بنابراین قرار نیست توسط شما مستقیما جایی فراخوانی شود. یکبار که در آغاز برنامه تعریف و به EF معرفی شدند، کافی است.
  • #
    ‫۸ سال قبل، شنبه ۲۷ شهریور ۱۳۹۵، ساعت ۱۶:۳۶
    راهی وجود داره که بشه کاری کرد که برای یه کوئری خاص این interceptor اجرا نشه؟
    • #
      ‫۸ سال قبل، شنبه ۲۷ شهریور ۱۳۹۵، ساعت ۱۶:۵۱
      command.CommandText متن کامل کوئری SQL را به همراه دارد. بر اساس این محتوا تصمیم گیری کنید که آیا ApplyCorrectYeKe به آن اعمال بشود یا خیر.
      • #
        ‫۸ سال قبل، شنبه ۲۷ شهریور ۱۳۹۵، ساعت ۱۷:۱۴
        ممنون از پاسختون
        راهی نیست که بشه به غیر از متن کوئری تصمیم گرفت؟ چون من چندین جا کوئری‌های مختلف و طولانی دارم که میخوام این اعمال انجام نشه و چک کردن از طریق متن کوئری یه کم سخت میشه
        • #
          ‫۸ سال قبل، شنبه ۲۷ شهریور ۱۳۹۵، ساعت ۱۷:۳۶
          می‌شود متد SaveChanges را بر اساس مفاهیم Tracking سفارشی سازی کرد (مثال «ب» آن). در اینجا مثلا یک متد SaveChangesWithApplyYeKe جدید را درست کنید که این یکسان سازی را اعمال کند (مطابق مثال ب) و برای متد اصلی SaveChanges خیر. به این صورت می‌توانید تصمیم گیری کنید که کجا این موارد اعمال شوند (با فراخوانی SaveChangesWithApplyYeKe سفارشی) یا خیر (با فراخوانی SaveChanges معمولی).
  • #
    ‫۸ سال قبل، یکشنبه ۲۸ شهریور ۱۳۹۵، ساعت ۱۴:۱۷
    سلام
    اگر در دیتابیس این حروف به فارسی تبدیل شوند و بخواهیم در سمت کلاینت از dataTables جهت نمایش جدول اطلاعات استفاده کنیم . اگر سمت کلاینت این حروف را به عربی وارد کنند در نتایج جستجو کلماتی که با حروف فارسی ی و ک است را نمایش نمی‌دهد
    • #
      ‫۸ سال قبل، یکشنبه ۲۸ شهریور ۱۳۹۵، ساعت ۱۴:۲۳
      - در مطلب جاری حلقه‌ی foreach (DbParameter parameter in command.Parameters) نوشته شده دقیقا همین مورد را اصلاح می‌کند. مقادیر رشته‌ای پارامترهای ارسالی به یک کوئری را هم اصلاح می‌کند (بحث سمت سرور).
      - برای فیلترهای سمت کلاینت و جستجوی صرفا داخل مرورگر، از این مطلب ایده بگیرید: « یک دست سازی ی و ک دریافتی در صفحات وب»
      • #
        ‫۸ سال قبل، یکشنبه ۲۸ شهریور ۱۳۹۵، ساعت ۱۵:۱۴
        از مطلب دستورات فوق استفاده کردم ولی با خطای Cannot read property 'msie' of undefined  مواجه شدم.
        • #
          ‫۸ سال قبل، یکشنبه ۲۸ شهریور ۱۳۹۵، ساعت ۱۶:۴۸
          برای اینکه خیلی از مسایل از چندسال قبل تا به امروز تغییر کردند و حذف شدند. برای بازگرداندن موارد حذف شده از jQuery Migrate استفاده کنید.
  • #
    ‫۵ سال و ۳ ماه قبل، شنبه ۱۸ خرداد ۱۳۹۸، ساعت ۱۸:۴۷
    با تشکر؛ یک دست سازی ی و ک  در EF Core به چه صورت هست؟ 
    • #
      ‫۵ سال و ۳ ماه قبل، شنبه ۱۸ خرداد ۱۳۹۸، ساعت ۱۸:۵۹
      نمونه‌اش در پروژه‌ی DNT Identity انجام شده: (^ و ^)
  • #
    ‫۴ سال و ۱۱ ماه قبل، دوشنبه ۸ مهر ۱۳۹۸، ساعت ۱۵:۵۴
    یک نکته‌ی تکمیلی:  طراحی یک Interceptor برای یک دست سازی ی و ک در EF Core

    یکی از ویژگی‌های جدید EF Core 3.0، بازگشت مجدد Interceptorهایی است که در این مطلب در مورد آن‌ها بحث شده‌است. اگر بخواهیم مطلب جاری را برای EF Core 3.0 بازنویسی کنیم، به کلاس زیر خواهیم رسید:
    using System;
    using System.Data;
    using System.Data.Common;
    using System.Threading;
    using System.Threading.Tasks;
    using DNTPersianUtils.Core; // dotnet add package DNTPersianUtils.Core 
    using Microsoft.EntityFrameworkCore.Diagnostics;
    
    namespace EFCore3Interceptors
    {
        public class PersianYeKeCommandInterceptor : DbCommandInterceptor
        {
            public override InterceptionResult<DbDataReader> ReaderExecuting(
                DbCommand command,
                CommandEventData eventData,
                InterceptionResult<DbDataReader> result)
            {
                ApplyCorrectYeKe(command);
                return result;
            }
    
            public override Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
                DbCommand command,
                CommandEventData eventData,
                InterceptionResult<DbDataReader> result,
                CancellationToken cancellationToken = new CancellationToken())
            {
                ApplyCorrectYeKe(command);
                return Task.FromResult(result);
            }
    
            public override InterceptionResult<int> NonQueryExecuting(
                DbCommand command, 
                CommandEventData eventData,
                InterceptionResult<int> result)
            {
                ApplyCorrectYeKe(command);
                return result;
            }
    
            public override Task<InterceptionResult<int>> NonQueryExecutingAsync(
                DbCommand command, 
                CommandEventData eventData, 
                InterceptionResult<int> result,
                CancellationToken cancellationToken = new CancellationToken())
            {
                ApplyCorrectYeKe(command);
                return Task.FromResult(result);
            }
    
            public override InterceptionResult<object> ScalarExecuting(
                DbCommand command, 
                CommandEventData eventData, 
                InterceptionResult<object> result)
            {
                ApplyCorrectYeKe(command);
                return result;
            }
    
            public override Task<InterceptionResult<object>> ScalarExecutingAsync(
                DbCommand command, 
                CommandEventData eventData, 
                InterceptionResult<object> result,
                CancellationToken cancellationToken = new CancellationToken())
            {
                ApplyCorrectYeKe(command);
                return Task.FromResult(result);
            }
    
            private static void ApplyCorrectYeKe(DbCommand command)
            {
                command.CommandText = command.CommandText.ApplyCorrectYeKe();
    
                foreach (DbParameter parameter in command.Parameters)
                {
                    switch (parameter.DbType)
                    {
                        case DbType.AnsiString:
                        case DbType.AnsiStringFixedLength:
                        case DbType.String:
                        case DbType.StringFixedLength:
                        case DbType.Xml:
                            parameter.Value =   parameter.Value is DBNull ? parameter.Value : parameter.Value.ToString().ApplyCorrectYeKe();
                            break;
                    }
                }
            }
        }
    }

    و روش استفاده و معرفی آن به سیستم توسط متد AddInterceptors، به صورت زیر است:
    namespace EFCore3Interceptors
    {
        public class BloggingContext : DbContext
        {
            // ...
     
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                if (!optionsBuilder.IsConfigured)
                {
                    optionsBuilder
                        .UseSqlServer("...")
                        .AddInterceptors(new PersianYeKeCommandInterceptor());
                }
            }
        }
    }
    • #
      ‫۴ سال و ۱۱ ماه قبل، دوشنبه ۸ مهر ۱۳۹۸، ساعت ۲۱:۴۹
      اصلاحیه برای SQLite

      EF Core یکسری از نوع‌ها را مانند bool و DateTime به صورت رشته‌ای در SQLite ذخیره می‌کند. به همین جهت سطر زیر در PersianYeKeCommandInterceptor فوق، سبب از کار افتادن این نوع‌ها می‌شود؛ چون برای مثال DateTime را هم تبدیل به رشته می‌کند (پیش از موعد و با فرمتی که مدنظر EF Core نیست):
      parameter.Value =   parameter.Value is DBNull ? parameter.Value : parameter.Value.ToString().ApplyCorrectYeKe();
      روش اصلاح شده‌ی آن به صورت زیر است که رشته‌ای بودن خود Value را هم بررسی می‌کند:
      if (!(parameter.Value is DBNull) && parameter.Value is string)
      {
         parameter.Value =  Convert.ToString(parameter.Value, CultureInfo.InvariantCulture).ApplyCorrectYeKe();
      }
  • #
    ‫۴ سال و ۱ ماه قبل، یکشنبه ۵ مرداد ۱۳۹۹، ساعت ۰۲:۵۰
    برای کلمه هایی مثل تئوری که باید به این شکل نوشته شود آیا راهی وجود داره که از این نوع کلمات صرف نظر کنه؟
    من از روش Interceptor  استفاده کردم
    یا اینکه باید به در متد saveChanges استفاده کنم؟