تراکنش (Transaction) در NET.

یکشنبه 17 بهمن 1395

دراین مقاله توضیحات بسیار کامل و جامعی از تراکنش ها را مورد بررسی قرار داده ایم. با خواندن این مقاله ازاطلاعاتی کاربردی در رابطه با تراکنش ها به دست می آوریم.

تراکنش (Transaction) در NET.

معرفی

بیشتر اپلیکیشن ها با دیتابیس ها، سر وکار دارند. ما نیاز داریم داده ها اتمی باشند به این معنی که داده ی هرزی وجود نداشته باشد. به طور مثال شما میخواهید داده ای به یک جدول والد (Master)  و جدول فرزند ( Child)  در دیتا بیس را وارد کنید. در حین وارد کردن این سطر به جدول والد یک خطا اتفاق می افتد ، چه اتفاقی افتاده که باعث بروز  این خطا شده ؟

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

تراکنش چیست ؟

تراکنش یک واحد کاری یکپارچه است به معنی اینکه ی کار به طور کامل اجرا میشود یا اصلا اجرا نمیشود. اگر تراکنش با موفقیت انجام شود به این معنی که همه عملیات داده پذیرفته شده اند و به صورت پایدار در دیتابیس قرارمیگیرد. اگر تراکنش با خطا یا استثنایی برخورد کند یا کنسل میشود  یا رول بک میشود و تمام داده ها که تغییر کرده اند باید حذف شوند.

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

ویژگی های یک تراکنش :

تراکنش ها چهار ویژگی استاندارد دارند و معمولا با این کلمه ACID میشناسند :

Atomicity : تضمین می کند عملیات در این کار با موفقیت انجام میشوند اگر این عملیات لغو شوند به حالت قبلی خود رول بک میکند.

Consistency : تضمین می کند که پایگاه داده به درستی تغییر  کرده و بر انجام شدن تراکنش با موفقیت متعهد است.

Isolation : تراکنش قادر هستند به صورت مستقل  از یگدیگر عمل کنند

Durability : تضمین می کند که در صورت خطای سیستمی تراکنش همچنان ادامه دارد .

میخواهیم در مورد پیاده سازی تراکنش در  ADO.NET، EntityFramework و SQLServer  صحبت کنیم.

برای این منظور ،ما دو جدول به نام  ProjectMember, Projectایجاد کردیم. در جدول ProjectMember یک فیلد به نام ProjectID  داریم که کلید خارجی برای فیلد ProejctID در جدولProject است.

-- Table1
CREATE TABLE tblProject  
(  
   ProjectID int PRIMARY KEY,  
   Name varchar(50) NULL  
);  
  
--Table2
CREATE TABLE tblProjectMember  
(  
   MemberID int,  
   ProjectID int foreign Key references Project(ProjectID)  
);  

 تراکنش درADO.NET

معمولا ما از   ADO.NET   برای اجرای عملیات دیتا بیس استفاده میکنیم. قطعه کد زیر برای پیاده سازی تراکنش در   ADO.NET ارائه شده است .

string strConnString = "myconnectionstring"; // get it from Web.config file  
SqlTransaction objTrans = null;  

using (SqlConnection objConn = new SqlConnection(strConnString))  
{  
   objConn.Open();  
   objTrans = objConn.BeginTransaction();  
   SqlCommand objCmd1 = new SqlCommand("insert into tblProject values(1, 'TestProject')", objConn);  
   SqlCommand objCmd2 = new SqlCommand("insert into tblProjectMember(MemberID, ProjectID) values(2, 1)", objConn);  
   try  
   {  
      objCmd1.ExecuteNonQuery();  
      objCmd2.ExecuteNonQuery(); // Throws exception due to foreign key constraint  

      objTrans.Commit();  
   }  
   catch (Exception)  
   {  
      objTrans.Rollback();  
   }  
   finally  
   {  
      objConn.Close();  
   }  
}  

درقطعه کد بالا دو کوئری اجرا شده که اولی یک رکورد به جدول Project  اضافه میکند و دومی یک رکورد به جدول projectMemberاضافه میکند

اولین عبارت SQL به درستی اجرا میشود اما دومین عبارت SQL خطا دارد چرا که ما در قرار دادن projectId   ، 2  هستیم که در حال حاضر در جدول Project وجود ندارد.

تراکنش در این قسمت فرمان میدهد که با موفقیت کامل انجام شده یا باشکست مواجه شده، بین این دو دیگه  وجود ندارد.

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

استفاده ازTransactionScope

