- ضمنا افزونه HTMLWorker این کتابخانه منسوخ شده (مطلب جاری) و به XMLWorker ارتقاء یافته.
- ضمنا افزونه HTMLWorker این کتابخانه منسوخ شده (مطلب جاری) و به XMLWorker ارتقاء یافته.
using System; using System.Collections; class ProgrArrayListExample { static void Main() { ArrayList list = new ArrayList(); list.Add("Hello"); list.Add(5); list.Add(3.14159); list.Add(DateTime.Now); for (int i = 0; i < list.Count; i++) { object value = list[i]; Console.WriteLine("Index={0}; Value={1}", i, value); } } }
Index=0; Value=Hello Index=1; Value=5 Index=2; Value=3.14159 Index=3; Value=29.02.2015 23:17:01
ArrayList list = new ArrayList(); list.Add(2); list.Add(3.5f); list.Add(25u); list.Add(" ریال"); dynamic sum = 0; for (int i = 0; i < list.Count; i++) { dynamic value = list[i]; sum = sum + value; } Console.WriteLine("Sum = " + sum); // Output: Sum = 30.5ریال
GenericType<T> instance = new GenericType<T>();
List<int> intList = new List<int>(); List<bool> boolList = new List<bool>(); List<double> realNumbersList = new List<double>();
List<int> intList = new List<int>();
استفاده از لیست پیوندی برای پیاده سازی پشته:
Stack<string> stack = new Stack<string>(); stack.Push("A"); stack.Push("B"); stack.Push("C"); while (stack.Count > 0) { string letter= stack.Pop(); Console.WriteLine(letter); } //خروجی //C //B //A
ابتدای آرایه مکانی است که عنصر از آنجا برداشته میشود و Head به آن اشاره میکند و tail هم به انتهای آرایه که جهت درج عنصر جدید مفید است. با برداشتن هر خانهای که head به آن اشاره میکند، head یک خانه به سمت جلو حرکت میکند و زمانی که Head از tail بیشتر شود، یعنی اینکه دیگر عنصری یا المانی در صف وجود ندارد و head و Tail به ابتدای صف حرکت میکنند. در این حالت موقعی که المان جدیدی قصد اضافه شدن داشته باشد، افزودن، مجددا از اول صف آغاز میشود و به این صفها، صف حلقوی میگویند.
عملیات اصلی صف دو مورد هستند enqueue که المان جدید را در انتهای صف قرار میدهد و dequeue اولین المان صف را بیرون میکشد.
پیاده سازی صف به صورت پویا با لیستهای پیوندی
برای پیاده سازی صف، لیستهای پیوندی یک طرفه کافی هستند:
در این حالت عنصر جدید مثل سابق به انتهای لیست اضافه میشود و برای حذف هم که از اول لیست کمک میگیریم و با حذف عنصر اول، متغیر Head به عنصر یا المان دوم اشاره خواهد کرد.
کلاس از پیش آمده صف در دات نت <Queue<T است و نحوهی استفاده آن بدین شکل است:
static void Main() { Queue<string> queue = new Queue<string>(); queue.Enqueue("Message One"); queue.Enqueue("Message Two"); queue.Enqueue("Message Three"); queue.Enqueue("Message Four"); while (queue.Count > 0) { string msg = queue.Dequeue(); Console.WriteLine(msg); } } //خروجی //Message One //Message Two //Message Thre //Message Four
using System; using System.Collections; using System.Linq; namespace Framework.Model { public interface IContext { T Get<T>(Func<T, bool> prediction) where T : class; IEnumerable List<T>(Func<T, bool> prediction) where T : class; void Insert<T>(T entity) where T : class; int Save(); } }
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Text; namespace Framework.Model { public class Context : IContext { private readonly DbContext _dbContext; public Context(DbContext context) { _dbContext = context; } public T Get<T>(Func<T,bool> prediction) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet!= null) return dbSet.Single(prediction); throw new Exception(); } public void Insert<T>(T entity) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet != null) { _dbContext.Entry(entity).State = EntityState.Added; } } public int Save() { return _dbContext.SaveChanges(); } IEnumerable IContext.List<T>(Func<T, bool> prediction) { var dbSet = _dbContext.Set<T>(); if (dbSet != null) return dbSet.Where(prediction).ToList(); throw new Exception(); } } }
using System.Data.Entity; using DataModel; namespace Model { public class EFContext : DbContext { public EFContext(string db): base(db) { } public DbSet<Product> Products { get; set; } } }
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; namespace Model { public class Context : Framework.Model.Context { public Context(string db): base(new EFContext(db)) { } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Biz { public class Context : Model.Context { public Context(string db) : base(db) { } } }
using System.Web.Mvc; using Framework.Model; namespace ProductionRepository.Controllers { public class BaseController : Controller { public IContext DataContext { get; set; } public BaseController() { DataContext = new Biz.Context(System.Configuration.ConfigurationManager.ConnectionStrings["Database"].ConnectionString); } } }
using System.Web.Mvc; using DataModel; using System.Collections.Generic; namespace ProductionRepository.Controllers { public class ProductController : BaseController { public ActionResult Index() { var x = DataContext.List<Product>(s => s.Name != null); return View(x); } } }
using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Mvc; namespace TestUnit { [TestFixture] public class Test { [Test] public void IndexShouldListProduct() { var repo = new Moq.Mock<Framework.Model.IContext>(); var products = new List<DataModel.Product>(); products.Add(new DataModel.Product { Id = 1, Name = "asdasdasd" }); products.Add(new DataModel.Product { Id = 2, Name = "adaqwe" }); products.Add(new DataModel.Product { Id = 4, Name = "qewqw" }); products.Add(new DataModel.Product { Id = 5, Name = "qwe" }); repo.Setup(x => x.List<DataModel.Product>(p => p.Name != null)).Returns(products.AsEnumerable()); var controller = new ProductionRepository.Controllers.ProductController(); controller.DataContext = repo.Object; var result = controller.Index() as ViewResult; var model = result.Model as List<DataModel.Product>; Assert.AreEqual(4, model.Count); Assert.AreEqual("", result.ViewName); } } }
Microsoft.AspNet.Identity.Core Microsoft.AspNet.Identity.EntityFrameWork
namespace MyShop.Models { // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. public class ApplicationUser : IdentityUser { } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } }
namespace MyShop.Models { // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. public class ApplicationUser : IdentityUser { [Display(Name = "نام انگلیسی")] public string EnglishName { get; set; } [Display(Name = "نام سیستمی")] public string NameInSystem { get; set; } [Display(Name = "نام فارسی")] public string PersianName { get; set; } [Required] [DataType(DataType.EmailAddress)] [Display(Name = "آدرس ایمیل")] public string Email { get; set; } } }
namespace MyShop.Models { public class AuthorProduct { [Key] public int AuthorProductId { get; set; } /* public int UserId { get; set; }*/ [Display(Name = "User")] public string ApplicationUserId { get; set; } public int ProductID { get; set; } public virtual Product Product { get; set; } public virtual ApplicationUser ApplicationUser { get; set; } } }
namespace MyShop.Models { [DisplayName("محصول")] [DisplayPluralName("محصولات")] public class Product { [Key] public int ProductID { get; set; } [Display(Name = "گروه محصول")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public int ProductGroupID { get; set; } [Display(Name = "مدت زمان")] public string Duration { get; set; } [Display(Name = "نام تهیه کننده")] public string Producer { get; set; } [Display(Name = "عنوان محصول")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public string ProductTitle { get; set; } [StringLength(200)] [Display(Name = "کلید واژه")] public string MetaKeyword { get; set; } [StringLength(200)] [Display(Name = "توضیح")] public string MetaDescription { get; set; } [Display(Name = "شرح محصول")] [UIHint("RichText")] [AllowHtml] public string ProductDescription { get; set; } [Display(Name = "قیمت محصول")] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")] [UIHint("Integer")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public int ProductPrice { get; set; } [Display(Name = "تاریخ ثبت محصول")] public DateTime? RegisterDate { get; set; } } }
protected override void Seed(MyShop.Models.ApplicationDbContext context) { context.Users.AddOrUpdate(u => u.Id, new ApplicationUser() { Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }, new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }, new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }, new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" } ); context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId, new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" }, new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" }, new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" } );
if (!context.Users.Where(u => u.UserName == "Admin").Any()) { var roleStore = new RoleStore<IdentityRole>(context); var rolemanager = new RoleManager<IdentityRole>(roleStore); var userstore = new UserStore<ApplicationUser>(context); var usermanager = new UserManager<ApplicationUser>(userstore); var user = new ApplicationUser {UserName = "Admin"}; usermanager.Create(user, "121212"); rolemanager.Create(new IdentityRole {Name = "admin"}); usermanager.AddToRole(user.Id, "admin"); }
مقادیر پایه (Primitive Values) و ارجاعی (Reference Values)
در جاوا اسکریپت، متغیرها شامل دادههایی از نوع پایه و یا ارجاعی میباشند. مقادیر String ، Number ، Boolean ، Null و Undefined به عنوان مقادیر پایه محسوب میگردند. در این نوع متغیرها تغییرات، مستقیما بر روی مقدار ذخیره شده در متغیر اعمال میشوند. اشیاء نیز که از مجموعهای از مقادیر پایه ساخته شدهاند، مقادیر ارجاعی نامیده میشوند. در این نوع مقادیر، شما به اشارهگری از شیء دسترسی دارید. بنابراین تغییرات مستقیما بر روی دادههای ذخیره شده اعمال نمیشوند. به مثالهای زیر توجه کنید:
var num1 = 10; var num2 = num1; alert("Num1=" + num1 + ", Num2=" + num2); num2 = 20; alert("Num1=" + num1 + ", Num2=" + num2); num1 = 30; alert("Num1=" + num1 + ", Num2=" + num2);
خروجی :
"Num1=10, Num2=10"
"Num1=10, Num2=20"
"Num1=30, Num2=20"
همانطور که از خروجی مثال فوق پیداست، در انتساب num1 به num2 ، مقدار num1 در num2 کپی شدهاست. بنابراین تغییراتی که بر روی num1 یا num2 صورت میگیرد، مستقیما بر روی مقدار ذخیره شده در هر یک از این متغیرها تاثیر میگذارد. رفتار مقادیر پایه همیشه به همین صورت میباشد.
var obj1 = new Object(); obj1.num = 10; var obj2 = obj1; alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num); obj2.num = 20; alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num); obj1.num = 30; alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num);
خروجی :
"Obj1.Num=10, Obj2.Num=10"
"Obj1.Num=20, Obj2.Num=20"
"Obj1.Num=30, Obj2.Num=30"
با استفاده از نوع ارجاعی Object میتوانیم اشیاء جدیدی را ایجاد کنیم و ویژگیهایی را به صورت پویا به آنها اختصاص دهیم. همانطور که قبلا گفته شد، اشیاء از نوع ارجاعی میباشند و حاوی اشارهگری به مقادیر ذخیره شده میباشند. بنابراین انتساب obj1 به obj2 به معنای انتساب اشارهگر obj1 به obj2 میباشد. به عبارتی دیگر obj2 به همانجایی اشاره میکند که obj1 نیز اشاره مینماید. پس هر تغییری که بر روی ویژگیهای obj1 رخ دهد، obj2 نیز تاثیرات آن را میبیند و بالعکس. همانطور که در خروجی مشاهده مینمایید، در مرحلهی اول obj1 به obj2 نسبت داده شد، پس مقدار ویژگی num برای هر دو آنها یکسان میباشد. در مرحلهی دوم، مقدار ویژگی num را در obj2 تغییر دادیم؛ ولی مقدار این ویژگی، در obj1 نیز تغییر نمود. در مرحلهی سوم نیز همان اتفاقات مرحلهی دوم تکرار شد.
با توجه به مثالهای فوق قطعا به تفاوتهای مقادیر پایه و ارجاعی پی بردید. همچنین در یک نمونهی کوچک و ساده نیز، یکی از روشهای ایجاد شیء را که استفاده از نوع ارجاعی Object میباشد، مشاهده نمودید. این دانستهها مقدمه ای بر شروع برنامه نویسی شیء گرا میباشند. ولی قبل از شروع برنامه نویسی شیء گرا در جاوا اسکریپت، به بررسی نکات و تکنیکهای دیگری میپردازیم.
توجه:
به انواع پایه، انواع دادهای مقداری یا اولیه نیز گفته میشود.
فراخوانی با مقدار (Call by Value)
در این نوع فراخوانی، آرگومانها از نوع مقادیر پایه هستند. بنابراین هر تغییری که بر روی آرگومانها در تابع رخ دهد، هیچ تاثیری بر روی آرگومانهای ارسالی در زمان فراخوانی تابع ندارند. به مثال زیر توجه کنید:
function primitive(a, b) { a += 100; b += 200; alert("a=" + a + ", b=" + b); } var x = 300, y = 400; primitive(x, y); alert("x=" + x + ", y=" + y);
خروجی :
"a=400, b=600"
"x=300, y=400"
x و y دو متغیر پایه میباشند، بنابراین تابع فوق به صورت مقداری فراخوانی شدهاست. یعنی مقدار آرگومانهای x و y در آرگومانهای a و b کپی میشوند. پس هر تغییری که بر روی a و b رخ دهد، هیچ تاثیری بر روی x و y ندارد. همچنین با توجه به توضیحی که در مورد مقادیر پایه داده شد، تغییرات مستقیما بر روی دادهی ذخیره شده در متغیر اعمال میشود. بنابراین تغییراتی که در تابع فوق بر روی a و b رخ داد، مستقیما مقادیر a و b را تغییر دادهاست وهیچ ارتباطی به x و y ندارد. البته توجه داشته باشید که حتی اگر نام آرگومانهای تابع با آرگومانهای ارسالی یکسان بود و یا حتی اگر تابع مقداری را به عنوان خروجی بر میگرداند، هیچ تفاوتی را در خروجی برنامه فوق مشاهده نمیکردید.
فراخوانی با ارجاع (Call by Reference)
در این نوع فراخوانی، آرگومانها از نوع مقادیر ارجاعی هستند. بنابراین هر تغییری که بر روی آرگومانها در تابع رخ دهد، بر روی آرگومانهای ارسالی در زمان فراخوانی تابع نیز تاثیر میگذارند. به مثال زیر توجه کنید:
function reference(obj) { obj.a += 100; obj.b += 200; alert("obj.a=" + obj.a + ", obj.b=" + obj.b); } var calc = new Object(); calc.a = 300; calc.b = 400; reference(calc); alert("calc.a=" + calc.a + ", calc.b=" + calc.b);
خروجی :
"obj.a=400, obj.b=600"
"calc.a=400, calc.b=600"
calc یک مقدار ارجاعی است که به عنوان آرگومان ورودی به تابع ارسال میشود و اشارهگر خود را به obj اختصاص میدهد. بنابراین obj به همان آدرسی اشاره میکند که calc اشاره مینماید. پس هر تغییری که بر روی obj رخ دهد، calc نیز تاثیرات آن را مشاهده مینماید. همانطور که در خروجی نیز مشاهده مینمایید، تغییرات صورت گرفته در تابع به calc نیز منعکس شده است.
حوزه دسترسی به متغیرها (Variable Scope)
متغیرهای محلی (Local Variables)
در اکثر زبانهای برنامه نویسی، متغیرهایی که در یک بلاک کد تعریف میشوند، به صورت محلی و فقط در همان بلاک کد قابل دسترسی میباشند. به این متغیرها، متغیرهای محلی میگویند که با خاتمهی اجرای بلاک کد، از بین خواهند رفت و دیگر قابل دسترسی نمیباشند. اما در جاوا اسکریپت حوزهی اجرایی متغیرها کمی متفاوت میباشد. به مثال زیر توجه کنید:
for (var i = 1; i <= 5; i++) { var sqr = i * i; alert(sqr); } alert(i); alert(sqr);
خروجی :
متغیرهای i و sqr داخل حلقهی for تعریف شدهاند و منطقا نباید خارج از این حلقه قابل دسترسی باشند. ولی با توجه به خروجی فوق، مشاهده نمودید که متغیرهای i و sqr، نه تنها خارج از این حلقه قابل شناسایی میباشند، بلکه آخرین مقدار خود را نیز حفظ نمودهاند. در جاوا اسکریپت، یک متغیر محلی زمانی مفهوم پیدا میکند که در داخل یک تابع تعریف شود. به مثال زیر توجه کنید:
function sqr(num) { var sum = num * num; return sum; } var n = 4; alert(sqr(n)); alert(num); // Error: num is not defined alert(sum); // Error: sum is not defined
خروجی :
16
Error: num is not defined
Error: sum is not defined
همانطور که مشاهده میکنید، متغیرهای num و sum به صورت محلی در تابع فوق تعریف شدهاند؛ بنابراین خارج از تابع قابل دسترسی نمیباشند و موجب بروز خطا میگردند.
متغیرهای عمومی (Global Variables)
در جاوا اسکریپت، متغیرهایی که خارج از تابع تعریف میگردند، به شیء window نسبت داده میشوند و عمومی میباشند. به عبارتی دیگر این متغیرها به عنوان یک ویژگی از شیء window تعریف میشوند و در تمامی توابع و بلاکهای کد قابل دسترسی میباشند. به مثال زیر توجه کنید:
var color = "Red"; function setColor() { color = "Blue"; } alert(color); setColor(); alert(color);
خروجی :
"Red"
"Blue"
در مثال فوق، متغیر color به صورت عمومی تعریف شدهاست. بنابراین در کل برنامه قابل دسترسی میباشد. در alert اول مقدار فعلی متغیر color یعنی “Red” نمایش مییابد. سپس با فراخوانی تابع، مقدار این متغیر تغییر میکند. در alert دوم مقدار تغییر یافتهی متغیر color نمایش خواهد یافت. حال به مثال زیر توجه کنید:
var color = "Red"; function getColor() { var color = "Blue"; return color; } alert(color); alert(getColor()); alert(color);
خروجی :
"Red"
"Blue"
"Red"
در مثال فوق، ابتدا یک متغیر color به صورت عمومی یا Global تعریف شده است. در تابع getColor نیز یک متغیر color به صورت local یا محلی تعریف شده است. زمانی که در alert تابع getColor فراخوانی میشود، متغیر color مقداردهی میگردد. این مقداردهی برای متغیر محلی صورت گرفته است و هیچ ربطی به متغیر color که به صورت عمومی تعریف شده است ندارد.
جهت تعریف متغیر در جاوا اسکریپت، از کلمهی کلیدی var استفاده میشود. اما تعریف متغیر در جاوا اسکریپت اجباری نمیباشد و میتوان یک متغیر را مقداردهی نمود بدون آنکه تعریف شده باشد. در صورتی که متغیر با var اعلان نشود، آن متغیر به شیء window نسبت داده میشود و ماهیت عمومی پیدا میکند. به مثال زیر توجه کنید:
function sum(a, b) { c = a + b; } sum(20, 30); alert(c);
خروجی :
50
همانطور که مشاهده میکنید، متغیر c بدون تعریف شدن مورد استفاده قرار گرفته است. با اینکه به صورت محلی مقداردهی گردیده است، ولی چون توسط var اعلان نشده است، به شیء window نسبت داده شده و ماهیت عمومی پیدا کرده است.
body{ font-family:Tahoma,sans-serif; font-size:9pt; margin:0;padding:0; overflow-x:hidden; background:#fff; direction:rtl }
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // Captchafa demo for ASP.NET applications // by: Behrouz Rad // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= namespace Captcha { using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Web; public class Captchafa { private static readonly string PRIVATE_KEY = "your private key"; private static readonly string PUBLIC_KEY = "your public key"; private static readonly string CAPTCHAFA_API_SERVER = "http://www.captchafa.com/api"; private static readonly string CAPTCHAFA_VERIFY_SERVER = "http://www.captchafa.com/api/verify/"; private IDictionary<string, string> CaptchafaData { get { HttpContext httpContext = HttpContext.Current; string remoteIp = httpContext.Request.ServerVariables["REMOTE_ADDR"]; string challenge = httpContext.Request.Form["captchafa_challenge_field"]; string response = httpContext.Request.Form["captchafa_response_field"]; IDictionary<string, string> data = new Dictionary<string, string>() { {"privatekey" , PRIVATE_KEY }, {"remoteip" , remoteIp }, {"challenge" , challenge }, {"response" , response } }; return data; } } public static string CaptchafaGetHtml() { return string.Format("<script type=\"text/javascript\" src=\"{0}/?challenge&k={1}\"></script>", CAPTCHAFA_API_SERVER, PUBLIC_KEY); } public bool IsAnswerCorrect() { string dataToSend = this.CaptchafaPrepareDataToSend(this.CaptchafaData); string result = this.CaptchafaPostResponse(dataToSend); return result.StartsWith("true"); } private string CaptchafaPrepareDataToSend(IDictionary<string, string> data) { string result = string.Empty; StringBuilder sb = new StringBuilder(); foreach (var item in data) { sb.AppendFormat("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value.Replace(@"\", string.Empty))); } result = sb.ToString(); sb = null; result = result.Substring(0, result.LastIndexOf("&")); return result; } private string CaptchafaPostResponse(string data) { StreamReader reader = null; Stream dataStream = null; WebResponse response = null; string responseFromServer = string.Empty; try { WebRequest request = WebRequest.Create(CAPTCHAFA_VERIFY_SERVER); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; byte[] byteData = Encoding.UTF8.GetBytes(data); request.ContentLength = byteData.Length; dataStream = request.GetRequestStream(); dataStream.Write(byteData, 0, byteData.Length); dataStream.Close(); response = request.GetResponse(); dataStream = response.GetResponseStream(); reader = new StreamReader(dataStream); responseFromServer = reader.ReadToEnd(); } finally { if (reader != null) { reader.Close(); } if (dataStream != null) { dataStream.Close(); } if (response != null) { response.Close(); } } return responseFromServer; } } }
if (!IsPostBack) { litCaptcha.Text = Captchafa.CaptchafaGetHtml(); }
Captchafa captchaFa = new Captchafa(); bool isAnswerCorrect = captchaFa.IsAnswerCorrect(); if (isAnswerCorrect) { // پاسخ صحیح است } else { // پاسخ صحیح نیست }
public static class CaptchaHelper { public static MvcHtmlString Captchafa(this HtmlHelper htmlHelper) { return MvcHtmlString.Create(Captcha.Captchafa.CaptchafaGetHtml()); } }
[HttpPost] [ActionName("Index")] public ViewResult CaptchaCheck() { Captchafa captchaFa = new Captchafa(); bool isAnswerCorrect = captchaFa.IsAnswerCorrect(); if (isAnswerCorrect) { ViewBag.IsAnswerCorrect = true; } else { ViewBag.IsAnswerCorrect = false; } return View(); }
@using CaptchafaDemoMvc.Helpers; @{ ViewBag.Title = "Index"; } <form action="/" method="post"> @Html.Captchafa(); <input type="submit" id="btnCaptchafa" name="btnCaptchafa" value="آزمایش" /> @{ bool isAnswerExists = ViewBag.IsAnswerCorrect != null; } @if (isAnswerExists) { if ((bool)ViewBag.IsAnswerCorrect == true) { <span id="lblResult">پاسخ صحیح است</span> } else { <span id="lblResult">پاسخ صحیح نیست</span> } } </form>
در این مقاله آموزشی که یکی دیگر از سری مقالات آموزشی اصول و مبانی پایگاه داده پیشرفته میباشد، قصد داریم به یکی دیگر از مقولههای مهم در طراحی سیستمهای مدیریت پایگاه داده (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 را بجای ان دو لاگ کرد). بعنوان مثال دیگر فرض کنید تراکنشی در گذشته دور انجام شده است و با موفقیت کامیت شده است. میتوان رکوردهای لاگ مربوط به این تراکنش را نیز بنا به شرایط حذف کرد.
دقت داشته باشید که ما عملیاتی مانند عملیات محاسباتی را در این لاگ فایل ثبت نمیکنیم. بعنوان مثال اگر دو فیلد با هم باید جمع شوند و نتیجه در فیلدی باید بروز گردد، جمع دو فیل را در سیستم لاگ نمیکنیم بلکه تنها مقدار نهایی ویرایش شده را ثبت میکنیم. چرا که عملیات محاسباتی در بازیابی ضروری نیستند و ثبت انها تنها باعث بزرگ شدن فایل میشود.
در برخی از سیستمهای حساس، ممکن است برای فایلهای لاگ هم یک کپی تهیه کنند تا در صورت بروز خطا در لاگ فایل بتوان آن را نیز بازیابی نمود.
انواع رکوردهای لاگ فایل :
در فایل لاگ رکوردهای مختلفی ممکن است درج شود که در این جا به چند نمونه از انها اشاره میکنیم:
در شکل زیر نمونه بسیار ساده از نحوه لاگ کردن در حین اجرای تراکنشها را مشاهده میکنید.
در این شکل نکته ای وجود دارد که به آن اشاره ای میکنیم. همانطور که میبینید در شکل از اصطلاح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 را بر روی انواع مختلف تراکنشها را بیان کنیم.
انجام عمل بازیابی و ترمیم :
تا اینجا مقدمات لازم برای ترمیم پایگاه داده را گفتیم. حال میخواهیم بسراغ چگونگی انجام عمل ترمیم برویم. هنگامی که میخواهیم پایگاه داده ای را ترمیم کنیم اولین کاری که باید انجام گیرد اینست که بوسیله 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 کامیت شده است عملی صورت نمیگیرد.
موفق و پیروز باشید