مطالب
انقیاد داده‌ها در WPF (بخش دوم)
در ادامه‌ی بخش اول از سری انقیاد داده‌ها در WPF، نحوه‌ی انقیاد داده‌ها در لیست را بررسی می‌کنیم.
• One Way Binding بخش اول
• INPC  بخش اول
• Tow Way Binding بخش اول
• List Binding  بخش دوم
• Element Binding بخش دوم
• Data Conversion بخش دوم

انقیاد در لیست List Binding
در ابتدا متدی با نام GetEmployees را با ساختار زیر، به کلاس Employee ایجاد شده‌ی در بخش اول این سری آموزشی، اضافه می‌کنیم: 
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee() { Name = "Mahdi", Title = "Manager" });
            employees.Add(new Employee() { Name = "Nima", Title = "Teacher" });
            employees.Add(new Employee() { Name = "Rahim", Title = "Assistant" });
            employees.Add(new Employee() { Name = "Saeed", Title = "Administrator" });
            return employees;
        }
در کد بالا از نوع داده‌ی جنریک ObservableCollection برای برگردان لیست کارمندان استفاده کردیم.
توجه ObservableCollection در فضای نام System.Collections.ObjectModel قرار دارد.

ObservableCollection  چیست؟
یک مجموعه‌ی پویا (Dynamic Collection) است که بر اثر عملیات‌هایی همچون به‌روز رسانی، حذف و ایجاد اشیاء در آن، یک اعلان صادر می‌شود و View از این اعلان مطلع می‌شود؛ شبیه به کاری که INotifyPropertyChanged انجام می‌دهد. 
در اینجا کلاس Employee با پیاده سازی اینترفیس INPC (معرفی شده در قسمت قبل) به یک کلاس Observable تبدیل شده است. بدین معنی که هر تغییری در وضعیت خصوصیات خود را اعلان می‌کند. با قرار دادن این شیء Observable در یک مجموعه که خود نیز از نوع Observable می‌باشد، از این پس هر تغییری در مجموعه نیز مستقیما View ما را تحت تاثیر قرار می‌دهد.
برای درک بهتر این موضوع در بخش markup، یک Combobox را قرار می‌دهیم:
<ComboBox Name="President" ItemsSource="{Binding}" FontSize="30"
                  Height="50"
                  Width="550">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding Title}" Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
 </ComboBox>
در کد xaml بالا چند نکته اهمیت دارد:
• در Tag مربوط به تعریف Combobox از طریق خصوصیت ItemSource اعلام کردیم که عملیات DataBinding اتفاق خواهد افتاد. پس Combobox ما به منبع داده‌ی پیش فرض در WPF که همان DataContext است مرتبط می‌شود. برای پر کردن منبع داده نیز همانند بخش قبل از طریق سازنده اقدام می‌کنیم.
 private ObservableCollection<Employee> employees;
        public MainWindow()
        {
            InitializeComponent();
            employees = Employee.GetEmployees();
            DataContext = employees;
        }
• برای نمایش عناصر در ComboBox، کار کمی متفاوت‌تر از عملیات انقیاد در یک TextBlock است. در کنترل‌هایی که قرار است لیستی از داده‌ها را پس از عملیات انقیاد نمایش دهند، می‌بایستی قالب نمایش داده‌ها مشخص شود.
حال باید نحوه‌ی نمایش و اعضایی از مجموعه که قرار است، در کنترل به نمایش در آیند مشخص شوند. در اینجا از Combobox.ItemTemplate برای مشخص کردن قالب و همچنین از DataTemplate، برای مشخص کردن داده‌های منتخب ما از مجموعه، استفاده شده است.
بعد از اجرای برنامه اطلاعات نام و عنوان، در Combobox نمایش داده می‌شوند. برای تست ویژگی Observable بودن مجموعه هم کافی است یک دکمه را بر روی صفحه قرار دهید و یک آیتم جدید را به لیست employees واقع در بخش CodeBehind اضافه کنید. مشاهده خواهید کرد که View ما با تغییر منبع داده، بطور اتوماتیک به‌روز می‌شود.

 انقیاد عناصر Element Binding 
در این بخش قصد داریم از یک کنترل به‌عنوان منبع داده استفاده کنیم. کنترل منتخب ما در این مثال یک Slider می‌باشد. در بخش markup کد زیر را اضافه می‌کنیم:
<Grid>
        <StackPanel Orientation="Horizontal">
            <Slider Name="mySlider" Minimum="0" Maximum="100"
                    Width="300"/>
            <TextBlock Margin="5" Text="{Binding Value,ElementName=mySlider}"/>
        </StackPanel>
    </Grid>
Value خصوصیتی است که مقدار Slider را برمی گرداند. پس از اجرای برنامه و حرکت ولوم اسلایدر مشاهده می‌کنید که مقدار جاری اسلایدر در textblock نمایش داده می‌شود.

Data Conversion 
برای درک بهتر مفهوم تبدیل داده در Data Binding با یک مثال کار را شروع می‌کنیم. به کلاس Employee موجود در بخش قبل، یک خصوصیت جدید را به نام تاریخ تولد، اضافه می‌کنیم:
public DateTime BornDate
        {
            get { return _bornDate; }
            set
            {
                _bornDate = value;
                OnPropertyChanged();
            }
        }
و متدی که لیستی از پرسنل را برای ما تولید می‌کرد، به شکل زیر بازنویسی می‌کنیم:
public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add(new Employee()
            { Name = "Mahdi", Title = "Manager", BornDate = DateTime.Parse("2008/8/8") });
            employees.Add(new Employee()
            { Name = "Nima", Title = "Teacher", BornDate = DateTime.Parse("2012/3/14") });
            employees.Add(new Employee()
            { Name = "Rahim", Title = "Assistant", BornDate = DateTime.Parse("2009/11/18") });
            employees.Add(new Employee()
            { Name = "Saeed", Title = "Administrator", BornDate = DateTime.Parse("2014/7/28") });
            return employees;
        }
حال قصد داریم اطلاعات را در یک ListBox، در بخش Markup نمایش دهیم:
<ListBox ItemsSource="{Binding}"
                 BorderThickness="1" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Title}" Width="100" Margin="5,0,0,0"/>
                        <TextBlock Text="{Binding BornDate}"  Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
 همانطور که قبلا اشاره شد، در نمایش لیست‌ها باید قالب و داده‌های مورد نظر برای نمایش، مشخص شوند.
پس از اجرا، خروجی برنامه به شکل زیر است:
 

 همه چیز همانطور که انتظار داشتیم به نمایش درآمد؛ اما اگر بخواهیم تاریخ میلادی، در زمان نمایش در View، بصورت شمسی نمایش داده شود، چطور؟
عملیات تبدیل داده یا همان Data Conversion در اینجا به کمک ما می‌آید.
به شکل زیر دقت کنید:

در زمانیکه در عملیات Data Binding نوع داده‌ی خصوصیت ما در Source (منبع داده) با نوع داده‌ی خصوصیت ما در target  (کنترل یا View) متفاوت است، به یک مبدل در حین Binding نیاز داریم. این کار را از طریق یک کلاس که اینترفیس IValueConvertor را پیاده سازی کرده است، انجام می‌دهیم.

  Converter به معنای مبدل است و اینجا وظیفه‌ی تبدیل مقدار گرفته شده‌ی از منبع داده را به نوع مورد نظر ما در View، دارد.
در مثال ذکر شده می‌خواهیم تاریخ میلادی موجود در منبع داده را به یک رشته (تاریخ شمسی) تبدیل کنیم و در View نمایش دهیم. (تبدیل نوع داده‌ی میلادی به رشته)
کلاسی را با نام DateConverter ایجاد می‌کنیم و اینترفیس IValueConverter را به شکل زیر پیاده سازی می‌کنیم:
public class DateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            PersianCalendar pc = new PersianCalendar();
            var persianDate = string.Format
                ($"{pc.GetYear(date)}/{pc.GetMonth(date)}/{pc.GetDayOfMonth(date)}");
            return persianDate;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
اینترفیس IValueConverter دو متد دارد:
• Convert جهت تبدیل اطلاعات از مبداء به مقصد 
• ConvertBack برای تبدیل اطلاعات از مقصد به مبداء
در متد Convert همانطور که می‌بینید مقدار تاریخ میلادی ارسال شده، به یک رشته تبدیل شده و بازگردانده می‌شود.

حال برای استفاده از این مبدل باید مراحل زیر انجام شود: 
• اضافه کردن فضای نام مبدل به بخش فضاهای نام در View؛ که در اینجا همان آدرس پروژه‌ی جاری است:
 xmlns:local="clr-namespace:DataConversion"
• ایجاد یک شیء از مبدل و اضافه کردن آن به بخش Resource‌ها در view جاری:
<Window.Resources>
        <local:DateConverter x:Key="MyConverter"/>
    </Window.Resources>
markup مربوط به Resource یک پارامتر می‌گیرد و آن هم Key  است. Key کلید آیتم موجود در دیکشنری Resource است. اینجا کلید را MyConverter تعریف کردیم تا در بخش بعدی به‌راحتی بتوانیم از طریق آن اطلاعات را از Resource بازیابی کنیم.
• ارجاع به Static Resource که در مرحله‌ی قبل مقدار دهی کردیم:
<TextBlock Text="{Binding BornDate,Converter={StaticResource MyConverter}}"  Margin="5,0,0,0"/>
پس از طی مراحل بالا، برنامه را مجددا اجرا کنید. اینبار خروجی به شکل زیر است:

مطالب
محدود کردن درخواست های Asp.Net Web Api بر اساس Client IP
در بسیاری از سناریو‌ها این موضوع مطرح می‌شود که سرویس‌های طراحی شده بر اساس Asp.Net Web Api، فقط به یک سری آی پی‌های مشخص سرویس دهند. برای مثال اگر Ip کلاینت در لیست کلاینت‌های دارای لایسنس خریداری شده بود، امکان استفاده از سرویس میسر باشد؛ در غیر این صورت خیر. بسته به نوع پیاده سازی سرویس‌های Web api، پیاده سازی این بخش کمی متفاوت خواهد شد. در طی این پست این موضوع را برای سه حالت IIs Host  و SelfHost و Owin Host بررسی می‌کنیم.
در اینجا قصد داریم حالتی را پیاده سازی نماییم که اگر درخواست جاری از سوی کلاینتی بود که Ip آن در لیست Ip‌های غیر مجاز قرار داشت، ادامه‌ی عملیات متوقف شود.

IIS Hosting:
حالت پیش فرض استفاده از سرویس‌های Web Api همین گزینه است؛ وابستگی مستقیم به System.Web . در مورد مزایا و معایب آن بحث نمی‌کنیم اما اگر این روش را انتخاب کردید تکه کد زیر این کار را برای ما انجام می‌دهد:
if (request.Properties.ContainsKey["MS_HttpContext"]) 
{
    var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper;
    if (ctx != null)
    {
        var ip = ctx.Request.UserHostAddress;       
    }
}
برای بدست آوردن شی HttpContext می‌توان آن را از لیست Properties‌های درخواست جاری به دست آورد. حال کد بالا را در قالب یک Extension Method در خواهیم آورد؛ به صورت زیر:

public static class HttpRequestMessageExtensions
{
    private const string HttpContext = "MS_HttpContext";

    public static string GetClientIpAddress(this HttpRequestMessage request)
    {
        if (request.Properties.ContainsKey(HttpContext))
        {
            dynamic ctx = request.Properties[HttpContext];
            if (ctx != null)
            {
                return ctx.Request.UserHostAddress;
            }
        }      
        return null;
    }
}

Self Hosting:

در حالت Self Host می‌توان عملیات بالا را با استفاده از خاصیت RemoteEndpointMessageProperty  انجام داد که تقریبا شبیه به حالت Web Host است. مقدار این خاصیت نیز در شی جاری HttpRequestMessage وجود دارد. فقط باید به صورت زیر آن را واکشی نماییم:
if (request.Properties.ContainsKey[RemoteEndpointMessageProperty.Name]) 
{
    var remote = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

    if (remote != null)
    {
        var ip = remote.Address;        
    }
}
خاصیت RemoteEndpointMessageProperty  به تمامی درخواست‌ها وارده در سرویس‌های WCF چه در حالت استفاده از Http و چه در حالت Tcp اضافه می‌شود و در اسمبلی System.ServiceModel نیز می‌باشد. ار آنجا که Web Api از هسته‌ی WCF استفاده می‌کند (WCF Core) در نتیجه می‌توان از این روش استفاده نمود. فقط باید اسمبلی System.ServiceModel را به پروژه‌ی خود اضافه نمایید.

ترکیب حالت‌های قبلی:
اگر می‌خواهید کد‌های نوشته شده شما وابستگی به نوع هاست پروژه نداشته باشد، یا به معنای دیگر، در هر دو حالت به درستی کار کند می‌توانید به روش زیر حالت‌های قبلی را با هم ترکیب کنید. 
»در این صورت دیگر نیازی به اضافه کردن اسمبلی System.ServiceModel  نیست.
public static class HttpRequestMessageExtensions
{
    private const string HttpContext = "MS_HttpContext";
    private const string RemoteEndpointMessage = "System.ServiceModel.Channels.RemoteEndpointMessageProperty";

    public static string GetClientIpAddress(this HttpRequestMessage request)
    {
        if (request.Properties.ContainsKey(HttpContext))
        {
            dynamic ctx = request.Properties[HttpContext];
            if (ctx != null)
            {
                return ctx.Request.UserHostAddress;
            }
        }

        if (request.Properties.ContainsKey(RemoteEndpointMessage))
        {
            dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];
            if (remoteEndpoint != null)
            {
                return remoteEndpoint.Address;
            }
        }

        return null;
    }
}
مرحله بعدی طراحی یک DelegatingHandler جهت استفاده از IP به دست آمده است .
 
public class MyHandler : DelegatingHandler
{
 private readonly HashSet<string> deniedIps;

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {

      if (deniedIps.Contains(request.GetClientIpAddress()))
        {            
            return Task.FromResult( new HttpResponseMessage( HttpStatusCode.Unauthorized ) );
        }

        return base.SendAsync(request, cancellationToken);
    }
}

Owin :

زمانی که از Owin   برای هاست سرویس‌های Web Api خود استفاده می‌کنید کمی روال انجام کار متفاوت خواهد شد. در این مورد نیز می‌توانید از DelegatingHandler‌ها استفاده کنید. معرفی DelegatingHandler طراحی شده به Asp.Net PipeLine به صورت زیر خواهد بود:
public class Startup
    {
        public void Configuration( IAppBuilder appBuilder )
        {     
            var config = new HttpConfiguration();
                 
            var routeHandler = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher( config ), new DelegatingHandler[] 
            {
                new MyHandler(),
            } );
      
            config.Routes.MapHttpRoute(
                name: "Default",
                routeTemplate: "{controller}/{action}",
                defaults: null,
                constraints: null,
                handler: routeHandler
            );            
           
            config.EnsureInitialized();
         
            appBuilder.UseWebApi( config );         
        }
    }
اما نکته ای را که باید به آن دقت داشت، این است که یکی از مزایای استفاده از Owin، یکپارچه سازی عملیات هاستینگ قسمت‌های مختلف برنامه است. برای مثال ممکن است قصد داشته باشید که بخش هایی که با Asp.Net SignalR نیز پیاده سازی شده‌اند، قابلیت استفاده از کد‌های بالا را داشته باشند. در این صورت بهتر است کل عملیات بالا در قالب یک Owin Middleware عمل نماید تا تمام قسمت‌های هاست شده‌ی برنامه از کد‌های بالا استفاده نمایند؛ به صورت زیر:
public class IpMiddleware : OwinMiddleware
{
    private readonly HashSet<string> _deniedIps;

    public IpMiddleware(OwinMiddleware next, HashSet<string> deniedIps) :
        base(next)
    {
        _deniedIps = deniedIps;
    }

    public override async Task Invoke(OwinRequest request, OwinResponse response)
    {
        var ipAddress = (string)request.Environment["server.RemoteIpAddress"];

        if (_deniedIps.Contains(ipAddress))
        {
            response.StatusCode = 403;
            return;
        }

        await Next.Invoke(request, response);
    }
}
برای نوشتن یک Owin Middleware کافیست کلاس مورد نظر از کلاس OwinMiddleware ارث ببرد و متد Invoke را Override کنید.  لیست Ip‌های غیر مجاز، از طریق سازنده در اختیار Middleware قرار می‌گیرد. اگر درخواست مجاز بود از طریق دستور Next.Invoke(request,response) کنترل برنامه به مرحله بعدی منتقل می‌شود در غیر صورت عملیات با کد 403 متوقف می‌شود.
در نهایت برای معرفی این Middleware طراحی شده به Application، مراحل زیر را انجام دهید.
public class Startup
    {
        public void Configuration( IAppBuilder appBuilder )
        {       
           var config = new HttpConfiguration();                      

           var deniedIps = new HashSet<string> {"192.168.0.100", "192.168.0.101"}; 
          
app.Use(typeof(IpMiddleware), deniedIps); appBuilder.UseWebApi( config ); } }
مطالب
بازیابی پایگاه داده (database recovery)

در این مقاله آموزشی که یکی دیگر از سری مقالات آموزشی اصول و مبانی پایگاه داده پیشرفته می‌باشد، قصد داریم به یکی دیگر از مقوله‌های مهم در طراحی سیستم‌های مدیریت پایگاه داده (DBMS) بپردازیم. همانطور که در مباحث قبلی  بیان کردیم یکی از وظایف سیستم مدیریت پایگاه داده، حفظ سازگاری(consistency) داده‌ها می‌باشد. برای مثال یکی از راهکار هایی که برای این منظور ارائه می‌دهد انجام عملیات در قالب تراکنش هاست که در مبحث مربوط به تراکنش ها مفصل در مورد آن بحث کردیم. با این حال گاهی خطا‌ها و شکست هایی (failure) در حین عملیات ممکن است پیش بیاید که منجر به خروج سیستم از وضعیت سازگار خود گردد. بعنوان مثال ممکن است سخت افزار سیستم دچار مشکل شود، مثلا دیسک از کار بیفتد (disk crash) یا آنکه برق قطع شود. خطاهای نرم افزاری نیز می‌توانند جزو موارد شکست و خرابی بحساب آیند که خطای منطق برنامه (logic) از این نمونه می‌باشد. در چنین شرایطی بحثی مطرح می‌شود تحت عنوان بازیابی  (recovery)  و ترمیم پایگاه داده که در این مقاله قصد داریم در مورد آن صحبت کنیم. بنا به تعریف بازیابی به معنای بازگرداندن یک پایگاه داده به وضعیت سازگار گذشته خود، بعد از وقوع یک شکست یا خرابی است. توجه داشته باشید که اهمیت بازیابی و ترمیم پایگاه داده تا آنجایی است که حدود 10 درصد از سیستم‌های مدیریت پایگاه داده را به خود اختصاص می‌دهند. 

آنچه که در اینجا در مورد آن صحبت خواهیم کرد بازیابی بصورت نرم افزاری است که از آن تحت عنوان fail soft نام برده می‌شود. دقت داشته باشید در بیشتر مواقع می‌توان از طریق نرم افزاری عمل بازیابی را انجام داد، اما در کنار راهکار‌های نرم افزاری باید حتما اقدامات سخت افزاری ضروری نیز پیش بینی شود. بعنوان مثال گرفتن نسخه‌های پشتیبان یک امر ضروری در سیستم‌های اطلاعاتی است. چرا که گاهی اوقات خرابی‌های فیزیکی باعث از دست رفتن تمامی اطلاعات می‌گردند که در این صورت نسخه‌های پشتیبان می‌توانند به کمک آیند و با کمک آنها سیستم را مجدد بازیابی کرد. در شکل زیر نمونه ای از روش‌های پشتیبان گیری بنام mirroring نشان داده شده است که روش رایجی در سیستم‌های بانک اطلاعاتی بشمار می‌رود. همانطور که در شکل نشان داده شده است در کنار نسخه اصلی (DISK)، نسخه(MIRROR) آن  قرار داده شده است. این دو نسخه کاملا مشابه یکدیگرند و هر عملی که در DICK انجام می‌شود در MIRROR ان نیز اعمال می‌شود تا در مواقع خرابی DISK بتوان از نسخه MIRROR استفاده نمود. 

در شکل زیر نمونه بسیار ساده از نحوه لاگ کردن در حین اجرای تراکنش‌ها را مشاهده می‌کنید. 

نیازمندی‌های اصلی در بازیابی پایگاه داده

برای آنکه وارد بحث اصلی شویم باید بگویم در یک نگاه کلی می‌توان گفت که ساختار زیر سیستم بازیابی پایگاه داده بر پایه سه عملیات استوار است که عبارتند از  log ،  redo  و  undo . برای آنکه بتوان در هنگام رخ دادن خطا عمل ترمیم و بازیابی را انجام داد، سیستم پایگاه داده با استفاده از مکانیزم لاگ کردن(logging) خود تمامی عملیاتی را که در پایگاه داده رخ می‌دهد و بنحوی منجر به تغییر وضعیت ان می‌گردد را در جایی ثبت و نگهداری می‌کند. اهمیت لاگ کردن وقایع بسیار بالاست، چرا که پس از رخ دادن شکست در سیستم ملاک ما برای بازیابی و ترمیم فایل‌های لاگ  (log files)  می باشند.

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

در کنار لاگ فایل ها، که مبنای کار در بازیابی هستند، فایل دیگری نیز در سیستم وجود دارد که به DBMS در بازیابی کمک می‌کند. این فایل  raster file  نام دارد که در بخش‌های بعدی این مقاله در مورد آن و کارایی آن بیشتر صحبت خواهیم نمود.

Recovery Manager

مسئولیت انجام بازیابی بصورت نرم افزاری (fail soft) بر عهده زیر سیستمی از DBMS بنام مدیر بازیابی (recovery manager) می باشد و همانطور که اشاره شد این زیر سیستم چیزی در حدود 10 در صد DBMSرا به خود اختصاص می‌دهد. برای آنکه این زیر سیستم بتواند مسئولیت خود را بنحو احسن انجام دهد بطوری که عمل بازیابی بدون نقص و قابل اعتماد باشد، باید به نکاتی توجه نمود. اولین نکته اینست که در لاگ کردن و همچنین خواندن لاگ فایل به جهت بازیابی و ترمیم پایگاه داده هیچ تراکنشی نباید از قلم بیفتد. تمامی تراکنش‌ها در طول حیات سیستم باید لاگ شود تا بازیابی ما قابل اعتماد و بدون نقص باشد. نکته دوم اینست که اگر تصمیم به اجرای مجدد (redo) تراکنشی گرفته شد، طوری باید عمل Redo انجام شود که بلحاظ منطقی آن تراکنش یک بار انجام شود و تاثیرش یکبار بر دیتابیس اعمال گردد. بعنوان مثال فرض کنید که در طی یک تراکنش مبلغ یک میلیون تومان به حساب شخصی واریز می‌شود. مدتی بعد از اجرای و تمکیل تراکنش سیستم دچار مشکل می‌شود و مجبور به انجام بازیابی می‌شویم. در حین عمل بازیابی سیستم مدیریت بازیابی و ترمیم تصمیم به اجرای مجدد تراکنش مذکور می‌گیرد. در اینجا سیستم نباید مجدد یک میلیون تومان دیگر به حساب ان شخص واریز کند. چرا که در این صورت موجودی حساب فرد دو میلیون تومان خواهد شد که این اشتباه است. سیستم باید طوری عمل کند که پس از انجام مجدد تراکنش باز هم موجودی همان یک میلیون تومان باشد. یعنی مثلا ابتدا یک میلیون کسر و سپس یک میلیون به آن اضافه کند. این مسئله نکته بسیار مهمی است که طراحان DBMS باید حتما آن را مد نظر قرار دهند.

لاگ کردن:

همانطور که گفته شد هر تغییری که در پایگاه داده رخ می‌دهد باید لاگ شود. لاگ کردن به این معنی است که هر گونه عملیاتی که در پایگاه داده انجام می‌شود در فایل هایی به نام فایل لاگ (log file) ذخیره شود. توجه داشته باشید  لاگ فایل‌ها در بسیاری از سیستم‌های نرم افزاری دیگر نیز استفاده می‌شود. بعنوان مثال در سیستم عامل ما انواع مختلفی فایل لاگ داریم. بعنوان نمونه یک فراخوانی سیستمی (system call) که در سیستم عامل توسط کاربر انجام می‌شود در فایلی مخصوص لاگ می‌شود. یکی از کاربرد این لاگ فایل شناسایی کاربران بد و خرابکار (malicious users) می تواند باشد که کارهای تحقیقاتی زیادی هم در این رابطه انجام شده و میشود. بدین صورت که می‌توان با بررسی این فایل لاگ و آنالیز فراخوانی‌های یک کاربر بدنبال فراخوانی هایی غیر عادی گشت و از این طریق تشخیص داد که کاربر بدنبال خرابکاری بوده یا خیر. مشابه چنین فایل هایی در DBMS نیز وجود دارد که هدف نهایی تمامی انها حفظ صحت، سازگاری و امنیت اطلاعات می‌باشد.

حال ببینیم در لاگ فایل مربوط به بازیابی اطلاعات چه چیز هایی نوشته می‌شود. در طول حیات پایگاه داده عملیات بسیار گوناگونی انجام می‌گیرد که جزئیات تمامی آنها باید لاگ شود. بعنوان مثال هنگامی که رکوردی درج می‌شود در لاگ فایل باید مشخص شود که در چه زمانی، توسط چه کاربری چه رکوردی، با چه شناسه ای به کدام جدول از دیتابیس اضافه شد. یا اینکه در موقع حذف باید مشخص شود چه رکوردی از چه جدولی حذف شده است. در هنگام بروز رسانی (update) باید علاوه بر مواردی که در درج لاگ می‌کنیم نام فیلد ویرایش شده، مقدار قبلی و مقدار جدید آن نیز مشخص شود. تمامی عملیات ریز لاگ می‌شوند و هیچ عملی نباید از قلم بیفتد. بنابراین فایل لاگ با سرعت زیاد بزرگ خواهد و اندازه دیتابیس نیز افزایش خواهد یافت. این افزایش اندازه مشکل ساز می‌تواند باشد. چراکه معمولا فضایی که ما بر روی دیسک به دیتابیس اختصاص می‌دهیم فضایی محدود است. بهمین دلیل به لحاظ فیزیکی نمی‌توان فایل لاگی با اندازه نامحدود داشت. این در حالی است که چنین فایل هایی باید نامحدود باشند تا همه چیز را در خود ثبت نمایند. برای پیاده سازی ظرفیت نامحدود به لحاظ منطقی یکی از روش‌ها پیاده سازی فایل‌های حلقه ای(circular) است. بدین صورت که هنگامی که سیستم به انتهای فایل لاگ می‌رسد مجددا به ابتدا آن بر می‌گردد و از ابتدا شروع به نوشتن می‌کند. البته چنین ساختار هایی بدون اشکال نیستند. چرا که پس از رسیدن به انتهای فایل و شروع مجدد از ابتدا ما برخی از تراکنش‌های گذشته را از دست خواهیم داد. این مسئله یکی از دلایلی است که بر اساس آن پیشنهاد می‌شود تا جایی که امکان دارد تراکنش‌ها را کوچک پیاده سازی کنیم. گاهی اوقات بر روی لاگ فایل عمل فشرده سازی را نیز انجام می‌دهند. البته فشرده سازی بمعنای رایج ان مطرح نیست. بلکه منظور از فشرده سازی آنست که رکورد هایی که غیر ضروری هستند را حذف کنیم. بعنوان مثال فرض کنید رکوردی را از 50 به 60 تغییر داده ایم. مجددا همان رکورد را از 60 به 70 تغییر می‌دهیم. در این صورت برای این عملیات دو رکورد در فایل لاگ ثبت شده است که در هنگام فشرده سازی در صورت امکان می‌توان ان دو را به یک رکورد تبدیل نمود (تغییر از 50 به 70 را بجای ان دو لاگ کرد). بعنوان مثال دیگر فرض کنید تراکنشی در گذشته دور انجام شده است و با موفقیت کامیت شده است. می‌توان رکورد‌های لاگ مربوط به این تراکنش را نیز بنا به شرایط حذف کرد.

دقت داشته باشید که ما عملیاتی مانند عملیات محاسباتی را در این لاگ فایل ثبت نمی‌کنیم. بعنوان مثال اگر دو فیلد با هم باید جمع شوند و نتیجه در فیلدی باید بروز گردد، جمع دو فیل را در سیستم لاگ نمی‌کنیم بلکه تنها مقدار نهایی ویرایش شده را ثبت می‌کنیم. چرا که عملیات محاسباتی در بازیابی ضروری نیستند و ثبت انها تنها باعث بزرگ شدن فایل می‌شود.

در برخی از سیستم‌های حساس، ممکن است برای فایل‌های لاگ هم یک کپی تهیه کنند تا در صورت بروز خطا در لاگ فایل بتوان آن را نیز بازیابی نمود.

انواع رکورد‌های لاگ فایل :

در فایل لاگ رکورد‌های مختلفی  ممکن است درج شود که در این جا به چند نمونه از انها اشاره می‌کنیم:

  • [start-transaction, T]
  • [write-item, T, X, old-value, new-value]
  • [read-item, T, X]
  • [commit, T]

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

در شکل زیر نمونه بسیار ساده از نحوه لاگ کردن در حین اجرای تراکنش‌ها را مشاهده می‌کنید.

در  این شکل نکته ای وجود دارد که به آن اشاره ای می‌کنیم. همانطور که میبینید در شکل از اصطلاحimmediate update استفاده شده است. در برخی از سیستم‌ها تغییرات تراکنش‌ها بصورت فوری اعمال میشوند که اصطلاحا می‌گوییم immediate updates دارند. در مقابل این اصطلاح ما deffered را داریم. در این مدل تغییرات در انتهای کار اعمال می‌شوند (در زمان commit). 

Write-Ahead Log (WAL) :

بر اساس آنچه تابحال گفته شد هر تغییری در پایگاه داده شامل دو عمل می‌شود. یکی انجام تغییر (اجرای تراکنش) و دیگری ثبت آن در لاگ فایل. حال سوالی که ممکن است مطرح شود اینست  که کدامیک از این دو کار بر دیگری تقدم دارد؟ آیا اول تراکنش را باید اجرا کرد و سپس لاگ آن را نوشت و یا برعکس باید عمل کرد. یعنی پیش از هر تراکنشی ابتدا باید لاگ آن را ثبت کرد و سپس تراکنش را اجرا نمود. بر همین اساس سیاستی تعریف می‌شود بنام سیاست write-ahead log یا WAL که سوال دوم را تایید می‌کند. یعنی می‌گوید هنگامی که قرار است عملی در پایگاه داده صورت گیرد ابتدا باید ان عمل بطور کامل لاگ شود و سپس آن را اجرا نمود. این سیاست هدفی را دنبال می‌کند. 

پیش از آنکه هدف این سیاست را توضیح دهیم لازم است نکته ای در مورد عملیات redo و  undo بیان شود. شما با این دو عملیات در برنامه‌های مختلفی مانند آفیس، فتوشاپ و غیره آشنایی دارید. اما توجه داشته باشید که در DBMS این دو عملیات از پیچیدگی بیشتری برخوردار می‌باشند. اصطلاحا در پایگاه داده گفته میشود که عملیات redo و undo باید idempotent باشند. معنی idempotent بودن اینست که اگر قرار است تراکنشی در پایگاه داده undo شود، اگر بار‌ها و بارها عمل undo را بر روی آن تراکنش انجام دهیم مانند این باشد این عمل را تنها یکبار انجام داده ایم. در مورد redo نیز این مسئله صادق است. 

در تعریف idempotent بودن ویژگی‌های دیگری نیز وجود دارد. بعنوان مثال گفته می‌شود undo بر روی عملی که هنوز انجام نشده هیچ تاثیری نخواهد داشت. این مسئله یکی از دلایل اهمیت استفاده از سیاستWAL را بیان می‌کند. بعنوان مثال فرض کنید می‌خواهیم رکوردی را در جدولی درج کنیم. همانطور که گفتیم دو روش برای این منظور وجود  دارد. در روش اول ابتدا رکورد را در جدول مورد نظر درج می‌کنیم و سپس لاگ آن را می‌نویسیم. در این صورت اگر پس از درج رکورد سیستم با مشکل مواجه شود و مجبور به انجام عمل بازیابی شویم، بدلیل آنکه برای بازیابی بر اساس لاگ فایل عمل می‌کنیم و برای درج آن رکورد لاگی در سیستم ثبت نشده است، آن عمل را از دست می‌دهیم. در نتیجه بازیابی بطور کامل نمی‌تواند سیستم را ترمیم نماید. چراکه درج صورت گرفته اما لاگی برای آن ثبت نشده است. در روش دوم فرض کنید بر اساس سیاست WAL عمل می‌کنیم. ابتدا لاگ مربوط به درج رکورد را می‌نویسم. سپس پیش از آنکه عمل درج را انجام دهیم سیستم crash می کند و مجبور به بازیابی می‌شویم. دراین صورت هنگامی که Recovery Manager به رکورد مربوط به عمل درج در لاگ فایل می‌رسد یا باید آن را redo کند و یا undo (بعدا می‌گوییم بر چه اساس تصمیم گیری می‌کند). اگر تصمیم به undo کردن بگیرد بدلیل ویژگی گفته شده، عمل undo بر روی عملی که انجام نشده است هیچ تاثیری در پایگاه داده نخواهد گذاشت. اگر عمل redo را بخواهد انجام دهد نیز بدلیل آنکه لاگ مربوط به عمل درج در سیستم ثبت شده بدون هیچ مشکلی این عمل مجددا انجام می‌گیرد. بنابراین بر خلاف روش قبل هیچ تراکنشی را از دست نمی‌دهیم و سیستم بطور کامل بازیابی و ترمیم می‌شود. به این دلیل است که توصیه می‌شود در طراحیDBMS ها سیاست WAL بکار گیری شود. 

نکته بسیار مهمی که در اینجا ذکر آن ضروری بنظر می‌رسد اینست که در هنگام لاگ کردن تراکنش ها، علاوه بر آنکه خود تراکنش لاگ می‌شود و این لاگ‌ها نیز در فایل فیزیکی باید نوشته شوند، عملیات لازم برای Redo کردن و یا undo کردن آن نیز لاگ می‌شود تا سیستم در هنگام بازیابی بداند که چه کاری برایredo و undo کردن باید انجام دهد. توجه داشته باشید در این سیاست، COMMIT تراکنشی انجام نمی‌شود مگر انکه تمامی لاگ‌های مربوط به عملیات redo و undo آن تراکنش در لاگ فایل فیزیکی ثبت شود. 

قرار دادن  checkpoint  در لاگ فایل:

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


نکته بسیار مهمی که در مورد checkpoint ها وجود دارد اینست که آنها چیزی فراتر از یک علامت در لاگ فایل هستند. هنگامی که DBMS به زمانی میرسد که باید در لاگ فایل checkpoint قرار دهد، باید اعمال مهمی ابتدا انجام شود.  اولین کاری که در زمان checkpoint باید صورت بگیرد اینست که رکورد هایی از لاگ فایل که هنوز به دیسک منتقل نشده اند، بر روی لاگ فایل فیزیکی بر روی دیسک نوشته شوند. به این عمل flush کردن لاگ رکورد‌ها نیز گفته می‌شود. دومین کاری که در این زمان باید صورت بگیرید اینست که رکوردی خاص بعنوان checkpoint record در لاگ فایل درج گردد. در این رکورد در واقع تصویری از وضعیت دیتابیس در زمان checkpoint را نگهداری می‌کنیم. دقت داشته باشید که در زمان checkpoint،DBMS برای یک لحظه تمامی تراکنش‌های در حال اجرا را متوقف می‌کند و لیستی از این تراکنش‌ها را در رکورد مربوط به checkpoint نگهداری می‌کند تا در زمان بازیابی بداند چه تراکنش هایی در آن زمان هنوز commit نشده و تاثیرشان به پایگاه داده اعمال نشده است. سومین کاری که در این لحظه بایدا انجام گیرد ایسنت که اگر داده هایی از پایگاه داده هستند که عملیات مربوط به آنها COMMIT شده اند اما هنوز به دیسک منتقل نشده اند بر روی دیسک نوشته شوند.آخرین کاری که باید انجام شود اینست که آدرس رکورد مربوط به checkpoint در فایلی بنام raster file ذخیره شود. علت این کار آنست که در هنگام بازیابی بتوانیم بسرعت آدرس آخرین checkpoint را بدست آوریم.


عمل  UNDO :

در اینجا قصد داریم معنی و مفهوم عمل undo را بر روی انواع مختلف تراکنش‌ها را بیان کنیم.

  • هنگامی که می‌گوییم یک عمل بروز رسانی (update) را می‌خواهیم undo کنیم منظور اینست که مقدار قبلی فیلد مورد نظر را به جای مقدار جدید آن قرار دهیم.
  • هنگامی که عمل undo را بر روی عملیات حذف می‌خواهیم انجام دهیم منظور اینست که مقدار قبلی جدول (رکورد حذف شده) را مجددا باز گردانیم.
  • هنگامی که عمل undo را بر روی عملیات درج (insert) می خواهیم انجام دهیم منظور این است که مقدار جدید درج شده در جدول را حذف کنیم.
البته این موارد ممکن است کمی بدیهی بنظر برسد اما برای کامل‌تر شدن این مقاله آموزشی بهتر دانستیم که اشاره ای به آنها کرده باشیم. 

انجام عمل بازیابی و ترمیم :

تا اینجا مقدمات لازم برای ترمیم پایگاه داده را گفتیم. حال می‌خواهیم بسراغ چگونگی انجام عمل ترمیم برویم. هنگامی که می‌خواهیم پایگاه داده ای را ترمیم کنیم اولین کاری که باید انجام گیرد اینست که بوسیله raster file، آدرس آخرین checkpoint لاگ فایل را پیدا کنیم. سپس فایل لاگ را از نقطه checkpoint  به پایین اسکن می‌کنیم. در هنگام اسکن کردن باید تراکنش‌ها را به دو گروه تقکیک کنیم، تراکنش هایی که باید undo شوند و تراکنش هایی که باید عمل redo بر روی انها انجام گیرد. علت این کار اینست که در هنگام undo کردن از انتهای لاگ فایل به سمت بالا باید حرکت کنیم و برای Redo کردن بصورت عکس، از بالا به سمت پایین می‌آییم. بنابراین جهت حرکت در لاگ فایل برای این دو عمل متفاوت است. بهمین دلیل باید ابتدا تراکنش‌ها تفکیک شوند. اما چگونه این تفکیک صورت می‌گیرد؟

  