این باعث میشود کد بلاک در این متد تراکنش تولید شود. و در فضای نام سیستمی به نام   System.Transactions.TransactionScope

معرفی میشود.که 3 ویژگی دارد:

-سطح انزوا (Isolation Level) :

مکانیزم قفل را برای خواندن در تراکنش های دیگر را معرفی میکند.

گزینه های در دسترس :

خواندن UnCommitted ،  خواندن Committed ، تکرار خواندن, Serializable. به طور پیش فرض Serializable است .

-اتمام مهلت (TimeOut) :

چقدر زمان  تراکنش باید صبر کنید تا کامل شود.  زمان اتمام مهلت SqlCommand با تراکنش متفاوت است. 

اتمام مهلت    :SqlCommand یعنی چقدر زمان شی  SqlCommand بایدصبر کند تا عملیات پایگاه داده  تکمیل شود.  به طور پیش فرض 1 دقیقه است.

-گزینه محدوده تراکنش( TransactionScopeOption)

از کار انداختن(ِDisable) :این گزینه در یک تراکنش شرکت نمی کنند. این یک مقدار پیش فرض است.

پشتیبانی نشده(NotSupported):این گزینه در خارج از چارچوب یک تراکنش اجرا می شود.

ضروری(Required):این مقدار پیش فرض برای TransactionScope است. اگر  وجود داشته باشد سپس آن را با تراکنش ملحق میسازد در غیر این صورت یکی جدید ایجاد کنید.

RequiresNew:زمانی که این گزینه انتخاب شده است یک تراکنش جدید همیشه ایجاد شده است. این تراکنش مستقل  است با تراکنش بیرونی اش.

سرکوب کردن (Suppress):زمانی که این گزینه انتخاب شده باشد، هیچ تراکنشی ایجاد نخواهد شد. حتی اگر در حال حاضر وجود داشته باشد.

اگر این آپشن انتخاب شود هیچ تراکنشی نمیتواند ایجاد شود تا زمانی که این تراکنش موجود است .

شما میتوانید تنظیمات برایTimeOut در web.config  مطابق زیر انجام دهید.

<system.transactions>  
  <defaultSettings timeout="30"/>  
  <machineSettings maxTimeout="1200"/>  
</system.transactions>

 System.Transactionsبه صورت پیش فرض قابل دسترس است، نیاز هست تا ما رفرنس را اضافه کنیم.

به شکل زیر برای اضافه کردن رفرنس توجه کنید.

در نمونه کد زیر، کلاس شی TransactionScope ایجاد شده و دو تا کوئری SQL برای اضافه کردن رکورد به جدول Project و جدول ProjectMember تعریف شده است. هنگامی که همه چیز خوب است،  و به درستی پیش میرود متد   Complete()صدا زده میشود و اگر خطایی رخ دهد آن را به حالت قبلی رل بک میکند.

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))  
{  
    try  
    {  
        // In the Data Access Layer  
        using (DBContextEntitis entities = new DBContextEntitis(entitiesConnectionstring))  
        {  
            Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
            context.Project.Add(proj);  
              
            ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
            context.ProjectMembers.Add(projMember);  
              
            //The Transaction will be completed  
            scope.Complete();  
        }  
    }  
    catch(Exception ex)  
    {  
        scope.Dispose();  
    }  
}

 

تراکنش در Transaction in EntityFramework 6.0

به طور پیش فرض انتیتی فریم ورک  عملیات درج، به روز رسانی و یا حذف در یک تراکنش انجام میشود، هنگامی که متد () SaveChanges صدا زده میشود. EF برای هر عمل یک تراکنش ایجاد میکند و پس از اتمام عمل زمانی تکمیل شد، تراکنش پایان می یابد.انتیتی فریم ورک 6.0  دو متد جدید را فراهم میکند.

Database.BeginTransaction()

 شروع  را تسهیل میکند و تراکنش های خود را درون یک شی DbContext موجود است  را تکمیل میکند. این اجازه می دهد چندین تراکنش در یک تراکنش ترکیب شود و از این رو همه با هم کامیت میشوند و یا رل بک میشوند .

  using (var context = new MyDBContext())  
{  
   using (System.Data.Entity.DbContextTransaction dbTran = context.Database.BeginTransaction( ))  
   {  
      try  
      {  
         Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
         context.Project.Add(proj);  

         ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
         context.ProjectMembers.Add(projMember);  

         //saves all above operations within one transaction  
         context.SaveChanges();  

         //commit transaction  
         dbTran.Commit();  
      }  
      catch (Exception ex)  
      {  
         //Rollback transaction if exception occurs  
         dbTran.Rollback();  
      }  
   }  
} 

