استفاده از TransactionScope بین دیتابیس ها در MVC
شناسه پست: 360
بازدید: 2087

زمان تقریبی برای مطالعه مقاله: 15 دقیقه

پیش نیاز جهت درک مطلب: c#, ADO.NET Entity Framework, MVC

چکیده: این مقاله، نحوه استفاده از TransactionScope را برای مدیریت تراکنش ها بر روی جداول در دیتابیس های مختلف در Asp.net MVC و با استفاده از Entity Framework  نشان می دهد.

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

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

استفاده از TransactionScope

در این مقاله، میخواهیم تراکنش ها را با استفاده از TransactionScope مدیریت نماییم. به این صورت که اگر به هر دلیلی در عملیات ذخیره سازی یکی از جداول خطایی رخ داد، هیچ داده ای در جدول دیگر نیز ذخیره نگردد. کلاس TransactionScope از زمان .NET 2.0 به بعد در فضای نام System.Transaction ارائه شد. این کلاس، تراکنش ها را به صورت خودکار می تواند مدیریت نماید. زمانی که شما یک نمونه ای را از این کلاس می سازید، transaction scope آعاز می شود. توصیه شدیدی می شود که TransactionScopeرا با بلاک using آغاز کنید.

زمانی که شما نمونه ای را از این کلاس می سازید باید TransactionScopeOption را در سازنده آن مشخص نمایید. TransactionScopeOption شامل سه مقدار می باشد:

  • Required: این مقدار، مقدار پیش فرض می باشد. این مقدار مشخص می نماید که اگر تراکنشی از قبل موجود باشد از همان تراکنش استفاده می نماید در غیر اینصورت یک تراکنش جدید می سازد.
  • RequiresNew: یک تراکنش جدید برای scope مورد نظر می سازد.
  • Suppress: این مقدار باعث می شود که تراکنشی اگر موجود باشد آن را در نظر نمی گیرد و برای آن scope مورد نظر که این مقدار را میگیرد استثنا قائل می گردد.

در این مثال، ما از دیتابیس های زیر استفاده می نماییم:

جدول FlightBooking در دیتابیس Flight 

CREATE TABLE [dbo].[FlightBooking] (
    [FlightId]       INT          NOT NULL,
    [FilghtName]     VARCHAR (50) NOT NULL,
    [Number]         NCHAR (10)   NOT NULL,
    [TravellingDate] DATETIME     NOT NULL,
    CONSTRAINT [PK_FlightBooking] PRIMARY KEY CLUSTERED ([FlightId] ASC)
);
جدول Reservation در دیتابیس Hotel:
CREATE TABLE [dbo].[Reservation] (
    [BookingId]   INT          NOT NULL,
    [Name]        VARCHAR (50) NOT NULL,
    [BookingDate] DATETIME     NOT NULL,
    CONSTRAINT [PK_REservation] PRIMARY KEY CLUSTERED ([BookingId] ASC)
);
حال ما میخواهیم یک پروژه در visual studio بسازیم و با استفاده از ADO.NET EF، مدلها را بسازیم.
  • یک پروژه جدید از نوع Asp.net Web Application در ویژوال استودیو بسازید و نام آن را MVC_EF_TransactionScope بگذارید.

  •  در فولدر model، یک ADO.NET Entity Data Model برای جدول FlightBooking از دیتابیس Flight بسازید.

ADO.NET Entity Data Model

ADO.NET Entity Data Model

ADO.NET Entity Data Model

همین کار را برای جدول Reservation از جدول Hotel نیز انجام دهید.

  •  در فولدر Model، یک کلاس با نام TripReservation بسازید و کدهای زیر را در آن قرار دهید. این مدل، اطلاعات مربوط به پرواز و هتل را از UI توسط کاربر گرفته و به  در دیتابیس ها ذخیره می نماید.
public class TripReservation
{
    public FlightBooking Filght { get; set; }
    public Reservation Hotel { get; set; }
}
  • در Application، یک فولدر به نام DataAccessRepository ایجاد نمایید و یک فایل با نام DataAccesss.cs در این فولدر ایجاد نموده و کدهای زیر را در آن قرار دهید:
using MVC_EF_TransactionScope.Models;
using System.Transactions;
namespace MVC_EF_TransactionScope.DataAceessRepository
{
    public class MakeReservation
    {
        FlightEntities flight;
        HotelEntities hotel;
        public MakeReservation()
        {
            flight = new FlightEntities();
            hotel = new HotelEntities();
        }
       //The method for handling transactions
        public bool ReservTrip(TripReservation trip)
        {
            bool reserved = false;
            //Define the scope for bundling the transaction
            using (var txscope =new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                try
                {
           
                    //The Flight Information
                    flight.FlightBookings.Add(trip.Filght);
                    flight.SaveChanges();
                    //The Hotel Information
                    hotel.Reservations.Add(trip.Hotel);
                    hotel.SaveChanges();
                    reserved = true;
                    //The Transaction will be completed
                    txscope.Complete();
                }
                catch
                {
                }
            }
            return reserved;
        }
    
    }
}

