مطالب
فراخوانی سرویس‌های OData توسط کلاینت‌های #C
فرض کنید در سرویس‌های خود، در حال استفاده از OData هستید. حال کافیست که metadata$ مربوط به سرویستان را برای استفاده‌ی کلاینت‌های دیگر، در اختیار آن‌ها قرار دهید.
وقتی از Odata استفاده میکنید، به صورت خودکار metadataی از سرویس‌ها و مدل‌های شما ساخته میشود و میتوان از آن به عنوان یک documentation کامل نام برد و حتی افرادی که استاندارد‌های Odata را نمیشناسند، به راحتی میتوانند آن را مطالعه و در صورت اجازه‌ی شما، از امکانات آن سرویس‌ها، در نرم افزار خودشان استفاده کنند.
بطور مثال میتوانید متادیتای برنامه‌ی خود را با استفاده از آدرس فرضی http://localhost:port/odata/$metadata مشاهده نمایید؛ که چیزی شبیه به محتوای زیر خواهد بود:
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OwinAspNetCore.Models">
<EntityType Name="Product">
<Key>
<PropertyRef Name="Id"/>
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false"/>
<Property Name="Name" Type="Edm.String"/>
<Property Name="Price" Type="Edm.Decimal" Nullable="false"/>
</EntityType>
</Schema>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
<Function Name="TestFunction" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(OwinAspNetCore.Models.Product)"/>
<Parameter Name="Val" Type="Edm.Int32" Nullable="false"/>
<Parameter Name="Name" Type="Edm.String"/>
<ReturnType Type="Edm.Int32" Nullable="false"/>
</Function>
<EntityContainer Name="Container">
<EntitySet Name="Products" EntityType="OwinAspNetCore.Models.Product"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
در اینجا میتوان EntityTypeها ، EntitySetها و همه‌ی Action‌ها و Function‌های خود را مشاهده نمایید.
به غیر از این، وجود metadata باعث شده به راحتی کلاینت‌های #JavaScript ،Java ،Objective-C ،C و ... بتوانند به راحتی ارتباط کاملی با سرویس‌های شما برقرار نمایند.
برای مثال به صورت معمول یک کلاینت #Cی برای ارتباط برقرار کردن با یک سرویس خارجی باید اینگونه عمل کند (یک درخواست از نوع POST):
string postUrl = "http://localhost:port/....";
HttpClient client = new HttpClient();
var response = client.PostAsync(postUrl, new StringContent(JsonConvert.SerializeObject(new { Rating = 5 }), Encoding.UTF8, "application/json")).Result;
مشکلات این روش کاملا روشن و گویاست: پیچیدگی خیلی زیاد، دیباگ خیلی سخت و refactoring پیچیده و ...
اگر مطالب قبلی را دنبال کرده باشید، به پیاده سازی سرویس‌های Odata پرداختیم. در این لینک یک repository کامل برای کار با odata در asp.net core آماده شده‌است و در این مقاله از آن استفاده نموده‌ام.
بعد از clone کردن آن، پروژه را run نمایید. به چیز بیشتری از آن نیازی نداریم.
حال کافیست یک پروژه‌ی Console Application را ساخته و بعد باید از طریق منوی Tools گزینه‌ی Extensions and Updates را انتخاب و odata v4 client code generator را جستجو نماییم:

آن را نصب نموده و بعد از تکمیل شدن، visual studio را restart کنید.

پروژه‌ی console خود را باز کرده و از طریق Add -> new item، آیتم OData client را جستجو کرده و با نام ProductClient.tt آن را تولید نمایید (نام آن اختیاری است):

فایل ProductClient.tt را که یک T4 code generator میباشد، باز کرده و مقدار ثابت MetadataDocumentUri را به آدرس سرویس odata خود تغییر دهید:

public const string MetadataDocumentUri = "http://localhost:port/odata/";

روی این آیتم کلیک راست و گزینه‌ی Run Custom tool را انتخاب نمایید. این تمام کاری است که نیاز به انجام دادن دارید.

حال فایل Program.cs را باز کرده و آن‌را اینگونه تغییر دهید:

using ConsoleApplication1.OwinAspNetCore.Models;
using System;
using System.Linq;
namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:24977/odata");

            //var context = new Default.Container(uri);
            var context = new TestNameSpace.TestNameSpace(uri);

            //get
            var products = context.Products.Where(pr => pr.Name.Contains("a"))
                .Take(1).Select(pr => new { Firstname = pr.Name, PriceValue = pr.Price }).ToList();

            //add
            context.AddToProducts(new Product() { Name = "Name1", Price = 123 });

            //update
            Product p = context.Products.First();
            p.Name = "changed";
            context.UpdateObject(p);

            //delete
            context.DeleteObject(context.Products.Last());

            //commit
            context.SaveChanges();
        }
    }
}
اینبار همه چیز strongly typed و با همان intellisense معروف خواهد بود. فقط دقت کنید که اگر از Repository معرفی شده، برای سمت سرور خود استفاده میکنید، به دلیل اینکه از Namespace استفاده کرده‌ام، context شما، به نام namespace شما خواهد بود. در غیر اینصورت به صورت default و بدون namespace، باید از Default.Container استفاده شود.

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

از این ویژگی فوق العاده میتوان حتی در کلاینت‌ها جاوااسکریپتی نیز استفاده کرد. فرض کنید نرم افزار تحت وبی را با استفاده از jquery یا angularjs طراحی کرده‌اید. قاعدتا فراخوانی درخواست‌های شما به سمت سرور، چیزی شبیه به این خواهد بود:

//angularjs
$http.get("/products/get", {Name: "Test", Company: "Test"})
    .then(function(response) {
        console.log(response.data);
    });

//jquery
$.get("/products/get", {Name: "Test", Company: "Test"}, function(data, status){
        console.log("Data: " + data);
    });

با استفاده از odata و typescript و یک library مربوط به odata client در سمت کلاینت، نرم افزار شما بجای موارد، بالا چیزی شبیه به مثال زیر خواهد بود (با همراه داشتن strongly typed و intellisense کامل)

let product1 = await context.products.filter(c => c.Name.contains("Ali")).toArray();
let product2 = await context.products.getSomeFunction(1, 'Test');
context.product.add({Name: 'Test'} as Product);
await context.saveChanges()


در مقاله‌های آتی به ویژگی‌های بیشتری از Odata خواهیم پرداخت.

مطالب
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS

زمانیکه اولین نگارش ASP.NET‌ حدود 10 سال قبل منتشر شد،‌ تنها سیستم عاملی که از آن پشتیبانی می‌کرد، ویندوز سرور 2000 بود، تنها پروسه‌ی اجرایی آن aspnet_wp نام داشت و تنها معماری پشتیبانی شده هم X86 بود. به پروسه‌ی aspnet_wp محدودیت مصرف حافظه‌ای اعمال شده بود که در حین آغاز آن بر اساس مقدار قابل تغییر processModel memoryLimit محاسبه و اعمال می‌شد (تعریف شده در فایل ماشین کانفیگ). این عدد به صورت درصدی از ظرفیت RAM فیزیکی سیستم، قابل تعریف و به صورت پیش فرض به 60 درصد تنظیم شده بود. به این ترتیب این پروسه مجاز نبود تا تمام حافظه‌ی فیزیکی مهیا را مصرف کند و در صورت وجود نشتی حافظه‌ای در برنامه‌ای خاص، این پروسه امکان بازیابی مجدد حافظه را پیدا می‌کرد (recycling). همچنین یک مورد دیگر را هم باید در نظر داشت و آن هم وجود قابلیتی است به نام ASP.NET Cache است که امکان ذخیره سازی مقادیر اشیاء را در حافظه‌ی مصرفی این پروسه مهیا می‌سازد. هر زمان که میزان این حافظه‌ی مصرفی به حد نزدیکی از محدودیت تعریف شده برسد، این پروسه به صورت خودکار شروع به حذف آن‌ها خواهد کرد.
محدودیت 60 درصدی تعریف شده، برای سیستم‌هایی با میزان RAM کم بسیار مفید بود اما در سیستم‌هایی با میزان RAM بیشتر، مثلا 4 گیگ به 2.4GB حافظه مهیا (60 درصد حافظه فیزیکی سیستم) محدود می‌شد و همچنین باید در نظر داشت که میزان user mode virtual address space مهیا نیز تنها 2 گیگابایت بود. بنابراین هیچگاه استفاده مؤثری از تمام ظرفیت RAM مهیا صورت نمی‌گرفت و گاها مشاهده می‌شد که یک برنامه تنها با مصرف 1.5GB RAM می‌توانست پیغام OutOfMemoryException را صادر کند. در این حالت مطابق بررسی‌های صورت گرفته مشخص شد که اگر مقدار processModel memoryLimit به حدود 800 مگابایت تنظیم شود، بهترین عملکرد را برای سیستم‌های مختلف می‌توان مشاهده کرد.

با ارائه‌ی ویندوز سرور 2003 و همچنین ارائه‌ی نسخه‌ی 1.1 دات نت فریم ورک و ASP.NET ، این وضعیت تغییر کرد. پروسه‌ی جدید در اینجا w3wp نام دارد و این پروسه تعاریف مرتبط با محدودیت حافظه‌ی خود را از تنظیمات IIS دریافت می‌کند (قسمت Maximum Used Memory در برگه‌ی Recycling مربوط به خواص Application Pool مرتبط). متاسفانه این عدد به صورت پیش فرض محدودیتی ندارد و به ظاهر برنامه مجاز است تا حد امکان از حافظه‌ی مهیا استفاده کند. به همین جهت یکی از مواردی را که باید در نظر داشت، مقدار دهی Maximum Used Memory ذکر شده است. خصوصا اینکه در نگارش 1.1 ، تنظیمات میزان مصرف RAM مرتبط با ASP.NET Cache نیز با برنامه یکی است.

در نگارش 2.0 دات نت فریم ورک، تنظیمات مرتبط با ASP.NET cache از تنظیمات میزان RAM مصرفی یک برنامه‌ی ASP.NET جدا شد و این مورد توسط قسمت cache privateBytesLimit قابل تنظیم و مدیریت است (در فایل IIS Metabase و همچنین فایل web.config برنامه).

نکته!
اگر process memory limit و همچنین cache memory limit را تنظیم نکنید، باز به همان عدد 60 درصد سابق بازخواهیم گشت و این مورد به صورت خودکار توسط IIS محاسبه و اعمال می‌شود. البته محدودیت ذکر شده برای پروسه‌های 64 بیتی در این حالت بسیار بهتر خواهد بود. اگر هر دوی این‌ها را تنظیم کنید، عدد حداقل بکارگرفته شده، مبنای کار خواهد بود و اگر تنها یکی را تنظیم کنید ، این عدد به هر دو حالت اعمال می‌گردد. برای بررسی بهتر می‌توان به مقدار Cache.EffectivePrivateBytesLimit و Cache.EffectivePercentagePhysicalMemoryLimit مراجعه کرد.

و ... اکنون بهتر می‌توانید به این سؤال پاسخ دهید که «سرور ما بیشتر از 4 گیگ رم دارد و برنامه‌ی ASP.NET من الان فقط 850 مگ رم مصرف کرده (که البته این هم نشانی از عدم dispose صحیح منابع است یا عدم تعیین تقدم و تاخر و زمان منقضی شدن، حین تعریف اشیاء کش)، اما پیغام out of memory exception را دریافت می‌کنم. چرا؟!»


بنابراین ایجاد یک Application pool جدید به ازای هر برنامه‌ی ASP.NET امری است بسیار مهم زیرا:
- به این ترتیب هر برنامه‌ی ASP.NET در پروسه‌ای ایزوله از پروسه‌ی دیگر اجرا خواهد شد (این مساله از لحاظ امنیتی هم بسیار مهم است). در اینجا هر برنامه، از پروسه‌ی w3wp.exe مجزای خاص خود استفاده خواهد کرد (شبیه به مرورگرهایی که هر tab را در یک پروسه جدید اجرا می‌کنند).
- اگر پروسه‌ای به حد بالای مصرف حافظه‌ی خود رسید با تنظیمات انجام شده در قسمت recycling مرتبط با Application pool اختصاصی آن، به صورت خودکار کار بازیابی حافظه صورت می‌گیرد و این امر بر روی سایر برنامه‌ها تاثیر نخواهد داشت (کاربران سایر برنامه‌ها مدام شکایت نمی‌کنند که سشن‌ها پرید. کش خالی شد. زیرا در حالت وجود application pool اختصاصی به ازای هر برنامه، مدیریت حافظه برنامه‌ها از هم ایزوله خواهند بود)
- کرش صورت گرفته در یک برنامه به دلیل عدم مدیریت خطاها، بر روی سایر برنامه‌ها تاثیر منفی نخواهد گذاشت. (زمانیکه ASP.NET worker process به دلیل استثنایی مدیریت نشده خاتمه یابد بلافاصله و به صورت خودکار مجددا «وهله‌ی دیگری» از آن شروع به کار خواهد کرد؛ یعنی تمام سشن‌های قبلی از بین خواهند رفت؛ که در صورت ایزوله سازی ذکر شده، سایر برنامه‌ها در امان خواهند ماند؛ چون در پروسه ایزوله‌ی خود مشغول به کار هستند)
- با وجود application pool اختصاصی به ازای هر برنامه، می‌توان برای سایت‌های کم ترافیک و پرترافیک، زمان‌های recycling متفاوتی را اعمال کرد. به این ترتیب مدیریت حافظه‌ی بهتری قابل پیاده سازی می‌باشد. همچنین در این حالت می‌توان مشخص کرد کدام سایت از تعداد worker process بیشتر یا کمتری استفاده کند.
- کاربری که پروسه‌ی ASP.NET تحت آن اجرا می‌شود نیز همینجا تعریف می‌گردد. بنابراین به این ترتیب می‌توان به برنامه‌ای دسترسی بیشتر و یا کمتر داد، بدون تاثیر گذاری بر روی سایر برنامه‌های موجود.

نتیجه گیری:
- از IIS استفاده می‌کنید؟ آیا می‌دانید Application pool چیست؟
- آیا می‌دانید در صورت عدم مقدار دهی پارامترهای حافظه‌ی یک Application pool ، به صورت پیش فرض چند درصد از حافظه‌ی فیزیکی مهیا در اختیار شما است؟


برای مطالعه بیشتر:

نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
- بجای اینکار (اضافه و حذف کردن سرویس‌ها در زمان اجرای برنامه که با ری‌استارت برنامه در سمت سرور غیرمعتبر می‌شود)، بهتر است با توجه به اینکه این وظایف به صورت یک سرویس ثبت می‌شوند، یک سرویس سفارشی فعال یا غیرفعالسازی را تعریف کنید و آن‌را به سازنده‌ی این وظایف تزریق و استفاده کنید. سپس زمانیکه حلقه‌ی انجام وظایف به RunAsync رسید، در اینجا فرصت خواهید داشت تا سرویس سفارشی جدید تزریق شده را بررسی کرده و از فعال بودن یا نبودن این وظیفه مطلع شوید (برای مثال این سرویس بر اساس نامی که به آن ارسال می‌کنید، به بانک اطلاعاتی مراجعه کرده و روشن و یا خاموش بودن آن‌را بررسی کند. تنظیم بانک اطلاعاتی آن‌را هم واگذار کنید به قسمت مدیریتی برنامه).  
+ این نوع Taskهای پویا را باید در ابتدای کار برنامه و در کلاس آغازین آن، به صورت زیر معرفی کنید:
services.AddTransient<DoEnableProductTasks>();
اشتراک‌ها
ویرایش سوم کتاب خود آموز گام به گام جاوا اسکریپت مایکروسافت
ویرایش سوم JavaScript Step by Step بر اساس دو ویرایش قبلی نگارش شده است. با این که معماری اصلی زبان جاوا اسکریپت تغییر آن چنانی نکرده است، استفاده از جاوا اسکریپت هر روزه بیشتر می‌شود و در طی چند سال اخیر اهمیت آن برای توسعه دهندگان بسیار افزایش یافته است. با این اوصاف ساختار و کلیات کتاب پیش رو تغییری نکرده به جز چند تغییر  قابل توجه. بخش Event Handling مورد تاکید بیشتری قرار گرفته است و برای افزایش سرعت توسعه از jQuery استفاده شده است. همچنین در فصل آخر، بخشی برای توسعه برنامه‌های Windows 8 با استفاده از جاوا اسکریپت در نظر گرفته شده است. با این حال، محتویات این کتاب کاملا در انحصار مایکروسافت نیست! 
ویرایش سوم کتاب خود آموز گام به گام جاوا اسکریپت مایکروسافت
نظرات مطالب
ASP.NET Web API - قسمت اول
دقت داشته باشید که Web API عرضه نشده تا WCF رو منسوخ کنه. برنامه هایی که صرفاً از بستر پروتوکل HTTP به عنوان یک سرویس برای رد و بدل کردن داده‌ها استفاده می‌کنند، بهتره که از این به بعد از Web API استفاده کنند. ضمن سادگی و مفاهیم آشنای ASP.NET MVC، روش یکپارچه ای برای ایجاد وب سرویس‌های HTTP نیز به وجود اومده که مشکلات استفاده از WCF رو از بین می‌بره. WCF ذاتاً برای پیغام‌های SOAP محور طراحی شده و به کار گرفتن اون برای وب سرویس‌های HTTP یا به زور خوراندن HTTP به اون بی معنیه. در WCF راه‌های مختلفی برای ایجاد وب سرویس‌های HTTP وجود داره که باعث گمراهی و سردرگمی توسعه گر میشه و حتی فریمورک‌های مختلفی مانند OpenRasta و ServiceStack نیز بدین منظور وجود دارند. بنابراین پشتیبانی WCF از HTTP به یک پروژه‌ی دیگه تحت نام ASP.NET Web API منتقل شده و WCF Web API دیگه پشتیبانی نمیشه. کمی تغییر نام و کمی جابجایی مفاهیم دراینجا صورت گرفته. WCF همچنان قدرتمنده و نباید Web API به هیچ وجه به عنوان جایگزینی برای اون تصور بشه. ایجاد بسترهایی برای ارتباطات دو طرفه یا صفی از پیغام‌ها یا سویچ بین کانال‌ها در هنگام فعال نبودن یک کانال، اینها همه از قابلیت‌هایی هست که Web API هرگز جایگزینی برای اونها نخواهد بود و مختص WCF هستند.
مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
در ادامه‌ی سری «ارتقاء به ASP.NET Core 1.0» اگر بخواهیم مباحث اعتبارسنجی کاربران و ASP.NET Identity مخصوص آن‌را بررسی کنیم، نیاز است ابتدا مباحث Entity framework Core 1.0 را بررسی کنیم. به همین جهت در طی چند قسمت مباحث پایه‌ای کار با EF Core 1.0 را در ASP.NET Core 1.0، بررسی خواهیم کرد. بنابراین پیشنیاز ضروری این مباحث، مطالعه‌ی سری «ارتقاء به ASP.NET Core 1.0» است و در آن از مباحثی مانند چگونگی کار با فایل‌های کانفیگ جدید، تزریق وابستگی‌ها و سرویس‌ها، فعال سازی سرویس Logging، فعال سازی صفحات مخصوص توسعه دهنده‌ها و ... در ASP.NET Core 1.0 استفاده خواهد شد.


EF Core چیست؟

EF Core یک ORM یا object-relational mapper چندسکویی است که امکان کار با بانک‌های اطلاعاتی مختلف را از طریق اشیاء دات نتی میسر می‌کند. توسط آن قسمت عمده‌ی کدهای مستقیم کار با بانک‌های اطلاعاتی حذف شده و تبدیل به کدهای دات نتی می‌شوند. مزیت این لایه‌ی Abstraction اضافی (لایه‌ای بر روی کدهای مستقیم لایه ADO.NET زیرین)، امکان تعویض بانک اطلاعاتی مورد استفاده، تنها با تغییر کدهای آغازین برنامه‌است؛ بدون نیاز به تغییری در سایر قسمت‌های برنامه. همچنین کار با اشیاء دات نتی و LINQ، مزایایی مانند تحت نظر قرار گرفتن کدها توسط کامپایلر و برخورداری از ابزارهای Refactoring پیشرفته را میسر می‌کنند. به علاوه SQL خودکار تولیدی توسط آن نیز همواره پارامتری بوده و مشکلات حملات تزریق SQL در این حالت تقریبا به صفر می‌رسند (اگر مستقیما SQL نویسی نکنید و صرفا از LINQ استفاده کنید). مزیت دیگر همواره پارامتری بودن کوئری‌ها، رفتار بسیاری از بانک‌های اطلاعاتی با آن‌ها همانند رویه‌های ذخیره شده است که به عدم تولید Query plan‌های مجزایی به ازای هر کوئری رسیده منجر می‌شود که در نهایت سبب بالا رفتن سرعت اجرای کوئری‌ها و مصرف حافظه‌ی کمتری در سمت سرور بانک اطلاعاتی می‌گردد.


تفاوت EF Core با نگارش‌های دیگر Entity framework در چیست؟

سورس باز بودن
EF از نگارش‌های آخر آن بود که سورس باز شد؛ اما EF Core از زمان نگارش‌های پیش نمایش آن به صورت سورس باز در GitHub قابل دسترسی است.

چند سکویی بودن
EF Core برخلاف EF 6.x (آخرین نگارش مبتنی بر Full Framework آن)، نه تنها چندسکویی است و قابلیت اجرای بر روی Mac و لینوکس را نیز دارا است، به علاوه امکان استفاده‌ی از آن در انواع و اقسام برنامه‌های دات نتی مانند UWP یا Universal Windows Platform و Windows phone که پیشتر با EF 6.x میسر نبود، وجود دارد. لیست این نوع سکوها و برنامه‌های مختلف به شرح زیر است:
 • All .NET application (Console, ASP.NET 4, WinForms, WPF)
 • Mac and Linux applications (Mono)
 • UWP (Universal Windows Platform)
 • ASP.NET Core applications
 • Can use EF Core in Windows phone and Windows store app