در کد بالا، اگر خطایی رخ بده مثلا ProjectID اشتباه از  ProjectMember  زده شود شی تراکنش dbTran تمام عملیات که در گذشته اتفاق افتاده را رل بک میکند. در حال حاضر شما هیچ رکورد در جدول Project و همچنین جدول ProjectMember دریافت نمیکنید.

Database.UseTransaction() 

 اجازه می دهد DbContext از یک تراکنش که خارج از انتیتی فریم ورک آغاز شده، استفاده کند.

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))  
{  
   try  
   {  
      // In the Data Access Layer  
      using (DBContextEntitis entities = new DBContextEntitis(entitiesConnectionstring))  
      {  
         Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
         context.Project.Add(proj);  
         ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
         context.ProjectMembers.Add(projMember);  
         //The Transaction will be completed  
         scope.Complete();  
      }  
   }  
   catch(Exception ex)  
   {  
      scope.Dispose();  
   }  
}

برای پیاده سازی UseTransaction، نیاز به اضافه  یک سازنده برای اتصال به پایگاه داده و نشانگر بسته شدن اتصال داریم. contextOwnConnection زمانی که Entity Framework  اتصالش بستن نیست و در حال  انجام  کار اطلاع میدهد.

public class DBEntitiesContext : DbContext  
{  
   public EntitiesContext() : base("name=Entities")  
   {  
      Database.Log = Console.WriteLine;  
   } 

   public EntitiesContext(DbConnection existingConnection, bool contextOwnConnection)  
   : base(existingDBConnection, contextOwnConnection)  
   {  
   }  
}  

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

 ()UseTransaction با پاس دادن  شی تراکنش  قبل از اینکه ایجاد کند، فراخوانی میکند . در حال حاضر عملیات انتیتی فریم ورک به دامنه تراکنش قبلی اضافه خواهد شد.

public class DBEntitiesContext : DbContext  
{  
   public EntitiesContext() : base("name=Entities")  
   {  
      Database.Log = Console.WriteLine;  
   } 

   public EntitiesContext(DbConnection existingConnection, bool contextOwnConnection)  
   : base(existingDBConnection, contextOwnConnection)  
   {  
   }  
}  

 تراکنش در SQL Server

ما تراکنش در front-end (کد #C)  پیاده سازی کردیم اما ما همچنین می توانیم تراکنس  در front-end مانند بانک اطلاعاتی SQLServer را تعریف کنیم.

کنترل تراکنش:

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

COMMIT To متعهد به ذخیره کردن تغییرات است .

ROLLBACK  عقبگرد  به تغییرات انجام شده است.

SAVEPOINT ایجاد نقاطی در درون گروه از تراکنش ها که در آن نقاط عقبگردشود.

SET TRANSACTION قرار دادن یک نام بر روی تراکنش است.

انواع تراکنش ها:

تراکنش ضمنی (Implicit transactions) : توسط  SQL سرور برای هر DDL (CREATE, ALTER, DROP, TRUNCATEDML INSERT, UPDATE, DELETE)) نگهداری میشود. تمام دستورات T-SQL تحت تراکنش ضمنی را اجرا میشود. اگر خطایی در این دستورات رخ دهد، SQL رول بک میکند تا دستورات کامل شوند.

معاملات صریح  (Explicit transactions) :توسط برنامه نویسان تعریف شده . در تراکنش صریح دستورات DML که باید به عنوان یک واحد اجرا شود. از آنجا که دستور SELECT باعث هیچ گونه تغییراتی بر روی داده نمی شوند، از این رو به طور کلی SELECT در یک تراکنش شامل نمی شود.

سطح انزوا

قفل ها را کنترل میکند و ردیف رفتار نسخه از دستورات Transact-SQL توسط یک اتصال به SQL سرور صادر میکند. پنج نوع از سطح انزوا وجود دارد.

READ UNCOMMITTED:

اگر هر جدول تحت یک تراکنش ( insert ، update یا  delete) به روز شده است و همان تراکنش کامیت یا رل بک نمیشود  در نتیجه uncommitted در select کوئری نمایش داده میشود.

READ COMMITTED:

در کوئری ها  آن را انتخاب میکنیم تنها مقادیر کامیت شده در جدول آن قرارمیگیرند . اگر هر گونه تراکنشی باز باشد و  جدول در جلسات(ُSession) دیگر  ناقص باشه در نتیجه  کوئری تا زمانی که هیچ تراکنشی دیگر  در انتظار  همان جدول نباشد صبر میکند.