در کلاس بالا، نمونه هایی از object های FlightEntities و HotelEntities  ساخته می شود که context از ADO.NET EF برای عملیات CRUD بر روی دیتابیس ها می سازد.

متد ReservTrip مدل TripReservation را به عنوان ورودی می گیرد و یک نمونه از TransactionScope می سازد و تراکنش ذخیره سازی در جداول FlightBooking و Reservation را داخل آن handle می کند.  چنانچه متد SaveChanges برای هر دو Entity با موفقیت انجام شود، تراکنش Complete می شود و اطلاعات با موفقیت در دو دیتابیس ذخیره می گردد. اما چنانچه به هر دلیلی، ذخیره اطلاعات در دیتابیس hotel با خطا روبه رو گردد، Exception رخ داده و تراکنش Complete نمی شود و اطلاعات داخل دیتابیس Flight هم ذخیره نمی گردد.

  • در Application، در فولدر Share داخل فولدر View، یک View به نام Success بسازید و کد زیر را درون آن قرار دهید:
@{
    ViewBag.Title = "Success";
}
<h1>Congratulations!!!!Your Flight and Hotel is booked successfully.</h1>

و یک view هم به نام Error بسازید و کد زیر را درون آن قرار دهید:

@{
    ViewBag.Title = "Error";
}
<h1>Error, The Trip is not Registered. This may be because either Flight or Hotel Booking is not available.</h1>
  • بر روی فولدر Controller، راست کلیک کرده و یک Empty MVC controller اضافه نمایید و درون آن کدهای زیر را قرار دهید:
using System;
using System.Web.Mvc;
using MVC_EF_TransactionScope.Models;
using MVC_EF_TransactionScope.DataAceessRepository;
namespace MVC_EF_TransactionScope.Controllers
{
    public class TripController : Controller
    {
        MakeReservation reserv;
        public TripController()
        {
            reserv = new MakeReservation();
        }
        // GET: Trip
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult Create()
        {
            return View(new TripReservation());
        }
        //The Reservation Process
        [HttpPost]
        public ActionResult Create(TripReservation tripinfo)
        {
            try
            {
                tripinfo.Filght.TravellingDate = DateTime.Now;
                tripinfo.Hotel.BookingDate = DateTime.Now;
               var res =  reserv.ReservTrip(tripinfo);
               if (!res)
               {
                   return View("Error");
               }
            }
            catch (Exception)
            {
                return View("Error");
            }
            return View("Success");
        }
    }
}

این کنترلر، کلاس MakeReservation  را برای انجام تراکنش ذخیره سازی در دیتابیس ها فراخوانی می نماید. در این کنترلر، اولین متد create که از نوع httpGet است یک نمونه از TripReservation  را برای ایجاد یک view از نوع Create، می سازد. در ادامه، بعد از submit شدن فرم، create httpPost، مدل TripReservation  را به عنوان ورودی پذیرفته و آن را به متد ReservTrip از کلاس MakeReservation ارسال می نماید. ReservTrip یک مقدار boolean برمی گرداند. اگر مقدار true برگرداند به این معنی است که تراکنش با موفقیت انجام شده است و success view نمایش داده می شود در غیر اینصورت Error view نمایش داده می شود.

  • برای ساخت ui و گرفتن اطلاعات پرواز و هتل ها از کاربر، باید یک Empty View با مدل TripReservation از HttpGet Create در TripController را scaffold نمایید. به این منظور، روی HttpGet Create در TripController راست کلیک کرده و Add View را بزنید.

ساختن view در mvc

سپس دکمه Add را کلیک نمایید.

این یک view خالی است که ما نیاز داریم آن را طراحی نماییم. به این منظور کدهای زیر را درون آن قرار دهید:

@model MVC_EF_TransactionScope.Models.TripReservation
@{
    ViewBag.Title = "Create";
}
<h2 class="text-center">Plan Your Trip</h2>
@using(Html.BeginForm()){
<table class="table table-condensed table-striped table-bordered">
    <tr>
        <td>
            <table class="table table-condensed table-striped table-bordered">
                <tr>
                    <td colspan="2" class="text-center">
                        The Flight Booking Info
                    </td>
                </tr>
                <tr>
                    <td>
                        Flight Id:
                    </td>
                    <td>
                        @Html.EditorFor(m => m.Filght.FlightId)
                    </td>
                </tr>
                <tr>
                    <td>
                        Flight Name:
                    </td>
                    <td>
                        @Html.EditorFor(m => m.Filght.FilghtName)
                    </td>
                </tr>
                <tr>
                    <td>
                        Flight Number:
                    </td>
                    <td>
                        @Html.EditorFor(m => m.Filght.Number)
                    </td>
                </tr>
            </table>
        </td>
        <td>
            <table class="table table-condensed table-striped table-bordered">
                <tr>
                    <td colspan="2" class="text-center">
                        The Hotel Booking Info
                    </td>
                </tr>
                <tr>
                    <td>
                        Booking Id:
                    </td>
                    <td>
                        @Html.EditorFor(m => m.Hotel.BookingId)
                    </td>
                </tr>
                <tr>
                    <td>
                        Customer Name:
                    </td>
                    <td>
                        @Html.EditorFor(m => m.Hotel.Name)
                    </td>
                </tr>
            </table>
        </td>
    </tr>
    <tr>
        <td colspan="2" class="text-center">
            <input type="submit" value="Save Trip" />
        </td>
    </tr>
</table>
}