افزایش تعداد بانک‌های اطلاعاتی پشتیبانی شده
در EF Full یا EF 6.x، هدف اصلی، تنها کار با بانک‌های اطلاعاتی رابطه‌‌ای بود و همچنین مایکروسافت صرفا نگارش‌های مختلف SQL Server را به صورت رسمی پشتیبانی می‌کرد و برای سایر بانک‌های اطلاعاتی دیگر باید از پروایدرهای ثالث استفاده کرد.
در EF Core علاوه بر افزایش تعداد پروایدرهای رسمی بانک‌های اطلاعاتی رابطه‌ای، پشتیبانی از بانک‌های اطلاعاتی NoSQL هم اضافه شده‌است؛ به همراه پروایدر ویژه‌‌ای به نام In Memory جهت انجام ساده‌تر Unit testing. کاری که با نگارش‌های پیشین EF به سادگی و از روز اول پشتیبانی نمی‌شد.

حذف و یا عدم پیاده سازی تعدادی از قابلیت‌های EF 6.x
اگر موارد فوق جزو مهم‌ترین مزایای کار با EF Core باشند، باید درنظر داشت که به علت حذف و یا تقلیل یافتن یک سری از ویژگی‌ها در NET Core.، مانند Reflection (جهت پشتیبانی از دات نت در سکوهای مختلف کاری و خصوصا پشتیبانی از حالتی که کامپایلر مخصوص برنامه‌های UWP نیاز دارد تمام نوع‌ها را همانند زبان‌های C و ++C، در زمان کامپایل بداند)، یک سری از قابلیت‌های EF 6.x مانند Lazy loading هنوز در EF Core پشتیبانی نمی‌شوند. لیست کامل و به روز شده‌ی این موارد را در اینجا می‌توانید مطالعه کنید.
بنابراین امکان انتقال برنامه‌های EF 6.x به EF Core 1.0 عموما وجود نداشته و نیاز به بازنویسی کامل دارند. هرچند بسیاری از مفاهیم آن با EF Code First یکی است.


برپایی تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0

برای نصب تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0، جهت کار با مشتقات SQL Server (و SQL LocalDB) نیاز است سه بسته‌ی ذیل را نصب کرد (از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console):
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Pre
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
البته در این قسمت صرفا از بسته‌ی اول که جهت اتصال به SQL Server است استفاده می‌کنیم. بسته‌های دیگر را در قسمت‌های بعد، برای به روز رسانی اسکیمای بانک اطلاعاتی (مباحث Migrations) و مباحث scaffolding استفاده خواهیم کرد.
پس از اجرای سه دستور فوق، تغییرات مداخل فایل project.json برنامه به صورت ذیل خواهند بود:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
        "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0"
    }
}
این مداخلی که توسط نیوگت اضافه شده‌اند، نیاز به اصلاح دارند؛ به این صورت:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        },
        "Microsoft.EntityFrameworkCore.SqlServer.Design": {
            "version": "1.0.0",
            "type": "build"
        }
    },

    "tools": {
       // same as before
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "imports": [
                "portable-net45+win8"
            ]
        }   
   }
}
نیاز است در قسمت dependencies مشخص کنیم که ابزارهای اضافه شده مخصوص build هستند و نه اجرای برنامه. همچنین قسمت tools را باید با Microsoft.EntityFrameworkCore.Tools مقدار دهی کرد تا بتوان از این ابزار در خط فرمان، جهت اجرای فرامین migrations استفاده کرد.
بنابراین از همین ابتدای کار، بدون مراجعه‌ی به Package Manager Console، چهار تغییر فوق را به فایل project.json اعمال کرده و آن‌را ذخیره کنید؛ تا کار به روز رسانی و نصب بسته‌ها، به صورت خودکار و همچنین صحیحی انجام شود.


فعال سازی صفحات مخصوص توسعه دهنده‌های EF Core 1.0

در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها» با تعدادی از اینگونه صفحات آشنا شدیم. برای EF Core نیز بسته‌ی مخصوصی به نام Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore وجود دارد که امکان فعال سازی صفحه‌ی نمایش خطاهای بانک اطلاعاتی را میسر می‌کند. بنابراین ابتدا به فایل project.json مراجعه کرده و این بسته را اضافه کنید:
{
    "dependencies": {
       // same as before
        "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0"
    }
}
سپس می‌توان متد جدید UseDatabaseErrorPage را در متد Configure کلاس آغازین برنامه، فراخوانی کرد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDatabaseErrorPage();
   }
با فعال سازی این صفحه، اگر در حین توسعه‌ی برنامه و اتصال به بانک اطلاعاتی، خطایی رخ دهد، بجای مشاهده‌ی یک صفحه‌ی خطای عمومی (اگر UseDeveloperExceptionPage را فعال کرده باشید)، اینبار ریز جزئیات بیشتری را به همراه توصیه‌هایی از طرف تیم EF مشاهده خواهید کرد.


تعریف اولین Context برنامه و مشخص سازی رشته‌ی اتصالی آن


در این تصویر، زیر ساخت نگاشت‌های EF Core را مشاهده می‌کنید. در سمت چپ، ظرفی را داریم به نام DB Context که در برگیرنده‌ی Db Setها است. در سمت راست که بیانگر ساختار کلی یک بانک اطلاعاتی است، معادل این‌ها را مشاهده می‌کنیم. هر Db Set به یک جدول بانک اطلاعاتی نگاشت خواهد شد و متشکل است از کلاسی به همراه یک سری خواص که این‌ها نیز به فیلدها و ستون‌های آن جدول در سمت بانک اطلاعاتی نگاشت می‌شوند.
بنابراین برای شروع کار، پوشه‌ای را به نام Entities به پروژه اضافه کرده و سپس کلاس ذیل را به آن اضافه می‌کنیم:
namespace Core1RtmEmptyTest.Entities
{
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
کلاس Person بیانگر ساختار جدول اشخاص بانک اطلاعاتی است. برای اینکه این کلاس را تبدیل و نگاشت به یک جدول کنیم، نیاز است آن‌را به صورت یک DbSet در معرض دید EF Core قرار دهیم و اینکار در کلاسی که از DbContex مشتق می‌شود، صورت خواهد گرفت:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<Person> Persons { get; set; }
    }
}
بنابراین در ادامه کلاس جدید ApplicationDbContext را که از کلاس پایه DbContext مشتق می‌شود تعریف کرده و سپس کلاس Person را به صورت یک DbSet در معرض دید EF Core قرار می‌دهیم.
سازنده‌ی این کلاس نیز به نحو خاصی تعریف شده‌است. اگر به سورس‌های EF Core مراجعه کنیم، کلاس پایه‌ی DbContext دارای دو سازنده‌ی با و بدون پارامتر است:
protected DbContext()
   : this((DbContextOptions) new DbContextOptions<DbContext>())
{
}

public DbContext([NotNull] DbContextOptions options)
{
  // …
}
اگر از سازنده‌ی بدون پارامتر استفاده کنیم و برای مثال در کلاس ApplicationDbContext فوق، به طور کامل سازنده‌ی تعریف شده را حذف کنیم، باید به نحو ذیل تنظیمات بانک اطلاعاتی را مشخص کنیم:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"... connection string ...");
        }
    }
}
در این حالت باید متد OnConfiguring را override و یا بازنویسی کنیم، تا بتوان از اول مشخص کرد که قرار است از پروایدر SQL Server استفاده کنیم و ثانیا رشته‌ی اتصالی به آن چیست.
اما چون در یک برنامه‌ی ASP.NET Core، کار ثبت سرویس مربوط به EF Core، در کلاس آغازین برنامه انجام می‌شود و در آنجا به سادگی می‌توان به خاصیت Configuration برنامه دسترسی یافت و توسط آن رشته‌ی اتصالی را دریافت کرد، مرسوم است از سازنده‌ی با پارامتر DbContext به نحوی که در ابتدا عنوان شد، استفاده شود.
بنابراین در ادامه، پس از مطالعه‌ی مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» به فایل appsettings.json مراجعه کرده و تنظیمات رشته‌ی اتصالی برنامه را به صورت ذیل در آن مشخص می‌کنیم:
{
    "ConnectionStrings": {
        "ApplicationDbContextConnection": "Data Source=(local);Initial Catalog=TestDbCore2016;Integrated Security = true"
    }
}
باید دقت داشت که نام این مداخل کاملا اختیاری هستند و در نهایت باید در کلاس آغازین برنامه به صورت صریحی مشخص شوند.
در اینجا به وهله‌ی پیش فرض SQL Server اشاره شده‌است؛ از حالت اعتبارسنجی ویندوزی SQL Server استفاده می‌شود و بانک اطلاعاتی جدیدی به نام TestDbCore2016 در آن مشخص گردیده‌است.