REPEATABLE READ:

داده پرس و جو انتخاب از جدول است که تحت تراکنش سطح انزوا استفاده می شود. "" REPEATABLE READ  نمی تواند تغییر داد تا زمانی که تراکنش کامل نشده است.

SERIALIZABLE  :

 Serializable  ایزولویشن مشابه REPEATABLE READ  ایزولویشن است  کار آن بر روی قفل محدوده است.

SNAPSHOT:

 ایزولیشن SNAPSHOT شبیه ایزولیشن Serializable  است. تفاوت در این است SNAPSHOT جدول را  قفل نمیکند تا تراکنش ها  بتوانند در جلسات دیگر  به راحتی  آن را تعییر دهند.  Snapshot ایزولیشن نسخه در  Tempdb برای داده های قدیمی که مورد  تغییر در جلسات دیگر  قرار میگیرند را حفظ میکند، در نتیجه از تراکنش موجود  برای نمایش داده  قدیمی از  Tempdb استفاده میشود.

در حال حاضر ما  یک رویه(StoredProcedure) که با تراکنش پیاده سازی شده را ایجاد کردیم. در رویه دو عبارت SQL  ، INSERTقرار داده شده: یکی برای جدول tblProject دیگری برای جدول tblProjectMember است.  تمام دستورات SQL در داخل بلوک BEGIN TRANSACTION تراکنش نگه میدارد و سپس آن را کامیت میکند. اگر هر SQL ،دچار خطایی شود سپس آن را به بلوک   Catch میفرستد و به حالت قبلی از پایگاه داده رل بک میکند.

CREATE PROCEDURE spAddProject  
   @ProjectID INT,  
   @MemberID INT,  
   @Name VARCHAR(10)  
AS  
BEGIN  
   BEGIN TRY  
      BEGIN TRANSACTION;  
         -- Insert record into Project table  
         INSERT INTO tblProject(ProjectID, Name) VALUES(1, 'TestProject');  

         -- Insert record into ProjectMember table  
         INSERT INTO tblProjectMember(MemberID, ProjectID) VALUES(2, 1);  

         COMMIT TRANSACTION;  
   END TRY  
   BEGIN CATCH  
      IF @@TRANCOUNT > 0  
         ROLLBACK TRANSACTION; 
 
      DECLARE @ErrorNumber INT = ERROR_NUMBER();  
      DECLARE @ErrorLine INT = ERROR_LINE();  
      DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
  
      PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));  
      PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10));  

      RAISERROR(@ErrorMessage);  
   END CATCH  
END;  

@@TRANCOUNT: تعداد دستورات BEGIN TRANSACTION  در تراکنش های جاری را شمارش میکند. BEGIN TRANSACTIONبرای@@TRANCOUN 1 را اضافه میکند و   ROLLBACK TRANSACTION  برای @@TRANCOUNT  0 را قرار میدهد.

TRANSACTION  یاCOMMIT WORK ، @@TRANCOUNT یکی کم میکنند

حال متدهای رویه مانند کد زیر فراخوانی کنیم.

using (SqlConnection con = new SqlConnection(connectionString))  
{  
   using (SqlCommand cmd = new SqlCommand("spAddProject", con))  
   {  
      cmd.CommandType = CommandType.StoredProcedure;  
      cmd.Parameters.AddWithValue("@ProjectID", 1);  
      cmd.Parameters.AddWithValue("@MemberID", 1);  
      cmd.Parameters.AddWithValue("@Name", "Test Proejct");  

      con.Open();  
      cmd.ExecuteNonQuery();  
   }  
}  

تراکنش توزیع شده

تراکنشی که میتواند با چندین منبع اطلاعاتی کار کند تراکنش های توزیع شده نامیده می شود. اگر یک تراکنش دچار خطا بشود ،منابع داده آسیب دیده رل بک خواهد شد. در System.Transactions، MSDTC (Microsoft Distributed Transaction Coordinator) مدیریت تراکنش های توزیع شده  را بر عهده دارد. تراکنش های توزیع شده بسیار آهسته تر از تراکنش های محلی است ،وقتی انها بفهمند که به یک تراکنش توزیع شده نیاز است، سرعت تراکنش محلی به طور خودکار به تراکنش های توزیع شده افزایش می یابد.

آموزش سی شارپ

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 6k بازدید
  • 11 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید