تراکنش (Transaction) در NET.
یکشنبه 17 بهمن 1395دراین مقاله توضیحات بسیار کامل و جامعی از تراکنش ها را مورد بررسی قرار داده ایم. با خواندن این مقاله ازاطلاعاتی کاربردی در رابطه با تراکنش ها به دست می آوریم.
معرفی
بیشتر اپلیکیشن ها با دیتابیس ها، سر وکار دارند. ما نیاز داریم داده ها اتمی باشند به این معنی که داده ی هرزی وجود نداشته باشد. به طور مثال شما میخواهید داده ای به یک جدول والد (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, TRUNCATE)، DML 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) مدیریت تراکنش های توزیع شده را بر عهده دارد. تراکنش های توزیع شده بسیار آهسته تر از تراکنش های محلی است ،وقتی انها بفهمند که به یک تراکنش توزیع شده نیاز است، سرعت تراکنش محلی به طور خودکار به تراکنش های توزیع شده افزایش می یابد.
- C#.net
- 6k بازدید
- 11 تشکر