پس از تعریف رشته‌ی اتصالی، متد OnConfiguring را از کلاس ApplicationDbContext حذف کرده و از همان نگارش دارای سازنده‌ی با پارامتر آن استفاده می‌کنیم. برای اینکار به کلاس آغازین برنامه مراجعه کرده و توسط متد AddDbContext این Context را به سرویس‌های ASP.NET Core معرفی می‌کنیم:
    public class Startup
    {
        public IConfigurationRoot Configuration { set; get; }

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                                .SetBasePath(env.ContentRootPath)
                                .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
                                .AddJsonFile($"appsettings.{env}.json", optional: true);
            Configuration = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(Configuration["ConnectionStrings:ApplicationDbContextConnection"]);
            });
در اینجا جهت یادآوری مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» نحوه‌ی وهله سازی خاصیت Configuration که در متد UseSqlServer مورد استفاده قرار گرفته‌است، نیز ذکر شده‌است.
بنابراین قسمت options.UseSqlServer را یا در اینجا مقدار دهی می‌کنید و یا از طریق بازنویسی متد OnConfiguring کلاس Context برنامه.


یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه وجود دارد

با توجه به اینکه Context برنامه را به صورت یک سرویس به ASP.NET Core معرفی کردیم، امکان تزریق وابستگی‌ها نیز در آن وجود دارد. یعنی بجای روش فوق، می‌توان IConfigurationRoot را به سازنده‌ی کلاس Context برنامه نیز تزریق کرد:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        private readonly IConfigurationRoot _configuration;

        public ApplicationDbContext(IConfigurationRoot configuration)
        {
            _configuration = configuration;
        }

        //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        //{
        //}

        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(_configuration["ConnectionStrings:ApplicationDbContextConnection"]);
        }
    }
}
با توجه به اینکه IConfigurationRoot در کلاس ConfigureServices به صورت Singleton، به مجموعه‌ی سرویس‌های برنامه معرفی شده‌است، از آن در تمام کلاس‌های برنامه که تحت نظر سیستم تزریق وابستگی‌های توکار ASP.NET Core هستند، می‌توان استفاده کرد.
در این حالت متد ConfigureServices کلاس آغازین برنامه، چنین شکلی را پیدا می‌کند و ساده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>();


یک نکته: امکان تزریق ApplicationDbContext به تمام کلاس‌های برنامه وجود دارد

همینقدر که ApplicationDbContext را به عنوان سرویسی در ConfigureServices تعریف کردیم، امکان تزریق آن در اجزای مختلف یک برنامه‌ی ASP.NET Core نیز وجود دارد:
using System.Linq;
using Core1RtmEmptyTest.Entities;
using Microsoft.AspNetCore.Mvc;

namespace Core1RtmEmptyTest.Controllers
{
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
}
در اینجا نحوه‌ی تزریق DB Context برنامه را به یک کنترلر مشاهده می‌کنید. البته هرچند تزریق یک کلاس مشخص به این شکل، تزریق وابستگی‌ها نام ندارد و هنوز این کنترلر دقیقا وابسته‌است به پیاده سازی خاص کلاس ApplicationDbContext، اما ... در کل امکان آن هست.

در این حالت پس از اجرای برنامه، خطای ذیل را مشاهده خواهیم کرد:


علت اینجا است که هنوز این بانک اطلاعاتی ایجاد نشده‌است و همچنین ساختار جداول را به آن منتقل نکرده‌ایم که این موارد را در قسمت‌های بعدی مرور خواهیم کرد.
مطالب
قسمت دوم -- نگاهی دقیق تر به اولین پروژه VC++ (درک مفهوم فایلهای سرآیند و فضای نام ، ویژگیهای زبان ++C و برخی قوانین برنامه نویسی در ++C)
در این قسمت نگاهی دقیق‌تر به فایلهای سرآیند ، فضای نام ، ویژگیهای زبان ++C  و برخی قوانین برنامه نویسی ++C  خواهیم داشت و همچنین در مورد  اولین پروژه توضیحات جامع‌تری ارائه میکنیم .

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

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

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

زبانهای سطح بالا :
زبانهای سطح بالا قابل فهم بودند و این امکان را داشتند تا چند دستور زبان ماشین یا اسمبلی را بتوان در قالب یک دستور نوشت ( منظور توابع کتابخانه ای در ++C/C) . پس هم فهم ، هم نوشتن برنامه در این زبانها راحت و هم تعداد خطوط کد کمتر شد . این زبانها به زبانهای برنامه نویسی سطح بالا یا High-Level Programming Language  معروفند . البته برنامه نوشته شده در این زبان نیز برای کامپیوتر قابل فهم نبوده و باید به زبان ماشین ترجمه شوند ، این وظیفه بر عهده کامپایلر می‌باشد . اولین زبانهای برنامه نویسی سطح بالا مانند  FORTRAN ، COBOL ، PASCAL و C  می‌باشند . زبان برنامه نویسی ++C تکامل یافته زبان  میباشد .
هر یک از زبانهای برنامه نویسی سطح بالا یک روش برنامه نویسی را پشتیبانی میکند به طور مثال زبان C  و  PASCAL  از روشهای برنامه نویسی  ساخت یافته ای و پیمانه ای و زبانهای مانند ++C و JAVA  از روش برنامه سازی شی گرا یا Object Oriented Programming یا به اختصار (OOP) استفاده میکنند . زبان ++C  چون زبان C را بطور کامل در بر  دارد پس از هر سه روش برنامه نویسی ساخت یافته و پیمانه ای و شی گرا استفاده میکند .

تا اینجا با تاریخچه ای از زبانها و مراحل تکامل آنها آشنا شدیم . حال ویژگیها و دلایل استفاده از زبان  ++C  را مرور میکنیم :

زبان C  در سال 1972 توسط دنیس ریچی طراحی شد . زبان C تکامل یافته زبان  BCPL است که طراح آن مارتین ریچاردز می‌باشد ، زبان BCPL نیز از زبان B  مشتق شده است که طراح آن  کن تامسون بود . (خداوند روح دنیس ریچی را همچون هوگو چاوز با مسیح بازگرداند ! ...) .
از این زبان برای نوشتن برنامه‌های سیستمی ، همچون سیستم عامل ، کامپایلر ، مفسر ، ویرایشگر ، برنامه‌های مدیریت بانک اطلاعاتی ، اسمبلر استفاده میکنند .
زبان C  برای اجرای بسیاری از دستوراتش از توابع کتابخانه ای استفاده میکند و بیشتر خصوصیات وابسته به سخت افزار را به این توابع واگذار میکند لذا نرم افزار تولید شده با این زبان به سخت افزار خاصی بستگی ندارد و با اندکی تغییرات می‌توانیم نرم افزار مورد نظر را روی ماشینهای متفاوت اجرا کنیم ، در نتیجه برنامه نوشته شده با C  قابلیت انتقال (Portability) دارند . بعلاوه کاربر میتواند توابع کتابخانه ای خاص خودش را بنویسد و از آنها در برنامه هایش استفاده کند .
برنامه‌های مقصدی که توسط کامپیلرهای C  ساخته میشود بسیار فشرده و کم حجم‌تر از برنامه‌های مشابه در سایر زبانهاست ، این امر باعث افزایش سرعت اجرای آنها میشود .
++C  که از نسل  C  است تمام ویژگیهای ذکر شده بالا را دارد ، علاوه بر آن شی گرا نیز می‌باشد . برنامه‌های شی گرا منظم و ساخت یافته اند و قابل آپدیت هستند و به سهولت تغییر و بهبود می‌یابند و قابلیت اطمینان و پایداری بیشتری دارند .

تحلیل  اولین پروژه :



در اولین پروژه  کد فوق را بکار بردیم ، حال به شرح دستورات آن می‌پردازیم .
#include <iostream>
دستوراتی که علامت # پیش از آنها قرار میگیرد ، دستورات راهنمای پیش پردازنده هستند . این خط یک دستور پیش پردازنده است که توسط پیش پردازنده و قبل از شروع کامپایل ، پردازش میشود . این کد فایل iostream  را به برنامه اضافه میکند . کتابخانه استاندارد  ++C  به چندین بخش تقسیم شده است و هر بخش فایل سرآیند خود را دارد . دلیل قرار گرفتن این دستور در ابتدای برنامه این است  که ، پیش از استفاده از هر تابع و فراخوانی کردن آن در برنامه ، کامپایلر لازم است اطلاعاتی در مورد آن تابع داشته باشد .  در خط کد بالا فایل سرآیند  iostream  استفاده نمودیم زیرا شامل توابع مربوط به I/O  ( ورودی / خروجی ) می‌باشد .

int  main()
{
 
   return 0;
}
دستور فوق بخشی از هر برنامه ++C  است ، main  تابع اصلی هر برنامه ++C است که شروع برنامه از آنجا آغاز می‌شود . کلمه  int  در ابتدای این خط ، مشخص میکند که تابع main پس از اجرا و به عنوان مقدار برگشتی (;return 0)  یک عدد صحیح باز می‌گرداند .

std::cout<<"Hello world ...\n";
دستور فوق یک رشته را در خروجی استاندارد که معمولا صفحه نمایش می‌باشد ارسال میکند . std  یک فضای نام است . فضای نام محدوده ای است که چند موجودیت در آن تعریف شده است . مثلا موجودیت cout  در فضای نام std در فایل سرآیند iostream تعریف شده است .


در زبان ++C  هر دستور به ; (سیموکالن) ختم میشود .


 

مطالب
بازیابی پایگاه داده (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 کامیت شده است عملی صورت نمی‌گیرد. 


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

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت یازدهم - پیاده سازی پویای Decoratorها توسط Castle.Core
در قسمت قبل، نحوه‌ی پیاده سازی الگوی Decorator را با استفاده از امکانات تزریق وابستگی‌های NET Core. بررسی کردیم؛ اما ... این روزها کسی Decoratorها را دستی ایجاد نمی‌کند. یعنی اگر قرار باشد به ازای هر کلاسی و هر سرویسی، یکبار کلاس Decorator آن‌را با پیاده سازی همان اینترفیس سرویس اصلی و فراخوانی دستی تک تک متدهای سرویس اصلی تزریق شده‌ی در سازنده‌ی آن انجام دهیم، آنچنان کاربردی به نظر نمی‌رسد. به همین منظور کتابخانه‌هایی تحت عنوان Dynamic Proxy تهیه شده‌اند تا کار ساخت و پیاده سازی پویای Decorator‌ها را انجام دهند. در این بین ما فقط منطق برای مثال مدیریت استثناءها، لاگ کردن ورودی‌ها و خروجی‌های متدها، کش کردن خروجی متدها، سعی مجدد اجرای متدهای با شکست مواجه شده و ... تک تک متدهای یک سرویس را به آن‌ها معرفی می‌کنیم و سپس پروکسی‌های پویا، کار محصور سازی خودکار اشیاء ساخته شده‌ی از سرویس اصلی را برای ما انجام می‌دهند. این روش نه فقط کار نوشتن دستی Decorator کلاس‌ها را حذف می‌کند، بلکه عمومی‌تر نیز بوده و به تمام سرویس‌ها قابل اعمال است.


Interceptors پایه‌ی پروکسی‌های پویا هستند

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

با اضافه کردن Interception به پروسه وهله سازی سرویس‌ها توسط یک IoC Container، مراحل کار اینبار به صورت زیر تغییر می‌کنند:
الف) در اینجا نیز در ابتدا فراخوان، درخواست وهله‌ای را بر اساس اینترفیسی خاص، به IOC Container ارائه می‌دهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده را بر اساس تنظیمات اولیه‌ی خود می‌کند.
ج) اما در این حالت IOC Container تشخیص می‌دهد نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه می‌دهد.
د) در  ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت می‌دهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Decorator را سبب خواهد شد.

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


پیاده سازی پروکسی‌های پویا توسط کتابخانه‌ی Castle.Core در برنامه‌های NET Core.

در ادامه از کتابخانه‌ی بسیار معروف Castle.Core برای پیاده سازی پروکسی‌های پویا استفاده خواهیم کرد. از این کتابخانه در پروژه‌ی EF Core، برای پیاده سازی Lazy loading نیز استفاده شده‌است.
برای دریافت آن یکی از دستورات زیر را اجرا نمائید:
> Install-Package Castle.Core
> dotnet add package Castle.Core
و یا به صورت خلاصه برای افزودن آن، فایل csproj برنامه به صورت زیر تغییر می‌کند:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="castle.core" Version="4.3.1" />
  </ItemGroup>
</Project>


تبدیل ExceptionHandlingDecorator مثال قسمت قبل، به یک Interceptor مخصوص Castle.Core

در قسمت قبل، کلاس MyTaskServiceDecorator را جهت اعمال یک try/catch به همراه logging، به متد Run سرویس MyTaskService، تهیه کردیم. در اینجا قصد داریم نگارش عمومی‌تر این تزئین کننده را با طراحی یک Interceptor مخصوص Castle.Core انجام دهیم:
using System;
using Castle.DynamicProxy;
using Microsoft.Extensions.Logging;

namespace CoreIocSample02.Utils
{
    public class ExceptionHandlingInterceptor : IInterceptor
    {
        private readonly ILogger<ExceptionHandlingInterceptor> _logger;

        public ExceptionHandlingInterceptor(ILogger<ExceptionHandlingInterceptor> logger)
        {
            _logger = logger;
        }

        public void Intercept(IInvocation invocation)
        {
            try
            {
                invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت می‌گیرد
            }
            catch (Exception ex)
            {
                _logger.LogCritical(ex, "An unhandled exception has been occurred.");
            }
        }
    }
}
برای تهیه‌ی یک کلاس Interceptor، کار با پیاده سازی اینترفیس IInterceptor شروع می‌شود. در اینجا فراخوانی متد ()invocation.Proceed، دقیقا معادل فراخوانی متد اصلی سرویس است؛ شبیه به کاری که در قسمت قبل انجام دادیم:
        public void Run()
        {
            try
            {
                _decorated.Run();
            }
            catch (Exception ex)
            {
                _logger.LogCritical(ex, "An unhandled exception has been occurred.");
            }
        }
فراخوان، یک نمونه‌ی تزئین شده‌ی از سرویس درخواستی را دریافت می‌کند. زمانیکه متد Run این نمونه‌ی ویژه را اجرا می‌کند، در حقیقت وارد متد Run این Decorator شده‌است که اینبار در پشت صحنه، توسط Dynamic proxy ما به صورت پویا ایجاد می‌شود. اکنون جائیکه ()invocation.Proceed فراخوانی می‌شود، دقیقا معادل همان ()decorated.Run_ قسمت قبل است؛ یا همان اجرای متد اصلی سرویس مدنظر. اینجا است که می‌توان منطق‌های سفارشی را مانند لاگ کردن، کش کردن، سعی مجدد در اجرا و بسیاری از حالات دیگر، پیاده سازی کرد.


اتصال ExceptionHandlingInterceptor تهیه شده به سیستم تزریق وابستگی‌ها

در ادامه روش معرفی ExceptionHandlingInterceptor تهیه شده را به سیستم تزریق وابستگی‌ها، توسط متد Decorate کتابخانه‌ی Scrutor که آن‌را در قسمت قبل بررسی کردیم، ملاحظه می‌کنید:
namespace CoreIocSample02
{
    public class Startup
    {
        private static readonly ProxyGenerator _dynamicProxy = new ProxyGenerator();

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITaskService, MyTaskService>();
            services.AddTransient<ExceptionHandlingInterceptor>();
            services.Decorate(typeof(ITaskService),
             (target, serviceProvider) =>
                _dynamicProxy.CreateInterfaceProxyWithTargetInterface(
                  interfaceToProxy: typeof(ITaskService),
                  target: target,
                  interceptors: serviceProvider.GetRequiredService<ExceptionHandlingInterceptor>())
            );
- ProxyGenerator به همین نحو static readonly باید تعریف شود و در کل برنامه یک وهله از آن مورد نیاز است.
- سپس نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود.
- در ادامه توسط متد الحاقی Decorate، کار تزئین ITaskService را با یک dynamicProxy که ExceptionHandlingInterceptor را به صورت پویا تبدیل به یک Decorator کرده و بر روی تک تک متدهای سرویس ITaskService اجرا می‌کند، انجام می‌دهیم.
- کاری که Scrutor در اینجا انجام می‌دهد، یافتن سرویس ITaskService معرفی شده‌ی پیشین و تعویض آن با dynamicProxy می‌باشد. بنابراین نیاز است تعریف services.AddTransient، پیش از تعریف services.Decorate انجام شده باشد.

یک نکته: چون ExceptionHandlingInterceptor دارای پارامتر تزریق شده‌ای در سازنده‌ی آن است، بهتر است خود آن‌را نیز به صورت یک سرویس ثبت کنیم و سپس وهله‌ای از آن‌را از طریق serviceProvider.GetRequiredService در قسمت interceptors متد CreateInterfaceProxyWithTargetInterface معرفی کنیم تا نیازی به مقدار دهی دستی تک تک پارامترهای سازنده‌ی آن نباشد.

اکنون اگر برنامه را اجرا کنیم و برای مثال ITaskService را در سازنده‌ی یک کنترلر تزریق کنیم، بجای دریافت وهله‌ای از کلاس MyTaskService، اینبار وهله‌ای از Castle.Proxies.ITaskServiceProxy را دریافت می‌کنیم.


به این معنا که Castle.Core به صورت پویا وهله‌ی سرویس MyTaskService را داخل یک Castle.Proxies پیچیده‌است و از این پس ما از طریق این واسط، با سرویس اصلی MyTaskService ارتباط برقرار خواهیم کرد. برای درک بهتر این مراحل، بر روی سازنده‌ی کلاس کنترلر و همچنین متد Intercept، تعدادی break-point را قرار دهید.
مطالب
React 16x - قسمت 22 - ارتباط با سرور - بخش 1 - برپایی تنظیمات
هر برنامه‌ی وب، دارای یک frontend و یک backend است. تا اینجا، تمام تمرکز این سری، بر روی پیاده سازی frontend بود و هیچکدام از برنامه‌هایی را که تکمیل کردیم، تبادل اطلاعاتی را با وب سرویس‌های backend نداشتند؛ اما به عنوان یک توسعه دهنده‌ی React، نیاز است با نحوه‌ی ارتباط با سرور آشنایی داشت که در طی چند قسمت به آن می‌پردازیم.


ایجاد برنامه‌ی backend ارائه دهنده‌ی REST API

در اینجا یک برنامه‌ی ساده‌ی ASP.NET Core Web API را جهت تدارک سرویس‌های backend، مورد استفاده قرار می‌دهیم. هرچند این مورد الزامی نبوده و اگر علاقمند بودید که مستقل از آن کار کنید، حتی می‌توانید از سرویس آنلاین JSONPlaceholder نیز برای این منظور استفاده کنید که یک Fake Online REST API است. کار آن ارائه‌ی یک سری endpoint است که به صورت عمومی از طریق وب قابل دسترسی هستند. می‌توان به این endpintها درخواست‌های HTTP خود را مانند GET/POST/DELETE/UPDATE ارسال کرد و از آن اطلاعاتی را دریافت نمود و یا تغییر داد. به هر کدام از این endpointها یک API گفته می‌شود که جهت آزمایش برنامه‌ها بسیار مناسب هستند. برای نمونه در قسمت resources آن اگر به آدرس https://jsonplaceholder.typicode.com/posts مراجعه کنید، می‌توان لیستی از مطالب را با فرمت JSON مشاهده کرد. کار آن ارائه‌ی آرایه‌ای از اشیاء جاوا اسکریپتی قابل استفاده‌ی در برنامه‌های frontend است. بنابراین زمانیکه یک HTTP GET را به این endpoint ارسال می‌کنیم، آرایه‌ای از اشیاء مطالب را دریافت خواهیم کرد. همین endpoint، امکان تغییر این اطلاعات را توسط برای مثال HTTP Delete نیز میسر کرده‌است.

اگر علاقمندید بودید می‌توانید از JSONPlaceholder استفاده کنید و یا در ادامه دقیقا ساختار همین endpoint ارائه‌ی مطالب آن‌را با ASP.NET Core Web API نیز پیاده سازی می‌کنیم (برای مطالعه‌ی قسمت «ارتباط با سرور» اختیاری است و از هر REST API مشابهی که توسط nodejs یا PHP و غیره تولید شده باشد نیز می‌توان استفاده کرد):

مدل مطالب
namespace sample_22_backend.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        public int UserId { set; get; }
    }
}
ساختار این مدل، با ساختار مدل مطالب JSONPlaceholder یکی درنظر گرفته شده‌است، تا مطلب قابلیت پیگیری بیشتری را پیدا کند.


منبع داده‌ی فرضی مطالب

برای ارائه‌ی ساده‌تر برنامه، یک منبع داده‌ی درون حافظه‌ای را به همراه یک سرویس، در اختیار کنترلر مطالب، قرار می‌دهیم:
using System;
using System.Collections.Generic;
using System.Linq;
using sample_22_backend.Models;

namespace sample_22_backend.Services
{
    public interface IPostsDataSource
    {
        List<Post> GetAllPosts();
        bool DeletePost(int id);
        Post AddPost(Post post);
        bool UpdatePost(int id, Post post);
        Post GetPost(int id);
    }

    /// <summary>
    /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
    /// </summary>
    public class PostsDataSource : IPostsDataSource
    {
        private readonly List<Post> _allPosts;

        public PostsDataSource()
        {
            _allPosts = createDataSource();
        }

        public List<Post> GetAllPosts()
        {
            return _allPosts;
        }

        public Post GetPost(int id)
        {
            return _allPosts.Find(x => x.Id == id);
        }