ویوی بالا فرم مورد نظر برای گرفتن اطلاعات مربوط به پرواز و هتل را نمایش می دهد.

  • جهت اجرای Application و چک کردن تراکنش ها، ما باید از سرویس Distributed Transaction Coordinator استفاده نماییم. این سرویس، تراکنشی که دو یا بیشتر تراکنش ها را handle می کند را تحت پوشش قرار می دهد. برای مثال Databases, Message Queues, File Systems و غیره. جهت باز کردن تنظیمات DTC، مراحل زیر را طی نمایید:
  1. از قسمت start ویندوز، MMC را جست و جو نمایید و بر روی آن کلیک نمایید.
  2. Component Services snap-in از MMC را باز نمایید.
  3. روی فولدر Computers در کنسول دابل کلیک نمایید.
  4. بر روی My Computers، راست کلیک نموده و روی properties کلیک نمایید.
  5. در پنجره Properties، روی تب MSDTC کلیک نمایید و چک باکس Use local coordinator را در حالت انتخاب شده قرار دهید.
  6. بر روی ok کلیک نموده و پنجره Properties را ببندید.
  7. روی My Computer دابل کلیک نموده، سپس روی فولدر Distributed Transaction Coordinator دابل کلیک نمایید. در نتیجه Local DTC نمایش داده خواهد شد.
  8. بر روی Local DTC دابل کلیک نمایید. در نتیجه Transaction List و Transaction Statistics نمایش داده خواهد شد.
  9. بر روی Transaction-Statistics دابل کلیک نمایید که در نتیجه ui زیر نمایش داده خواهد شد:

 Distributed Transaction Coordinator

تصویر بالا، آمار تراکنشهای از نوع Committed و Aborted را نمایش می دهد.

  • حال پروژه را اجرا نمایید که در نتیجه فرم زیر نمایش داده خواهد شد:

اطلاعات را طبق فرم بالا وارد نمایید. اگر data با موفقیت در هر دو جدول ذخیره گردد، تراکنش با موفقیت Complete می گردد و صفحه Success نمایش داده می شود.

Success view

آمار تراکنش به صورت زیر نمایش داده خواهد شد:

 Distributed Transaction Coordinator

تراکنش از نوع Commit ما یک عدد اضافه شد و به عدد 3 رسید. (توجه داشته باشید که قبلا من در همین اجرای خود 2 تراکنش از نوع Commit داشتم و در جال حاضر چون تراکنش ما با موفقیت به انجام رسید تراکنش از نوع Commit ما به عدد 3 رسید.) در ضمن در تصویر بالا تراکنش از نوع Aborted ما 1 است. حال یکبار دیگر پروژه را اجرا نمایید و اطلاعات پرواز را مانند تصویر زیر تغییر داده ولی اطلاعات Reservation را مانند قبل وارد نمایید. (به این معنی که ما میخواهیم یک Primary Exception رخ دهد و تراکنش RoleBack شود.):

بر روی دکمه save کلیک نمایید که به دلیل خطای Primary Exception، خطای زیر رخ می دهد:

خطای Error

حال آمار تراکنشهای خود را چک نمایید. همانطور که در تصویر زیر مشاهده می نمایید تراکنش از نوع Aborted به عدد 2 تغییر پیدا کرد:

آمار تراکنش

اگر شما اطلاعات تکراری در دیتابیس وارد نمایید، رکورد تکراری در دیتابیس نمایش داده نخواهد شد و این به این معنی است که TransactionScope تراکنش را با استفاده از اتصال بین دو جدول در دو دیتابیس را در یکی single scope مدیریت می کند و تراکنش های Commitشده و Aborted شده را مانیتور می کند.

نتیجه گیری: همانطور که مشاهده نمودید ما در این پروژه از Entity Framework استفاده نمودیم و به این مهم رسیدیم که برای عملیات چندگانه در چند دیتابیس، بهتر است که تراکنش ها را با استفاده از TransactionScope مدیریت نماییم تا داده های هرز در دیتابیس های ما به وجود نیاید.

نویسنده

امید عباسی
من امید عباسی هستم. سالهاست که در زمینه برنامه نویسی با تکنولوژی دات نت فعالیت میکنم و عاشق این هستم که تجربیات و دانش خودم را در این زمینه با دیگران به اشتراک بزارم. خیلی دوست دارم که نظر و انتقاد خودتون رو در مورد این نوشته برای من بنویسید تا بتونم در آینده، مطالب بهتر و ارزشمندتری را برای شما فراهم کنم. در صورت داشتن هرگونه سوال هم در قسمت دیدگاه ها میتونید با بنده در ارتباط باشید