هنگام اسکن کردن (از نقطه checkpoint به سمت انتهای لاگ فایل (لحظه خطا) )، هر تراکنشی که رکورد لاگ مربوط به commit آن دیده شود باید در گروه redo قرار گیرد. بعبارت دیگر تراکنش هایی که در این فاصله commit شده اند را در گروه redo قرار می‌دهیم. در مقابل هر تراکنشی که commit آن دیده نشود (commit نشده اند) باید undo  شود. باز هم تاکید می‌کنیم که این عمل تنها در فاصله بین آخرینcheckpoint تا لحظه وقوع خطا انجام می‌شود.

  

  دقت داشته باشید که در شروع اسکن کردن اولین رکوردی که خوانده می‌شود رکورد مربوط بهcheckpoint می باشد که حاوی تراکنش هایی است که در زمان checkpoint در حال انجام بوده اند، یعنی هنوز commit نشده اند. بنابراین تمامی این تراکنش‌ها را ابتدا در گروه تراکنش هایی که باید undo شوند قرار می‌دهیم. بمرور که عمل اسکن را ادامه می‌دهیم اگر به تراکنشی رسیدیم که رکورد مربوط به شروع ان ثبت شده باشد، باید آن تراکنش را در لیست undo قرار دهیم. تراکنش هایی که commit آنها دیده شود را نیز باید از گروه undo حذف و به گروه Redo اضافه نماییم. پس از خاتمه عمل اسکن ما دو لیست از تراکنش‌ها داریم. یکی تراکنش هایی که باید Redo شوند و دیگری  آنهایی که باید undo  گردند. 


پس از مشخص شدن دو لیست Redo و Undo، باید دو کار دیگر انجام شود. اولین کار اینست که تراکنش هایی که باید undo شوند را از پایین به بالا undo کنیم. یکی از دلایل اینکه ابتدا عملیات undo را انجام می‌دهیم ایسنت هنگامی که تراکنش ها commit نشده اند، قفل هایی را که بر روی منابع پایگاه داده زده اند هنوز آزاد نکرده اند. با عمل undo کردن این قفل‌ها را آزاد می‌کنیم و بدین وسیله کمک می‌کنیم تا درجه همروندی پایگاه داده پایین نیاید. پس از خاتمه عملیات undo، به نقطه checkpoint می رسیم. در این لحظه مانند اینست که هیچ تراکنشی در سیستم وجود ندارد. حالا بر اساس لیست redo از بالا یعنی نقطهcheckpoint به سمت پایین فایل لاگ حرکت می‌کنیم و تراکنش‌های موجود در لیست  redo را مجدد اجرا می‌کنیم. پس از خاتمه این گام نیز عملیات بازیابی خاتمه می‌یابد می‌توان گفت سیستم به وضعیت پایدار قبلی خود باز گشسته است.

  

برای روشن‌تر شدن موضوع به شکل زیر توجه کنید. در این شکل نقطه Tf زمان رخ دادن خطا را در پایگاه داده نشان می‌دهد. اولین کاری که برای بازیابی باید انجام گیرد، همانطور که گفته شده اینست که آدرس مربوط به زمان checkpoint (Tc) از raster file خوانده شود. پس از این کار از لحظه Tc به سمت Tf شروع به اسکن کردن لاگ فایل می‌کنیم. بدلیل آنکه در زمان Tc دو تراکنش T2 و T3 در حال اجرا بودند (و نام آنها در checkpoint record نیز ثبت شده است)، این دو تراکنش را در لیست redo قرار می‌دهیم. سپس عمل اسکن را به سمت پایین ادامه می‌دهیم. در حین اسکن کردن ابتدا به رکورد start trasnactionمربوط به تراکنش T4 می رسیم. بهمین دلیل این تراکنش را به لیست undo ها اضافه می‌کنیم. پس از آن به commit تراکنش T2 می رسیم. همانطور که گفته شد باید T2 را از لیست undo ها خارج و به یست تراکنش هایی که باید redo شوند اضافه گردد. سپس به تراکنش T5 می رسیم که تازه آغاز شده است. ان را نیز در گروه undo قرار می‌دهیم. بعد از ان رکورد مربوط به commit تراکنش T4 دیده می‌شود و ان را از لیست undo حذف و لیست redo اضافه می‌کنی. اسکن را ادامه می‌دهیم تا به نقطه Tf می رسیم. در ان لحظه لیست undo ها شامل دو تراکنش T3 و T5 و لیست Redo ها شامل تراکنش های T2 و T4 می باشند. در مورد تراکنش T1 نیز چون پیش از لحظه Tc کامیت شده است عملی صورت نمی‌گیرد. 


موفق و پیروز باشید

مطالب
بهینه سازی سرعت یافت ویوها با سفارشی سازی Lookup Caching در Razor View Engine
 در این مقاله سعی داریم تا سرعت یافت و جستجوی View‌های متناظر با هر اکشن را در View Engine، با پیاده سازی قابلیت Caching نتیجه یافت آدرس فیزیکی view‌ها در درخواست‌های متوالی، افزایش دهیم تا عملا بازده سیستم را تا حدودی بهبود ببخشیم.

طی مطالعاتی که بنده بر روی سورس MVC داشتم، به صورت پیش فرض، در زمانیکه پروژه در حالت Release اجرا می‌شود، نتیجه حاصل از یافت آدرس فیزیکی ویو‌های متناظر با اکشن متدها در Application cache ذخیره می‌شود (HttpContext.Cache). این امر سبب اجتناب از عمل یافت چند باره بر روی آدرس فیزیکی ویو‌ها در درخواست‌های متوالی ارسال شده برای رندر یک ویو خواهد شد.

 نکته ای که وجود دارد این هست که علاوه بر مفید بودن این امر و بهبود سرعت در درخواست‌های متوالی برای اکشن متد‌ها، این عمل با توجه به مشاهدات بنده از سورس MVC علاوه بر مفید بودن، تا حدودی هزینه بر هم هست و هزینه‌ای که متوجه سیستم می‌شود شامل مسائل مدیریت توکار حافظه کش توسط MVC است که مسائلی مانند سیاستهای مدیریت زمان انقضاء مداخل موجود در حافظه‌ی کش اختصاص داده شده به Lookup Cahching و  مدیریت مسائل thread-safe و ... را شامل می‌شود.

همانطور که می‌دانید، معمولا تعداد ویو‌ها اینقدر زیاد نیست که Caching نتایج یافت مسیر فیزیکی view ها، حجم زیادی از حافظه Ram را اشغال کند پس با این وجود به نظر می‌رسد که اشغال کردن این میزان اندک از حافظه در مقابل بهبود سرعت، قابل چشم پوشی است و سیاست‌های توکار نامبرده فقط عملا تاثیر منفی در روند Lookup Caching پیشفرض MVC خواهند گذاشت. برای جلوگیری از تاثیرات منفی سیاست‌های نامبرده و عملا بهبود سرعت Caching نتایج Lookup آدرس فیزیکی ویو‌ها میتوانیم یک لایه Caching سطح بالاتر به View Engine اضافه کنیم .

خوشبختانه تمامی View Engine‌های MVC شامل Web Forms  و Razor از کلاس VirtualPathProviderViewEngine مشتق شده‌اند که نکته مثبت که توسعه Caching اختصاصی نامبرده را برای ما مقدور می‌کند. در اینجا خاصیت ( Property ) قابل تنظیم ViewLocationCache از نوع IViewLocationCache هست .

بنابراین ما یک کلاس جدید ایجاد کرده و از اینترفیس IViewLocationCache مشتق میکنیم تا به صورت دلخواه بتوانیم اعضای این اینترفیس را پیاده سازی کنیم .

خوب؛ بنابر این اوصاف، من کلاس یاد شده را به شکل زیر پیاده سازی کردم :
    public class CustomViewCache : IViewLocationCache
    {

        private readonly static string s_key = "_customLookupCach" + Guid.NewGuid().ToString();
        private readonly IViewLocationCache _cache;

        public CustomViewCache(IViewLocationCache cache)
        {
            _cache = cache;
        }

        private static IDictionary<string, string> GetRequestCache(HttpContextBase httpContext)
        {
            var d = httpContext.Cache[s_key] as IDictionary<string, string>;
            if (d == null)
            {
                d = new Dictionary<string, string>();
                httpContext.Cache.Insert(s_key, d, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 15, 0));
            }
            return d;
        }

        public string GetViewLocation(HttpContextBase httpContext, string key)
        {
            var d = GetRequestCache(httpContext);
            string location;
            if (!d.TryGetValue(key, out location))
            {
                location = _cache.GetViewLocation(httpContext, key);
                d[key] = location;
            }
            return location;
        }

        public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
        {
            _cache.InsertViewLocation(httpContext, key, virtualPath);
        }
    }
و به صورت زیر می‌توانید از آن استفاده کنید:
 protected void Application_Start() {
    ViewEngines.Engines.Clear();
    var ve = new RazorViewEngine();
    ve.ViewLocationCache = new CustomViewCache(ve.ViewLocationCache);
    ViewEngines.Engines.Add(ve);
    ... 
}

نکته: فقط به یاد داشته باشید که اگر View جدیدی اضافه کردید یا یک View را حذف کردید، برای جلوگیری از بروز مشکل، حتما و حتما اگر پروژه در مراحل توسعه بر روی IIS قرار دارد app domain را ری‌استارت کنید تا حافظه کش مربوط به یافت‌ها پاک شود (و به روز رسانی) تا عدم وجود آدرس فیزیکی View جدید در کش، شما را دچار مشکل نکند.
مطالب
بخش دوم - بررسی امکانات (کلاس ها و متدهای) کتابخانه Gridify
در بخش قبل، به چند نمونه کلی از امکانات کتابخانه Gridify اشاره کردیم. در این مقاله به معرفی کلاس‌ها و متدهای این کتابخانه میپردازیم.

GridifyQuery
از این کلاس برای اعمال تنظیمات مورد نیاز در متدهای ارائه شده توسط Gridify استفاده میشود. در ادامه به خصیصه (پراپرتی)های این کلاس میپردازیم.
  • Filter : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات فیلترینگ اعمال میشود. مثال :
Filter = "Name==Ali,Age>>10";
  • SortBy : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات چیدمان یا سورتینگ با استفاده از نام فیلد انجام میشود. مثال : 
SortBy = "Age";
  • IsSortAsc: یک پراپرتی از نوع bool است که مشخص کننده چیدمان به صورت نزولی و یا صعودی است.
  • Page : یک پراپرتی از نوع عددی short است. از این پراپرتی برای عملیات Pagination یا صفحه بندی استفاده میشود که مشخص کننده شماره صفحه درخواستی است.
  • PageSize : یک پراپرتی از نوع عددی int است که برای مشخص کردن تعداد رکورد در هر صفحه استفاده میشود.
وارد کردن این اطلاعات هنگام استفاده از کتابخانه Gridify الزامی نیست؛ به همین جهت تنها در صورت مقداردهی، از این اطلاعات استفاده میشود. درصورتیکه هیچ اطلاعاتی در این پراپرتی‌ها وجود نداشته باشد، به صورت پیش فرض توسط Gridify نادیده گرفته میشود. البته استثنایی برای اکستنشن متد Gridify و GridifyAsync وجود دارد، به دلیل اینکه خروجی این دو متد یک کلاس <Paging<T است. در صورت اینکه مقداری برای پراپرتی‌های Page و PageSize وارد نشده باشد، به صورت پیش فرض اطلاعات Page 1 با DefaultPageSize را بازمیگرداند که مقدار پیش فرض آن 10 میباشد. با استفاده از تغییر فیلد استاتیک DefaultPageSize میتوان این عدد نیز را تغییر داد.


متد‌های الحاقی یا IQueryable  Extensions 
برای استفاده از کتابخانه Gridify نیازی به ساخت هیچ کلاسی نیست و صرفا امکانات اینترفیس IQueryable را گسترش میدهد.
 ApplyFiltering  از این متد برای اعمال فیلترینگ روی یک IQueryable استفاده میشود. این متد یک رشته متنی (string) ویا یک GridifyQuery دریافت کرده و پس از اعمال فیلترینگ یک IQueryable بازمیگرداند.
 ApplyOrdering  از این متد برای اعمال چیدمان یا Sorting روی یک IQueryable استفاده میشود. پس از اعمال چیدمان، یک IQueryable را بازمیگرداند.
 ApplyPaging از این متد برای اعمال صفحه بندی (Pagination) استفاده میشود. پس از اعمال صفحه بندی یک IQueryable را بازمیگرداند. 
 ApplyOrderingAndPaging   از این متد برای اعمال همزمان چیدمان و صفحه بندی استفاده میشود که یک IQueryable را باز میگرداند. 
ApplyFilterAndOrdering  از این متد برای اعمال همزمان فیلترینگ و چیدمان استفاده میشود که یک IQueryalbe را باز میگرداند.
 ApplyEverything   از این متد برای اعمال عملیات صفحه بندی، چیدمان و فیلترینگ استفاده میشود که یک IQueryable را باز میگرداند.
 GridifyQueryable   این متد مشابه ApplyEverything است که مقدار یک <QueryablePaging<T را برمیگرداند و دارای یک خصیصه اضافی TotalItems است که در عملیات صفحه بندی عموما نیاز داریم. (تعداد کل رکورد‌های موجود در پایگاه داده، با توجه به فیلتر اعمال شده)
 Gridify  متدهای قبلی فقط به query موجود ما یکسری شرط را اضافه میکردند. ولی مسئولیت اجرای query به عهده ما بود. (مثلا با استفاده از ToList.). متد Gridify تمامی شرط‌ها را باتوجه به GridifyQuery دریافتی اعمال کرده، سپس اطلاعات را بارگذاری کرده و یک <Paging<T را بازمیگرداند که کاملا قابل استفاده و بهینه شده برای دیتاگرید‌ها میباشد.


متد‌های الحاقی GridifyQuery:
 GetFilteringExpression   این متد expression معادل فیلتر string نوشته شده شما را برمیگرداند که میتوانید از آن به طور مثال در متد Where در Linq استفاده نمایید. 
 GetOrderingExpression   این متد expression انتخاب فیلد برای Orderby و OrderByDescending را باتوجه به مقدار وارد شده در فیلد SortBy بازمیگرداند.

Filtering Operators:
با علائم پشتیبانی شده در Gridify برای اعمال فیلترینگ در زیر آشنا میشویم.

همانطور که در تصویر بالا مشاهده میکنید، برای اعمال فیلترینگ‌های پیچیده میتوانیم از چهار اپراتور , | ( )  استفاده کنیم. به همین جهت اگر نیاز داشتید که در مقدار جستجوی خود از این علائم استفاده کنید، باید قبل از هرکدام از آنها، علامت \ را اضافه کنید. 

 مثال رج‌اکس escape character در JavaScript  : 

let esc = (v) => v.replace(/([(),|])/g, '\\$1')

مثال #‍C : 

var value = "(test,test2)";
var esc = Regex.Replace(value, "([(),|])", "\\$1" ); // esc = \(test\,test2\)  

در بخش بعد با امکانات Mapper توکار Gridify و شخصی سازی آن آشنا خواهیم شد.

نظرات مطالب
طبقه بندی Bad Code Smell ها
دستورات  if ، switch مربوط به دوران زبان برنامه نویسی ساخت یافته است و مهمترین فرق بین زبان‌های شی گرا با ساخت یافته وجود dynamic dispatch است و این قابلیت به ما امکان می‌دهد که در زمان اجرا از حالتی به حالتی دیگر برویم. در اینجا ما پیچیدگی کد رو افزایش داده ولی این پیچیدگی دارای ارزش افزوده ای است که مهمترین آن این است که دیگر با object سرو کار داریم نه یک وضعیت.
عبارت switch با حالت enum-as-class کار نمیکند درست است زیرا آن زمان  switch را برای object‌ها طراحی نکرده بود و استفاده از switch یعنی هاردکد کرد برنامه و ما رو با مشکلات نگهداری و همچنین احتمال بروز باگ رو در برنامه افزایش میدهد.در این مقاله اشاره ای به حذف switch کرده ولی به خوبی مزایایی آن رو توضیح نداده است و یکی از این مزایا این است که ما حالات شرطی رو می‌توانیم بر اساس یک شی به کلاس استفاده کننده تزریق کنیم و مشکلات نگهداری برنامه کمتر شود. 
مطالب
Asp.Net Identity #1
API ، Identity جدید مایکروسافت جهت مدیریت کاربران در برنامه‌های ASP.NET می‌باشد. نقطه‌ی اتکای مایکروسافت در سالهای اخیر برای مدیریت کاربران سیستم ASP.NET Membership بود که از ضعف‌های طراحی رنج می‌برد. مهمترین محدودیت این سیستم این بود که داده‌های ذخیره شده توسط Schema، فقط قابلیت کار با SQL Server را دارا بود که توسعه‌ی آن بدون پیاده سازی دوباره‌ی کلاسهای تامین کننده ( Provider Classes ) بسیار مشکل بود. بعد از آن مایکروسافت جهت کاهش پیچیدگی سیستم ASEP.NET Membership، دو سیستم دیگر به نامهای Simple Membership و ASP.NET Universal Providers را عرضه نمود. این سیستم‌ها تا حدودی پیچیدگی را کم کردند، اما همچنان برمبنای SQL Server بودند. تا اینکه مایکروسافت سیستم ASP.NET Membership را با سیستم Identity جهت حل دو مشکل مطرح شده تعویض نمود. سیستم Identity هم پایدار است و هم قابل توسعه. به علاوه با استفاده از قابلیت Open Source بودن، قابل وفق دادن می‌باشد (یعنی برحسب نیاز پروژه می‌توان کل یا قسمتهایی را پیاده سازی نمود). برای بسیاری از توسعه دهندگان برنامه‌های ASP.NET، سیستم Identity اولین پرده برداری از  OWIN خواهد بود. در حقیقت OWIN یک لایه‌ی Abstract می‌باشد که برنامه‌ی وب را از محیطی که آن‌را میزبانی کرده، ایزوله می‌کند. هدف از ایزوله کردن، ایجاد انعطاف پذیری بیشتر در محیط هایی که برنامه‌های ASP.Net را میزبانی می‌کنند و همچنین ایجاد یک زیرساخت سبک وزن جهت سرویس دهی می‌باشد. همان طور که عنوان شد OWIN یک استاندارد باز است و مایکروسافت پروژه KATANA را به منظور پیاده سازی استانداردی از OWIN طراحی کرد. این طراحی همراه با یک سری کامپوننت به منظور تامین ویژگی‌هایی (Functionalities) که یک برنامه وب به آنها نیاز دارد همراه بود. زیبایی کار مایکروسافت در این بود که OWIN/KATANA پشته‌ی تکنولوژی ASP.NET را از بقیه‌ی قسمتهای .NET Framework ایزوله کرده که اجازه نرخ بالاتری از تغییرات را می‌دهد.
توسعه دهندگان OWIN به جای استفاده از کل پلتفرم (بر عکس چیزی که در حال حاضر در سرویسهای ASP.NET رخ می‌دهد) فقط سرویس‌هایی را که برنامه آنها نیاز دارد، انتخاب می‌کنند. این سرویسهای منفرد که در واژه نامه‌ی OWIN با نام Middleware شناخته می‌شوند، می‌توانند در نرخ‌های مختلفی توسعه داده شوند و توسعه دهندگان را قادر می‌سازند که بین تأمین کنندگان سرویسهای مختلف یکی را انتخاب کنند و فقط وابسته به سرویس‌های پیاده سازی شده توسط مایکروسافت نباشند.
و در پایان باید گفت که پلتفرم ASP.NET و IIS کنار گذاشته نخواهند شد. مایکروسافت شفاف سازی کرده‌است که یکی از ابعاد زیبای OWIN این است که به توسعه دهندگان اجازه انعطاف پذیری بیشتر را می‌دهد که در آن کامپوننت‌های Middleware بوسیله IIS میزبانی می‌شوند و هم اکنون پروژه‌ی KATANA توسط فضاهای نام System.Web پشتیبانی می‌شود. 
خوب دوستان این مقدمه ای بود بر سیستم Identity . انشالله در نوشته‌های بعدی بیشتر سیستم Identity را تحلیل و بررسی خواهیم کرد. 
اشتراک‌ها
BitVector 0.0.1 منتشر شد 🚀

دات نت متاسفانه هیچ تایپ مناسبی برای کار و دستکاری داده‌های باینری (Bit) به غیر از کلاس BitArray ندارد, که این کلاس اولا Immutable و ValueType نیست و دوم به عنوان بافر از Int32 استفاده میکند که باعث میشود (بسته به نوع دیتا) Memory زیادی مصرف کند و همچنین امکاناتی مثل Bit Shifting هم ندارد. 
BitVector یه struct ساده ولی قدرتمند است که این مشکلات را برطرف میکند.

(البته struct BitArray32 هم وجود دارد که برای دیتاست‌های بزرگ به دلیل اینکه نهایتا از 32 بیت استفاده میکند مناسب نیست.)

BitVector 0.0.1 منتشر شد 🚀
مطالب
رمزنگاری خودکار فیلدها توسط Entity Framework Core
از EF Core 2.1 به بعد، قابلیت جدیدی تحت عنوان «تبدیلگرهای مقدار»، به آن اضافه شده‌است. برای مثال در EF Core، زمانیکه اطلاعات Enums، در بانک اطلاعاتی ذخیره می‌شوند، معادل عددی آن‌ها درج خواهند شد. اگر علاقمند باشید تا بجای این مقادیر عددی دقیقا همان رشته‌ی تعریف کننده‌ی Enum درج شود، می‌توان یک «تبدیلگر مقدار» را برای آن نوشت. برای مثال در موجودیت Rider زیر، خاصیت Mount از نوع یک enum است.
public class Rider
{
    public int Id { get; set; }
    public EquineBeast Mount { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}
برای اینکه در حین درج رکوردهای Rider در بانک اطلاعاتی دقیقا از مقادیر رشته‌ای EquineBeast استفاده شود، می‌توان به صورت زیر عمل کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(
            v => v.ToString(),
            v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
در اینجا در حین تعریف جزئیات نگاشت یک مدل می‌توان متد جدید HasConversion را نیز فراخوانی کرد. پارامتر اول آن، روش تبدیل مقدار enum را به یک رشته، جهت درج در بانک اطلاعاتی و پارامتر دوم آن، روش تبدیل مقدار رشته‌ای خوانده شده‌ی از بانک اطلاعاتی را جهت وهله سازی یک Rider داری خاصیت enum، مشخص می‌کند.

نکته 1: مقادیر نال، هیچگاه به تبدیلگرهای مقدار، ارسال نمی‌شوند. اینکار پیاده سازی آن‌ها را ساده‌تر می‌کند و همچنین می‌توان آن‌ها را بین خواص نال‌پذیر و نال‌نپذیر، به اشتراک گذاشت. بنابراین برای مقادیر نال نمی‌توان تبدیلگر نوشت.

نکته 2: کاری که در متد HasConversion فوق انجام شده‌است، در حقیقت وهله سازی ضمنی یک ValueConverter و استفاده از آن است. می‌توان اینکار را به صورت صریح نیز انجام داد:
var converter = new ValueConverter<EquineBeast, string>(
    v => v.ToString(),
    v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
مزیت اینکار این است که اگر قرار شد برای چندین خاصیت از تبدیلگر مقدار مشابهی استفاده کنیم، می‌توان از یک converter تعریف شده بجای تکرار کدهای آن استفاده کرد.


تبدیلگرهای مقدار توکار EF Core

برای بسیاری از اعمال متداول، در فضای نام Microsoft.EntityFrameworkCore.Storage.ValueConversion، تعدادی تبدیلگر مقدار تدارک دیده شده‌اند که به این شرح می‌باشند:
BoolToZeroOneConverter: تبدیلگر bool به صفر و یک
BoolToStringConverter: تبدیلگر bool به Y و یا N
BoolToTwoValuesConverter: تبدیلگر bool به دو مقداری دلخواه
BytesToStringConverter: تبدیلگر آرایه‌ای از بایت‌ها به یک رشته‌ی Base64-encoded
CastingConverter: تبدیلگر یک نوع به نوعی دیگر
CharToStringConverter: تبدیلگر char به string
DateTimeOffsetToBinaryConverter: تبدیلگر DateTimeOffset به یک مقدار 64 بیتی باینری
DateTimeOffsetToBytesConverter: تبدیلگر DateTimeOffset به آرایه‌ای از بایت‌ها
DateTimeOffsetToStringConverter: تبدیلگر DateTimeOffset به رشته
DateTimeToBinaryConverter: تبدیلگر DateTime به یک مقدار 64 بیتی با درج DateTimeKind
DateTimeToStringConverter: تبدیلگر DateTime به یک رشته
DateTimeToTicksConverter: تبدیلگر DateTime به ticks آن
EnumToNumberConverter: تبدیلگر Enum به عدد متناظر با آن
EnumToStringConverter: تبدیلگر Enum به رشته
GuidToBytesConverter: تبدیلگر Guid به آرایه‌ای از بایت‌ها
GuidToStringConverter: تبدیلگر Guid به رشته
NumberToBytesConverter: تبدیلگر اعداد به آرایه‌ای از بایت‌ها
NumberToStringConverter: تبدیلگر اعداد به رشته
StringToBytesConverter: تبدیلگر رشته به آرایه‌ای از بایت‌های UTF8 معادل آن
TimeSpanToStringConverter: تبدیلگر TimeSpan به رشته
TimeSpanToTicksConverter: تبدیلگر TimeSpan به ticks آن

برای نمونه در این لیست، EnumToStringConverter نیز وجود دارد. بنابراین نیازی به تعریف دستی آن مانند مثال ابتدای بحث نیست و می‌توان به صورت زیر از آن استفاده کرد:
var converter = new EnumToStringConverter<EquineBeast>();
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion(converter);
نکته: تمام تبدیل کننده‌های مقدار توکار EF Core، بدون حالت هستند. بنابراین می‌توان یک تک وهله‌ی از آن‌ها را بین چندین خاصیت به اشتراک گذاشت.


تعیین نوع تبدیلگر مقدار، جهت ساده سازی تعاریف

برای حالاتی که تبدیلگر مقدار توکاری تعریف شده‌است، صرفا تعریف نوع تبدیل، کفایت می‌کند:
modelBuilder
    .Entity<Rider>()
    .Property(e => e.Mount)
    .HasConversion<string>();
برای نمونه در اینجا با ذکر نوع رشته، تبدیل enum به string به صورت خودکار انجام خواهد شد. معادل اینکار، تعریف نوع سمت بانک اطلاعاتی این خاصیت است:
public class Rider
{
    public int Id { get; set; }

    [Column(TypeName = "nvarchar(24)")]
    public EquineBeast Mount { get; set; }
}
در این حالت حتی نیازی به تعریف HasConversion هم نیست.


نوشتن تبدیلگر خودکار مقادیر خواص، به نمونه‌ای رمزنگاری شده

پس از آشنایی با مفهوم «تبدیلگرهای مقدار» در +EF Core 2.1، اکنون می‌توانیم یک نمونه‌ی سفارشی از آن‌را نیز طراحی کنیم:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
      // …

        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            // Custom application mappings
            builder.Entity<ConfigurationValue>(entity =>
            {
                entity.Property(e => e.Value).IsRequired().HasConversion(encryptedConverter);
            });
        }
    }
}
در اینجا معکوس کردن رشته‌ها به عنوان الگوریتم ساده‌ی رمزنگاری اطلاعات انتخاب شده‌است. نحوه‌ی اعمال این ValueConverter جدید را نیز ملاحظه می‌کنید.
می‌توان قسمت HasConversion را به صورت زیر خودکار کرد:
ابتدا یک Attribute جدید را به نام Encrypted به برنامه اضافه می‌کنیم:
using System;

namespace Test
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public sealed class EncryptedAttribute : Attribute
    { }
}
هدف از این Attribute خالی، صرفا نشانه گذاری خاصیت‌هایی است که قرار است به صورت رمزنگاری شده در بانک اطلاعاتی ذخیره شوند؛ مانند خاصیت Value زیر:
namespace DbConfig.Web.DomainClasses
{
    public class ConfigurationValue
    {
        public int Id { get; set; }
        public string Key { get; set; }

        [Encrypted]
        public string Value { get; set; }
    }
}
پس از آن، متد OnModelCreating را به صورت زیر اصلاح می‌کنیم تا به کمک Reflection و اطلاعات موجودیت‌های ثبت شده‌ی در سیستم، متد SetValueConverter را بر روی خواصی که دارای EncryptedAttribute هستند، به صورت خودکار فراخوانی کند:
namespace DbConfig.Web.DataLayer.Context
{
    public class MyAppContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder builder)
        {
            var encryptedConverter = new ValueConverter<string, string>(
               convertToProviderExpression: v => new string(v.Reverse().ToArray()), // encrypt
               convertFromProviderExpression: v => new string(v.Reverse().ToArray()) // decrypt
            );

            foreach (var entityType in builder.Model.GetEntityTypes())
            {
                foreach (var property in entityType.GetProperties())
                {
                    var attributes = property.PropertyInfo.GetCustomAttributes(typeof(EncryptedAttribute), false);
                    if (attributes.Any())
                    {
                        property.SetValueConverter(encryptedConverter);
                    }
                }
            }
        }


تاثیر ValueConverter‌ها بر روی اعمال متداول کار با بانک اطلاعاتی

از دیدگاه برنامه، ValueConverterهای تعریف شده، هیچگونه تاثیری را بر روی کوئری نوشتن و یا ثبت و ویرایش اطلاعات ندارند و عملکرد آن‌ها کاملا از دیدگاه سایر قسمت‌های برنامه مخفی است. برای مثال در برنامه، فرمان به روز رسانی خاصیت Value را با مقدار .A new value to test صادر کرده‌ایم (مقدار دهی متداول)، اما همانطور که ملاحظه می‌کنید، نمونه‌ی رمزنگاری شده‌ی آن به صورت خودکار در بانک اطلاعاتی درج شده‌است (پارامتر p0):
 Executed DbCommand (22ms) 
   [Parameters=[@p1='1', 
                @p0='.tset ot eulav wen A' (Nullable = false) (Size = 4000)],
CommandType='Text', CommandTimeout='180']
SET NOCOUNT ON;
UPDATE [Configurations] SET [Value] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

و یا کوئری زیر
 db.Set<ConfigurationValue>().Where(x => x.Value.EndsWith("world!"))
به این نحو ترجمه خواهد شد:
SELECT [x].[Id], [x].[Key], [x].[Value]
FROM [Configurations] AS [x]
WHERE RIGHT([x].[Value], LEN(N'world!')) = N'!dlrow'
یعنی نیازی نیست تا مقداری را که در حال جستجوی آن هستیم، خودمان به صورت دستی رمزنگاری کرده و سپس در کوئری قرار دهیم. اینکار به صورت خودکار انجام می‌شود.
مطالب
مفاهیم پایه سیستم های کنترل نسخه؛ قسمت سوم : جمع بندی
در اولین قسمت این سری، گیت و در قسمت دوم ، SVN را بررسی کردیم؛ در این مقاله قصد داریم یک جمع بندی از این دو مقاله داشته باشیم.
احتمالا در مورد این دو سیستم حرف‌های زیادی شنیده‌اید و احتمالا بیشتر آن‌ها در مورد گیت نظر مساعدتری داشته‌اند؛ ولی تفاوت‌هایی بین این دو سیستم هست که باید به نسبت هدف و نیازی که دارید آن را مشخص کنید. یکی از اصلی‌ترین این تفاوت‌ها این است که svn یک سیستم مرکزی است؛ ولی گیت اینگونه نیست که در ادامه تفاوت این دو مورد را تشریح می‌کنیم.
یک. SVN یک مخزن مرکزی دارد که همه‌ی تغییراتی که روی کپی‌ها انجام می‌شود، باید به سمت مخزن مرکزی Commit یا ارسال شوند. ولی در سیستم گیت یک سیستم مرکزی وجود ندارد و هر مخزنی که fork یا Clone می‌شود، یک مخزن جداگانه به حساب می‌آید و Commit شدن تنها به مخزن کپی شده صورت میگیرد و در صورت pull request ادغام با مخزن اولیه خودش صورت میگیرد.
دو. گیت به نسبت svn از پیچیدگی بیشتری برخوردار است؛ ولی برای پروژه‌های بزرگتر که کاربران زیادی با آن کار می‌کنند و احتمال شاخه بندی‌های زیادتر، در آن وجود دارد بهتر عمل می‌کند. موقعی که یک پروژه یا تیم کوچکی روی آن کار می‌کنند به دلیل commit شدن مستقیمی که svn دارد، کار راحت‌تر و آسان‌تر صورت می‌گیرد ولی با زیاد شدن کاربران و حجم کار، گیت کارآیی بالاتری دارد.
سه. از آن جا که گیت نیاز به fork شدن دارد و یک مخزن کاملا مجزا از پروژه اصلی تولید می‌کند؛ سرعت بهتری نسبت به svn که یک کپی از زیر مجموعه ساختار اصلی ایجاد می‌کند دارد.
چهار. شاخه بندی یک مفهوم اصلی و مهم در گیت به شمار می‌آید که اکثر کاربران همه روزه از آن استفاده می‌کنند و این اجازه را می‌دهد که که تغییرات و تاریخچه فعالیت هر کاربر را بر روی هر شاخه، جداگانه ببینیم. در svn پیاده سازی شاخه‌ها یا تگ‌ها سخت و مشکل است. همچنین شاخه بندی کار در svn به شکل سابق با کپی کردن صورت گرفته که گاهی اوقات به دلایلی که در قسمت قبل گفتیم، باعث ناسازگاری می‌گردد.
پنج. حجم مخازن گیت به نسبت svn خیلی کمتر است برای نمونه پروژه موزیلا 30 درصد حجم کمتری در مخزن گیت دارد. یکی از دلایلی که svn حجم بیشتری میگیرد این است که به ازای هر فایل دو فایل موجود است یکی که همان فایل اصلی است که کاربر با آن کار می‌کند و دیگری یک فایل دیگر در شاخه svn. است که برای کمک به عملیاتی چون وضعیت، تفاوت ها، ثبت تغییرات به کار می‌رود. در صورتی که در آن سمت، گیت، تنها به یک فایل شاخص 100 بایتی برای هر  دایرکتوری کاری نیاز دارد
شش. گیت عملیات کاربری را به جز fetch و push، خیلی سریع انجام میدهد. این عملیات شامل یافتن تفاوت‌ها، نمایش تاریخچه، ثبت تغییرات، ادغام شاخه‌ها و جابجایی بین شاخه‌ها می‌گردد.
هفت. در سیستم SVN به دلیل ساختار درختی که دارد، می‌توانید زیر مجموعه‌ی یک مخزن را بررسی کنید ولی در سیستم گیت اینکار امکان پذیر نیست. البته باید به این نکته توجه داشت که برای یک پروژه‌ی بزرگ شما مجبور هستید همیشه کل مخزن را دانلود کنید. حتی اگر تنها نسخه‌ی خاصی از این زیرمجموعه را در نظر داشته باشید. به همین علت در شهرهایی که اینترنت گرانقیمت و یا سرعت پایین عرضه می‌شود، گیت به صرفه‌تر است و زمان کمتری برای دانلود آن می‌ برد.
موارد تعریف شده زیر طبق گفته ویکی سایت Kernel.Org ذکر می‌شود:
  • گیت از سیستم SVN سریعتر عمل می‌کند.
  • در سیستم گیت هر شاخه بندی کل تاریخچه خود را به دنبال دارد.
  • فایل git که تنظیمات مخزن داخلش قرار دارد، ساختار ساده‌ای دارد و به راحتی می‌توان در صورت ایجاد مشکل، آن را حل کرد و به ندرت هم پیش می‌آید که مشکلی برایش پیش بیاید.
  • پشتیبانی گیری از یک سیستم مرکزی مثل SVN راحت‌تر از پشتیبانی گیری از پوشه‌های توزیع شده در مخزن گیت است.
  • ابزارهای کاربری svn تا به الان پیشرفت‌های چشمگیری داشته است. پلاگین‌ها و برنامه‌های بیشتری نسبت به سیستم گیت دارد. یکی از معروفترین این پلاگین‌ها، ابزار  tortoisesvn  است (البته ابزارهای گیت امروز رشد چشمگیرتری داشته اند که در قسمت اول نمونه‌های آن ذکر شد).
  • سیستم svn برای نسخه بندی و تشخیص تفاوت‌ها از یک سیستم ساده اعداد ترتیبی استفاده می‌کند که اولین ثبت با شماره یک آغاز شده و به ترتیب ادامه می‌یابد و برای کاربران هم خواندنش راحت است و هم قابل پیش بینی است. به همین جهت برای بررسی تاریخچه‌ها و دیگر گزارش‌ها تا حدی راحت عمل می‌کند. در سیستم شاخه بندی این سیستم شماره گذاری چندان مطلوب نیست و متوجه نمی‌شوید که این شاخه از کجا نشات گرفته است. در حال حاضر برای پروژه‌ی موزیلا این عدد به 6 رقم رسیده است ولی در آن سمت، سیستم گیت از هش SH-1 استفاده می‌کند که یک رشته 40 کاراکتری است و 8 رقم اول آن به منشاء اشاره می‌کند که باعث می‌شود متوجه بشویم که این شاخه از کجا آمده است ولی از آنجا که این عدد یکتا ترتیبی نیست، برای خواندن و گزارشگیری‌هایی که در SVN راحت صورت می‌گیرد، در گیت ممکن نیست یا مشکل است.
  • گیت رویدادهای ادغام و شاخه بندی را بهتر انجام می‌دهد.