        public bool DeletePost(int id)
        {
            var item = _allPosts.Find(x => x.Id == id);
            if (item == null)
            {
                return false;
            }

            _allPosts.Remove(item);
            return true;
        }

        public Post AddPost(Post post)
        {
            var id = 1;
            var lastItem = _allPosts.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }

            post.Id = id;
            _allPosts.Add(post);
            return post;
        }

        public bool UpdatePost(int id, Post post)
        {
            var item = _allPosts
                .Select((pst, index) => new { Item = pst, Index = index })
                .FirstOrDefault(x => x.Item.Id == id);
            if (item == null || id != post.Id)
            {
                return false;
            }

            _allPosts[item.Index] = post;
            return true;
        }

        private static List<Post> createDataSource()
        {
            var list = new List<Post>();
            var rnd = new Random();
            for (var i = 1; i < 10; i++)
            {
                list.Add(new Post { Id = i, UserId = rnd.Next(1, 1000), Title = $"Title {i} ...", Body = $"Body {i} ..." });
            }
            return list;
        }
    }
}
در این سرویس، نیازمندی‌های کنترلر مطالب مانند ارائه لیست تمام مطالب، نمایش اطلاعات یک مطلب، به روز رسانی، ایجاد و حذف یک مطلب، تدارک دیده شده‌اند. سپس از این سرویس در کنترلر زیر استفاده می‌کنیم:


کنترلر Web API برنامه‌ی backend

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using sample_22_backend.Models;
using sample_22_backend.Services;

namespace sample_22_backend.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class PostsController : ControllerBase
    {
        private readonly IPostsDataSource _postsDataSource;

        public PostsController(IPostsDataSource postsDataSource)
        {
            _postsDataSource = postsDataSource;
        }

        [HttpGet]
        public ActionResult<List<Post>> GetPosts()
        {
            return _postsDataSource.GetAllPosts();
        }

        [HttpGet("{id}")]
        public ActionResult<Post> GetPost(int id)
        {
            var post = _postsDataSource.GetPost(id);
            if (post == null)
            {
                return NotFound();
            }
            return Ok(post);
        }

        [HttpDelete("{id}")]
        public ActionResult DeletePost(int id)
        {
            var deleted = _postsDataSource.DeletePost(id);
            if (deleted)
            {
                return Ok();
            }
            return NotFound();
        }

        [HttpPost]
        public ActionResult<Post> CreatePost([FromBody]Post post)
        {
            post = _postsDataSource.AddPost(post);
            return CreatedAtRoute(nameof(GetPost), new { post.Id }, post);
        }

        [HttpPut("{id}")]
        public ActionResult<Post> UpdatePost(int id, [FromBody]Post post)
        {
            var updated = _postsDataSource.UpdatePost(id, post);
            if (updated)
            {
                return Ok(post);
            }
            return NotFound();
        }
    }
}
این کنترلر که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است که در ادامه، در برنامه‌ی React خود از آن‌ها استفاده خواهیم کرد. پس از ایجاد این پروژه‌ی web api، یک نمونه خروجی آن در مسیر https://localhost:5001/api/posts، به صورت زیر خواهد بود:


البته نمایش فرمت شده‌ی JSON در مرورگر کروم، نیاز به نصب این افزونه را دارد.


ایجاد ساختار ابتدایی برنامه‌ی ارتباط با سرور

در اینجا برای بررسی کار با سرور، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-22-frontend
> cd sample-22-frontend
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

سپس فایل app.js را به شکل زیر تکمیل می‌کنیم:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    posts: []
  };

  handleAdd = () => {
    console.log("Add");
  };

  handleUpdate = post => {
    console.log("Update", post);
  };

  handleDelete = post => {
    console.log("Delete", post);
  };

  render() {
    return (
      <React.Fragment>
        <button className="btn btn-primary mt-1 mb-1" onClick={this.handleAdd}>
          Add
        </button>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Update</th>
              <th>Delete</th>
            </tr>
          </thead>
          <tbody>
            {this.state.posts.map(post => (
              <tr key={post.id}>
                <td>{post.title}</td>
                <td>
                  <button
                    className="btn btn-info btn-sm"
                    onClick={() => this.handleUpdate(post)}
                  >
                    Update
                  </button>
                </td>
                <td>
                  <button
                    className="btn btn-danger btn-sm"
                    onClick={() => this.handleDelete(post)}
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default App;
که حاصل آن، یک دکمه، برای افزودن مطلبی جدید، به همراه جدولی است از مطالب که قصد داریم در ادامه، اطلاعات آن‌را از سرور دریافت کرده و حذف و یا به روز رسانی کنیم:



نگاهی به انواع و اقسام HTTP Client‌های مهیا

در ادامه نیاز خواهیم داشت تا از طریق برنامه‌های React خود، درخواست‌های HTTP را به سمت سرور (یا همان برنامه‌ی backend) ارسال کنیم، تا بتوان اطلاعاتی را از آن دریافت کرد و یا تغییری را در اطلاعات موجود، ایجاد نمود. همانطور که پیشتر نیز در این سری عنوان شد، React برای این مورد نیز راه‌حل توکاری را به همراه ندارد و تنها کار آن، رندر کردن View و مدیریت DOM است. البته شاید این مورد یکی از مزایای کار با React نیز باشد! چون در این حالت می‌توانید از کتابخانه‌هایی که خودتان ترجیح می‌دهید، نسبت به کتابخانه‌هایی که به شما ارائه/تحمیل (!) می‌شوند (مانند برنامه‌های Angular) آزادی انتخاب کاملی را داشته باشید. برای مثال هرچند Angular به همراه یک HTTP Module توکار است، اما تاکنون چندین بار بازنویسی از ابتدا شده‌است! ابتدا با یک کتابخانه‌ی HTTP مقدماتی شروع کردند. بعدی آن‌را منسوخ شده اعلام و با یک ماژول جدید جایگزین کردند. بعد در نگارشی دیگر، چون این کتابخانه وابسته‌است به RxJS و خود RxJS نیز بازنویسی کامل شد، روش کار کردن با این HTTP Module نیز مجددا تغییر پیدا کرد! بنابراین اگر با Angular کار می‌کنید، باید کارها را آنگونه که Angular می‌پسندد، انجام دهید؛ اما در اینجا خیر و آزادی انتخاب کاملی برقرار است.
بنابراین اکنون این سؤال مطرح می‌شود که در React، برای برقراری ارتباط با سرور، چه باید کرد؟ در اینجا آزاد هستید برای مثال از Fetch API جدید مرورگرها و یا روش Ajax ای مبتنی بر XML قدیمی‌تر آن‌ها، استفاده کنید (اطلاعات بیشتر) و یا حتی اگر علاقمند باشید می‌توانید از محصور کننده‌های آن مانند jQuery Ajax استفاده کنید. بنابراین اگر با jQuery Ajax راحت هستید، به سادگی می‌توانید از آن در برنامه‌های React نیز استفاده کنید. اما ... ما در اینجا از یک کتابخانه‌ی بسیار محبوب و قدرتمند HTTP Client، به نام Axios (اکسیوس/ یک واژه‌ی یونانی به معنای «سودمند») استفاده خواهیم کرد که فقط تعداد بار دانلود هفتگی آن، 6 میلیون بار است!


نصب Axios در برنامه‌ی React این قسمت

برای نصب کتابخانه‌ی Axios، در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
پس از برپایی این مقدمات، ادامه‌ی مطلب «ارتباط با سرور» را در قسمت بعدی پیگیری می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-frontend-part-01.zip و sample-22-backend-part-01.zip