مقدمه
در اکثر موارد در یک Landscape عملیاتی، چنانچه به تجمیع و انتقال دادهها از بانکهای اطلاعاتی مختلف نیاز باشد، از SSIS Package اختصار (SQL Server Integration Service) استفاده میشود و معمولاً با تعریف یک Job در سطح SQL Server به اجرای Package در زمانهای مشخص میپردازند. چنانچه در موقعیتی لازم باشد که از طریق برنامه کاربردی توسعه یافته، به اجرای Package مبادرت ورزیده شود و البته نخواهیم Job تعریف شده را از طریق کد برنامه، اجرا کنیم و در واقع این امکان را داشته باشیم که همانند یک رویه ذخیره شده تعریف شده در سطح بانک اطلاعاتی به اجرای عمل فوق بپردازیم، یک راه حل میتواند تعریف یک CLR Stored Procedures باشد. در این مقاله به بررسی این موضوع پرداخته میشود، در ابتدا لازم است به بیان تئوری موضوع پرداخته شود (قسمتهای 1 الی 5) در ادامه به ذکر پیاده سازی روش پیشنهادی پرداخته میشود.
1- اجرای Integration Service Package
جهت اجرای یک Package از ابزارهای زیر میتوان استفاده کرد:• command-line ابزار خط فرمان dtexec.exeتوجه: همچنین یک Package را در زمان طراحی در Business Intelligence Development Studio) BIDS) میتوان اجرا نمود.
• ابزار اجرائی پکیج dtexecui.exe
• استفاده از SQL Server Agent job
2- استفاده از dtexec جهت اجرای Package
با استفاده از ابزار dtexec میتوان Packageهای ذخیره شده در فایل سیستم، یک SQL Instance و یا Packageهای ذخیره شده در Integration Service را اجرا نمود.توجه: در سیستم عاملهای 64 بیتی، ابزار dtexec موجود در Integration Service با نسخه 64 بیتی نصب میشود. چنانچه بایست Packageهای معینی را در حالت 32 بیتی اجرا کنید، لازم است ابزار dtexec نسخه 32 بیتی نصب شود. ابزار dtexec دستیابی به تمامی ویژگیهای پیکربندی و اجرای Package از قبیل اتصالات، مشخصات(Properties)، متغیرها، logging و شاخصهای پردازشی را فراهم میکند.
توجه: زمانی که از نسخهی ابزار dtexec که با SQL Server 2008 ارائه شده استفاده میکنید برای اجرای یک SSIS Package نسخه 2005، Integration Service به صورت موقت Package را به نسخه 2008 ارتقا میدهد، اما نمیتوان از ابزار dtexec برای ذخیره این تغییرات استفاده کرد.
2-1- ملاحظات نصب dtexec روی سیستمهای 64 بیتی
به صورت پیش فرض، یک سیستم عامل 64 بیتی که هر دو نسخه 64 بیتی و 32 بیتی ابزار خط فرمان Integration Service را دارد، نسخه 32 بیتی نصب شده را در خط فرمان اجرا خواهد کرد. نسخه 32 بیتی بدین دلیل اجرا میشود که در متغیر محیطی (Path (Path environment variable مسیر directory نسخه 32 بیتی قرار گرفته است.به طور معمول:2-2- تفسیر کدهای خروجی
هنگامی که یک Package اجرا میشود، dtexec یک کد خروجی (Return Code) بر میگرداند:مقدار | توصیف |
0 | Package با موفقیت اجرا شده است. |
1 | Package با خطا مواجه شده است. |
3 | Package در حال اجرا توسط کاربر لغو شده است. |
4 | Package پیدا نشده است. |
5 | Package بارگذاری نشده است. |
6 | ابزار با یک خطای نحوی یا خطای معنایی در خط فرمان برخورد کرده است. |
2-3- قوانین نحوی dtexec
تمامی گزینهها (Options) باید با یک علامت Slash (/) و یا Minus (-) شروع شوند.یک آرگومان باید در یک quotation mark محصور شود چنانچه شامل یک فاصله خالی باشد.
گزینهها و آرگومانها بجز رمزعبور حساس به حروف کوچک و بزرگ نیستند.
2-3-1- Syntax
dtexec /option [value] [/option [value]]…
2-3-2- Parameters
نکته: در Integration Service، ابزار خط فرمان dtsrun که برایData Transformation Service) DTS)های نسخه SQL Server 2000 استفاده میشد، با ابزار خط فرمان dtexec جایگزین شده است.• تعدادی از گزینههای خط فرمان dtsrun به طور مستقیم در dtexec معادل دارند برای مثال نام Server و نام Package.
• تعدادی از گزینههای dtsrun به طور مستقیم در dtexec معادل ندارند.
• تعدادی گزینههای خط فرمان جدید dtsexec وجود دارد که در ویژگیهای جدید Integration Service پشتیبانی میشود.
2-3-3- مثال
1) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده است، با استفاده از Windows Authentication :dtexec /sq <Package Name> /ser <Server Name>
2) به منظور اجرای یک SSIS Package که در پوشه File System در SSIS Package Store ذخیره شده است :
dtexec /dts “\File System\<Package File Name>”
3) به منظور اجرای یک SSIS Package که در سیستم فایل ذخیره شده است و مشخص کردن گزینه logging:
dtexec /f “c:\<Package File Name>” /l “DTS.LogProviderTextFile; <Log File Name>”
4) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده با استفاده از SQL Server Authentication برای نمونه(user:ssis;pwd:ssis@ssis)و رمز (Package(123:
dtexec /server “<Server Name>” /sql “<Package Name>” / user “ssis” /Password “ssis@ssis” /De “123”
3- تنظیمات سطح حفاظتی یک Package
به منظور حفاظت از دادهها در Packageهای Integration Service میتوانید یک سطح حفاظتی (protection level) را تنظیم کنید که به حفاظت از دادههای صرفاً حساس یا تمامی دادههای یک Package کمک نماید. به علاوه میتوانید این دادهها را با یک Password یا یک User Key رمزگذاری نمائید یا به رمزگذاری دادهها در بانک اطلاعاتی اعتماد کنید. همچنین سطح حفاظتی که برای یک Package استفاده میکنید، الزاماً ایستا (static) نیست و در طول چرخه حیات یک Package میتواند تغییر کند. اغلب سطح حفاظتی در طول توسعه یا به محض (deploy) استقرار Package تنظیم میشود.توجه: علاوه بر سطوح حفاظتی که توصیف شد، Packageها در بانک اطلاعاتی msdb ذخیره میشوند که همچنین میتوانند توسط نقشهای ثابت در سطح بانک اطلاعاتی (fixed database-level roles) حفاظت شوند. Integration Service شامل 3 نقش ثابت بانک اطلاعاتی برای نسبت دادن مجوزها به Package است که عبارتند از db_ssisadmin ،db_ssisltduser و db_ssisoperator
3-1- درک سطوح حفاظتی
در یک Package اطلاعات زیر به عنوان حساس تعریف میشوند:• بخش password در یک connection string. گرچه، اگر گزینه ای را که همه چیز را رمزگذاری کند، انتخاب کنید تمامی connection string حساس در نظر گرفته میشود.
• گرههای task-generated XML که برچسب (tagged) هایی حساس هستند.
• هر متغییری که به عنوان حساس نشان گذاری شود.
3-1-1- Do not save sensitive
هنگامی که Package ذخیره میشود از ذخیره مقادیر ویژگیهای حساس در Package جلوگیری میکند. این سطح حفاظتی رمزگذاری نمیکند اما در عوض از ذخیره شدن ویژگی هایی که حساس نشان گذاری شده اند به همراه Package جلوگیری میکند.3-1-2- Encrypt all with password
به منظور رمزگذاری تمامی Package از یک Password استفاده میشود. Package توسط Password ای رمزگذاری میشود که کاربر هنگامی که Package را ایجاد یا Export میکند، ارائه میدهد. به منظور باز کردن Package در SSIS Designer یا اجرای Package توسط ابزار خط فرمان dtexec کاربر بایست رمز Package را ارائه نماید. بدون رمز کاربر قادر به دستیابی و اجرای Package نیست.3-1-3- Encrypt all with user key
به منظور رمزگذاری تمامی Package از یک کلید که مبتنی بر Profile کاربر جاری میباشد، استفاده میشود. تنها کاربری که Package را ایجاد یا Export میکند، میتواند Package را در SSIS Designer باز کند و یا Package را توسط ابزار خط فرمان dtexec اجرا کند.3-1-4- Encrypt sensitive with password
به منظور رمزگذاری تنها مقادیر ویژگیهای حساس در Package از یک Password استفاده میشود. برای رمزگذاری از DPAPI استفاده میشود. دادههای حساس به عنوان بخشی از Package ذخیره میشوند اما آن دادهها با استفاده از Password رمزگذاری میشوند. به منظور باز نمودن Package در SSIS Designer کاربر باید رمز Package را ارائه دهد. اگر رمز ارائه نشود، Package بدون دادههای حساس باز میشود و کاربر باید مقادیر جدیدی برای دادههای حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه میشود.3-1-5- Encrypt sensitive with user key
به منظور رمزگذاری تنها مقادیر ویژگیهای حساس در Package از یک کلید که مبتنی بر Profile کاربر جاری میباشد، استفاده میشود. تنها کاربری که از همان Profile استفاده میکند، Package را میتواند بارگذاری (load) کند. اگر کاربر متفاوتی Package را باز نماید، اطلاعات حساس با مقادیر پوچی جایگزین میشود و کاربر باید مقادیر جدیدی برای دادههای حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه میشود. برای رمزگذاری از DPAPI استفاده میشود.3-1-6- (Rely on server storage for encryption (ServerStorage
با استفاده از نقشهای بانک اطلاعاتی، SQL Server تمامی Package را حفاظت میکند. این گزینه تنها زمانی پشتیبانی میشود که Package در بانک اطلاعاتی msdb ذخیره شده است.4- استفاده از نقشهای Integration Service
برای کنترل کردن دستیابی به Package، SSIS شامل 3 نقش ثابت در سطح بانک اطلاعاتی است. نقشها میتوانند تنها روی Package هایی که در بانک اطلاعاتی msdb ذخیره شده اند، بکار روند. با استفاده از SSMS میتوانید نقشها را به Packageها نسبت دهید، این انتساب نقشها در بانک اطلاعاتی msdb ذخیره میشود.Write action | Read action | Role |
Import packages Delete own packages Delete all packages Change own package roles Change all package roles * به نکته رجوع شود | Enumerate own packages Enumerate all packages View own packages View all packages Execute own packages Execute all packages Export own packages Export all packages Execute all packages in SQL Server Agent | db_ssisadmin or sysadmin |
Import packages Delete own packages Change own package roles | Enumerate own packages Enumerate all packages View own packages Execute own packages Export own packages | db_ssisltduser |
None | Enumerate all packages View all packages Execute all packages Export all packages Execute all packages in SQL Server Agent | db_ssisoperator |
Stop all currently running packages | View execution details of all running packages | Windows administrators |
همچنین جدول sysssispackages در بانک اطلاعاتی msdb شامل Package هایی است که در SQL Server ذخیره میشوند. این جدول شامل ستون هایی که اطلاعاتی درباره نقش هایی که به Packageها نسبت داده شده است، میباشد.
به صورت پیش فرض، مجوزهای نقشهای ثابت بانک اطلاعاتی db_ssisadmin و db_ssisoperator و شناسه منحصر به فرد کاربری (unique security identifier) که Package را ایجاد کرده برای خواندن Package بکار میرود، و مجوزهای نقش db_ssisadmin و شناسه منحصر به فرد کاربری که Package را ایجاد کرده برای نوشتن Package به کار میرود. یک User باید عضو نقش db_ssisadmin و db_ssisltduser یا db_ssisoperator برای داشتن دسترسی خواندن Package باشد. یک User باید عضو نقش db_ssisadmin برای داشتن دسترسی نوشتن Package باشد.
5- اتصال به صورت Remote به Integration Service
زمانی که یک کاربر بدون داشتن دسترسی کافی تلاش کند به یک Integration Service به صورت Remote متصل شود، با پیغام خطای "Access is denied" مواجه میشود. برای اجتناب از این پیغام خطا میتوان تضمین کرد که کاربر مجوز مورد نیاز DCOM را دارد.به منظور پیکربندی کردن دسترسی کاربر به صورت Remote به سرویس Integration مراحل زیر را دنبال کنید:
- Component Service را باز نمایید ( در Run عبارت dcomcnfg را تایپ کنید).مجوز دسترسی Lunch به منظور شروع و خاتمه سرویس، اعطا یا رد میشود و مجوز دسترسیActivation به منظور متصل شدن به سرویس، اعطا (grant) یا رد (deny) میشود.
- گره Component Service را باز کنید، گره Computer و سپس My Computer را باز نمایید و روی DCOM Config کلیک نمایید.
- گره DCOM Config را باز کنید و از لیست برنامه هایی که میتوانند پیکربندی شوند MsDtsServer را انتخاب کنید.
- روی Properties برنامه MsDtsServer رفته و قسمت Security را انتخاب کنید.
- در قسمت Lunch and Activation Permissions، مورد Customize را انتخاب و سپس روی Edit کلیک نمایید تا پنجره Lunch Permission باز شود.
- در پنجره Lunch Permission، کاربران را اضافه و یا حذف کنید و مجوزهای مناسب را به کاربران یا گروههای مناسب نسبت دهید. مجوزهای موجود عبارتند از Local Lunch، Remote Lunch، Local Activation و Remote Activation .
- در قسمت Access Permission مراحل فوق را به منظور نسبت دادن مجوزهای مناسب به کاربران یا گروههای مناسب انجام دهید.
- سرویس Integration را Restart کنید.
6- پیاده سازی
در ابتدا به ایجاد یک CLR Stored Procedures پرداخته میشود نام اسمبلی ساخته شده به این نام RunningPackage.dll میباشد و حاوی کد زیر است:Partial Public Class StoredProcedures '------------------------------------------------ 'exec dbo.Spc_NtDtexec 'Package','ssis','ssis@ssis','1234512345' '------------------------------------------------ <Microsoft.SqlServer.Server.SqlProcedure()> _ Public Shared Sub Spc_NtDtexec(ByVal PackageName As String, _ ByVal UserName As String, _ ByVal Password As String, _ ByVal Decrypt As String) Dim p As New System.Diagnostics.Process() p.StartInfo.FileName = "C:\Program Files\Microsoft SQL Server\100\DTS\Binn\DTExec.exe" p.StartInfo.RedirectStandardOutput = True p.StartInfo.Arguments = "/sql " & PackageName & " /User " & UserName & " /Password " & Password & " /De " & Decrypt p.StartInfo.UseShellExecute = False p.Start() p.WaitForExit() Dim output As String output = p.StandardOutput.ReadToEnd() Microsoft.SqlServer.Server.SqlContext.Pipe.Send(output) End Sub End Class
در قدم بعدی نیاز به Register کردن dll ساخته شده در سطح بانک اطلاعاتی SQL Server است، این گامها پس از اتصال به SQL Server Management Studio به شرح زیر است:
1- فعال کردن CLR در سرویس SQL Server
SP_CONFIGURE 'clr enabled',1 GO RECONFIGURE
2- فعال کردن ویژگی TRUSTWORTHY در بانک اطلاعاتی مورد نظر
ALTER DATABASE <Database Name> SET TRUSTWORTHY ON GO RECONFIGURE
3- ایجاد Assembly و Stored Procedure در بانک اطلاعاتی مورد نظر
Assembly ساخته شده با نام RunningPacakge.dll در ریشه :C کپی شود. بعد از ثبت نمودن این Assembly لزومی به وجود آن نمیباشد.
USE <Database Name> GO CREATE ASSEMBLY [RunningPackage] AUTHORIZATION [dbo] FROM 'C:\RunningPackage.dll' WITH PERMISSION_SET = UNSAFE Go CREATE PROCEDURE [dbo].[Spc_NtDtexec] @PackageName [nvarchar](50), @UserName [nvarchar](50), @Password [nvarchar](50), @Decrypt [nvarchar](50) WITH EXECUTE AS CALLER AS EXTERNAL NAME [RunningPackage].[RunningPackage.StoredProcedures].[Spc_NtDtexec] GO
در برنامه کاربردی تان کافی است متدی به شکل زیر ایجاد و با توجه به نیازتان در برنامه به فراخوانی آن و اجرای Package بپردازید.
Private Sub ExecutePackage() Dim oSqlConnection As SqlClient.SqlConnection Dim oSqlCommand As SqlClient.SqlCommand Dim strCnt As String = String.Empty strCnt = "Data Source=" & txtServer.Text & ";User ID=" & txtUsername.Text & ";Password=" & txtPassword.Text & ";Initial Catalog=" & cmbDatabaseName.SelectedValue.ToString() & ";" Try oSqlConnection = New SqlClient.SqlConnection(strCnt) oSqlCommand = New SqlClient.SqlCommand With oSqlCommand .Connection = oSqlConnection .CommandType = System.Data.CommandType.StoredProcedure .CommandText = "dbo.Spc_NtDtexec" .Parameters.Clear() .Parameters.Add("@PackageName", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@UserName", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@Password", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@Decrypt", System.Data.SqlDbType.VarChar, 50) .Parameters("@PackageName").Value = txtPackageName.Text.Trim() .Parameters("@UserName").Value = txtUsername.Text.Trim() .Parameters("@Password").Value = txtPassword.Text.Trim() .Parameters("@Decrypt").Value = txtDecrypt.Text.Trim() End With If (oSqlCommand.Connection.State <> System.Data.ConnectionState.Open) Then oSqlCommand.Connection.Open() oSqlCommand.ExecuteNonQuery() System.Windows.Forms.MessageBox.Show("Success") End If If (oSqlCommand.Connection.State = System.Data.ConnectionState.Open) Then oSqlCommand.Connection.Close() End If Catch ex As Exception MessageBox.Show(ex.Message, "Error") End Try End Sub 'ExecutePackage
Exception و خوانایی کد
تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:
public ActionResult CreateEmployee(string name) { try { ValidateName(name); // ادامه کدها return View("با موفقیت ثبت شد"); } catch (ValidationException ex) { return View("خطا", ex.Message); } } private void ValidateName(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ValidationException("نام نمیتواند خالی باشد"); if (name.Length > 100) throw new ValidationException("نام نمیتواند طولانی باشد"); }
در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کدهای مشابه به این کد را زیاد دیدهاید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت سادهتر شما از امضای این متد نمیتوانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشهای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنهی متد برگردیم. اگر بهخاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم:
به عبارت دیگر وقتی از exceptionها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام میدهید که دستور GOTO انجام میداد. این دستور در روشهای قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگیهای زیادی را خواهد داشت. چراکه فراخوانی قطعههای کد و متدها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exceptionها برای کنترل جریان برنامه، میتوانند از GOTO هم بدتر باشند؛ چرا که exception میتواند از لایههای مختلف کد نیز عبور کند.
امیدوارم تا اینجا به یک عقیدهی مشترک رسیده باشیم. خوب راهکار چیست؟ تصور کنید که تکه کد بالا را به صورت زیر تبدیل کنیم:
public ActionResult CreateEmployee(string name) { string error = ValidateName(name); if (error != string.Empty) return View("خطا", error); // ادامه کدها return View("با موفقیت ثبت شد"); } private string ValidateName(string name) { if (string.IsNullOrWhiteSpace(name)) return "نام نمیتواند خالی باشد"; if (name.Length > 100) return "طول نام نمیتواند بیشتر از 100 کاراکتر باشد"; return string.Empty; }
با refactor ای که انجام دادیم، متد ValidateName را به یک تابع ریاضی تبدیل کردیم. به این معنا که هر آنچه را که از امضای متد، مشخص است، انجام میدهد و در این حالت چیزی مخفی نیست. توجه داشته باشید که این راهکار نهایی ما نیست و لطفا مقاله را تا انتها بخوانید!
موارد استفاده Exception
با همهی بدیهایی که از Exceptionها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟
- Exceptionها واقعا برای موارد استثنائی هستند.
- Exceptionها برای شرایطی هستند که به معنای واقعی یک باگ باشند.
- منتظر رخ دادن Exception نباشیم!
در توضیح مورد سوم، در اعتبار سنجی دادههای کاربر (Validation) انتظار دادهی نادرستی را میتوان داشت، پس نمیتوانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید
دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که میتوان انتظار استفادهی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامینهای مختلف، دیتایی رد و بدل میشود که معتبر نیست، میتوانیم آن را جزء استثناءها در نظر بگیریم. به مثال زیر دقت کنید:
public ActionResult UpdateEmployee(int employeeId, string name) { string error = ValidateName(name); if (error != string.Empty) return View("Error", error); Employee employee = GetEmployee(employeeId); employee.UpdateName(name); } public class Employee { public void UpdateName(string name){ if (name == null) throw new ArgumentNullException(); // ادامه کدها } }
در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین میباشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنهی متد UpdateName دقت کنید، میبینید که مجددا از خالی نبودن نام اطمینان حاصل کردهایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردنها در دامینهای مختلف، معمولا guard clause گفته میشود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است!
مفهوم fail fast
تا اینجا متوجه شدیم که از exceptionها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آنها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast میرساند. این مفهوم به ما میگوید:
- کار جاری را به محض یک اتفاق استثنائی باید متوقف کنیم.
- رعایت این نکته در نهایت ما را به یک نرم افزار پایدار خواهد رساند.
برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.
متد زیر را ببینید:
public void ProcessItems(List<Item> items) { foreach (Item item in items) { try { Process(item); } catch (Exception ex) { Logger.Log(ex); } } }
در قطعه کد بالا، در نگاه اول احتمالا حس نرم افزار پایدارتر و بدون خطا را خواهیم داشت. اما در واقع اینطور نیست. احتمال اینکه خطا از چشم برنامه نویس به دور باشد و بعد از اجرا باعث شود که یکپارچگی دادهها را به هم بریزد وجود دارد. در واقع هیچ راهی برای زمانیکه این عملیات نباید انجام شود، در نظر گرفته نشدهاست. طبق صحبتهایی که بالاتر داشتیم، شرایط غیر منتظره، در واقع یک باگ در نرم افزار است و هیچ مزیتی در جلوگیری از وقوع این باگ بدون حل مشکل نیست!
به صور خلاصه مهمترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:
- مسیر رسیدن به خطاها سر راستتر میشود.
- نرم افزار به پایداری مناسبی خواهد رسید.
- از اعتبار دیتای ذخیره شده اطمینان خواهیم داشت.
کجا exceptionها را به دام بیندازیم؟
در یکی از حالتهای زیر:
- لاگ کردن
- متوقف کردن عملیات
- هیچ گاه در بلاک catch هیچ منطقی را پیاده نکنید.
حالت دیگر در استفاه از کتابخانههای دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:
- جلوی این نوع استثنائات را در پایینترین حد ممکن در کد خود بگیرید.
- Exception هایی را catch کنید که میدانید در حالت استثناء، چه کاری را میتوانید انجام دهید.
این به این معنی میباشد که به صورت کلی همه نوع Exception ای را به صورت کلی نگیرید و نوع Exception اختصاصی را در بلاک catch قرار دهید. الان که قرار شد در بعضی از حالتها جلوی استثنائات را بگیریم، خوب است ببینیم چطور باید اینکار را انجام بدیم.
قطعه کد زیر را در نظر بگیرید:
public void CreateCustomer(string name) { Customer customer = new Customer(name); bool result = SaveCustomer(customer); if (!result) { MessageBox.Show("Error connecting to the database. Please try again later."); } } private bool SaveCustomer(Customer customer) { try { using (MyContext context = new MyContext()) { context.Customers.Add(customer); context.SaveChanges(); } return true; } catch (DbUpdateException ex) { if (ex.Message == "Unable to open the DB connection") return false; else throw; } }
همانطور که مشاهده میکنید، در حالتیکه خطایی از نوع DbUpdateException رخ میدهد، مقدار بازگشتی متد را برابر با false میکنیم. اما مشکلی که وجود دارد این است که اینکار به اندازهی کافی خوانا نیست. همچنین honest بودن متد را نقض کردهایم. به علاوه مشکل بزرگتر دیگر این است که ما با بازگرداندن یک مقدار bool، میتوانیم به متد بالاتر اطلاع بدهیم که کار مورد نظر انجام شده یا نه، اما در مورد دلیل انجام نشدن آن، هیچ کاری نمیتوانیم بکنیم. پیشنهاد من برای مقدار بازگشتی متدهایی که احتمال انجام نشدن کاری در آنها میرود، استفاده از یک نوع اختصاصی میباشد.
در اینجا من این نوع را با نام کلاس Result معرفی میکنم. انتظاری که از این نوع اختصاصی داریم:
- Honest بودن متد را نگه دارد.
- خروجی متد را به همراه وضعیت اجرا شدن برگرداند.
- شکل یکسانی را برای خطاها داشته باشد.
- فقط جلوی خطاهای غیر منتظره را بگیرد.
برای مثال کد بالا را به شکل زیر refactor میکنیم:
private Result SaveCustomer(Customer customer) { try { using (var context = new MyContext()) { context.Customers.Add(customer); context.SaveChanges(); } return Result.Ok(); } catch (DbUpdateException ex) { if (ex.Message == "Unable to open the DB connection") Result.Fail(ErrorType.DatabaseIsOffline); if (ex.Message.Contains("IX_Customer_Name")) return Result.Fail(ErrorType.CustomerAlreadyExists); throw; } }
به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.
اگر به امضای متدهای زیر نگاه کنیم، میتوانیم آنها را طبق الگوی CQS دستهبندی کنیم:
به عنوان نمونه یک پیاده سازی از این کلاس را در اینجا قرار دادهام. قطعا میتوانیم پیاده سازیهای بهتری را از این کلاس داشته باشیم. خوشحال میشوم که نظرات خود رو با ما به اشتراک بگذارید. امیدوارم که این قسمت و صحبتهایی که در مورد استثنائات داشتیم، توانسته باشد دیدگاه جدیدی را به کدهایتان بدهد. در ادامهی این سری مطالب، مفاهیم پارادایم برنامه نویسی تابعی را بیشتر مورد بررسی قرار خواهیم داد.
شما برای این کار باید چند مرحله را انجام دهید:
مرحلهی اول: یک پروژهی جدید بسازید
نکته: برای اینکار میتوانید پروژه را از نوع ASP.NET 5 Web Application نیز انتخاب نمایید و مراحل کوتاهتری را طی نمایید. اما راه اندازی دستی قسمتهای مختلف پروژه برای یکبار، به درک بهتر ساختار آن کمک زیادی میکند. از طرفی کار کردن بر روی یک پروژهی تمیز و خالی، برای انتقال دادههای مورد نیاز از یک پروژهی دیگر، از بروز خطاهای پیش بینی نشده و تداخلهای احتمالی نیز جلوگیری میکند.
مرحلهی دوم: اعمال تنظیمات جهت
استفادهی از MVC
همان طور که مشاهده میکنید در این پروژه دیگر خبری از web.config نیست. اما نگران نباشید، امکان اعمال تنظیمات، باز هم وجود دارد و فقط به فایلهای json منتقل شدهاند که project.json هم یکی از آنهاست. برای استفادهی از Microsoft.AspNet.Mvc فقط کافی است فایل project.json را باز کنید و در قسمت "dependencies" پکیج "Microsoft.AspNet.Mvc" را به آن اضافه نمایید تا به صورت خودکار دانلود و به پروژه اضافه گردد.
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", "Microsoft.AspNet.Mvc": "6.0.0-beta5" },
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
<h1>My First MVC6 Website!</h1>
برنامه را اجرا کنید تا خروجی مورد انتظار را مشاهده نمایید.
مرحلهی سوم: انتقال فایلهای پروژهی قبلی به پروژهی جدید
Viewهای مربوط به این controller را نیز به پوشهی Views/Home منتقل نمایید و پروژه را بار دیگر اجرا نمایید. حال باید نمایی از محتوای این فایلها بدون اعمال استایلها را مشاهده نمایید.
همان طور که میدانیم MVC 5 برای استایل صفحات خود از bootstrap استفاده میکند و فایلهای مورد نیاز آن در پوشههای Content و Script -که در root سایت موجود هستند- قرار دارند و نیز ارجاع به این فایلها در Layout.cshtml_ قرار دارد. اما در MVC 6 قضیه کمی متفاوت و البته بهتر شده است. در MVC 6 تمام فایلهای client-side شامل css و js در پوشهی wwwroot قرار دارند و ما میتواینم فایلهای bootstrap و غیره را از پروژهی خود، به این مکان کپی نماییم. ولی روش بهتر، استفاده از ابزارهای bower و gulp برای این کار است. همان طور که میدانید bower یک package manager برای نصب، به روزرسانی و مدیریت کتابخانههای سمت کلاینت و gulp نیز یک task runner برای انجام کارهای مختلف از قبیل script minification و ... در سمت کلاینت است. gulp و bower به طور تو کار در ویژوال 2015 پشتیبانی میشوند و حتی اگر پروژهی خود را از نوع ASP.NET 5 Web Application انتخاب کرده باشید، به صورت پیش فرض از آنها استفاده میکند.
در اینجا برای استفاده، ابتدا یک فایل از نوع Bower JSON Configuration را به root پروژه اضافه کرده و آن را bower.json بنامید و در خاصیت "dependencies" آن bootstrap, jquery, jquery-validation, jquery-validation-unobtrusive را اضافه نمایید.
نکته: من در این قسمت، در restore کردن پکیجها با استفاده از bower، با خطای زیر مواجه شدم:
visual 2015 ECMDERR Failed to execute "git ls-remote --tags --heads git://github.com/jquery/jquery.git", exit code of #-532462766
من از نسخهی Visual Studio 2015 Update 1 CTP استفاده میکنم، ولی ظاهرا این مشکل در نسخههای دیگر هم وجود دارد و فایل bower.cmd ویژوال به درستی کار نمیکند. من برای حل این مشکل، ابتدا git را نصب کردم و در تنظیمات bower، مسیر پیش فرض ویژال رو به مسیر نصب git تغییر دادم. یعنی در پروژه بر روی پوشهی Bower کلیک راست و configure external tools را انتخاب کردم و تیک $(DevEnvDir)\Extensions\Microsoft\Web Tools\External\git را برداشته و در عوض مسیر پیش فرض خودم یعنی C:\Program Files\Git\bin را اضافه کردم.
اگر راه درست و اصولیتری برای حل این مشکل وجود دارد ممنون میشوم دوستان راهنمایی بفرمایند.
خب بعد از اضافه کردن خاصیت dependencies و پکیجهای مورد نیاز، خاصیت exportsOverride را نیز مانند نمونه به فایل bower.json اضافه نمایید.
{ "name": "ASP.NET", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.1.4", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.4" }, "exportsOverride": { "bootstrap": { "js": "dist/js/*.*", "css": "dist/css/*.*", "fonts": "dist/fonts/*.*" }, "jquery": { "": "jquery.{js,min.js,min.map}" }, "jquery-validation": { "": "jquery.validate.js" }, "jquery-validation-unobtrusive": { "": "jquery.validate.unobtrusive.{js,min.js}" } } }
"devDependencies": { "gulp": "3.9.0", "rimraf": "2.4.4", "gulp-concat": "2.6.0" }
var gulp = require('gulp'); var rimraf = require('rimraf'); var paths = { bower: "./bower_components/", lib: "./wwwroot/lib/" }; gulp.task('clean', function (callback) { rimraf(paths.lib, callback); }); gulp.task('default', ['clean'], function () { var bower = { "bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}", "jquery": "jquery/jquery*.{js,map}", "jquery-validation": "jquery-validation/jquery.validate.js", "jquery-validation-unobtrusive": "jquery-validation-unobtrusive/jquery.validate.unobtrusive.js" }; for (var destinationDir in bower) { gulp.src(paths.bower + bower[destinationDir]) .pipe(gulp.dest(paths.lib + destinationDir)); } });
اگر پردازش فوق با موفقیت و بدون خطا انجام شود، میتوانید پکیجهای ایجاد شده را در مسیر wwwroot/lib، مشاهده نمایید.
مرحلهی چهارم: ویرایش برخی از view ها
حال که پکیجهای مورد نیاز پروژه، در پوشهی wwwroot قرار گرفتند، باید view هایی که ارجاعی را به این فایلها دارند، نیز ویرایش نماییم. یکی از این فایلها Layout.cshtml_ است که در مسیر Views/Shared قرار دارد. این فایل را باز کرده و به جای متد ()Styles .Render از عنصر <link> برای لود کردن استایلهای بوت استرپ و غیره استفاده نمایید.
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/css/site.css" />
<script src="~/lib/jquery/dist/jquery.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
مرحلهی پنجم(اختیاری): جایگزین کردن متدهای Html helper با ساختار Tag
Helper
یکی از ویژگیهای جالب و مفید MVC 6 ساختار TagHalper ها هستند که در واقع جایگزینی برای متدهای HtmlHelper و عملکردی مشابه به آنها دارند. البته استفاده از این ویژگی اجباری نیست ولی اگر تعداد ویوهای شما زیاد نیست و خواهان استفادهی از این قابلیت در پروژهی خود هستید، تنها کاری که باید انجام دهید، پیدا کردن HtmlHelperها و جایگزینی آنها به صورت زیر میباشد:
@Html.TextBoxFor(model => model.Name, new { style = "width: 100px" })
<input asp-for="Name" style="width: 100px" />
نتیجه گیری
همان طور ملاحظه نمودید انتقال پروژه از ASP.NET MVC 5 به ASP.NET MVC 6 شامل انجام چند مرحله است و دشواری خاصی ندارد. عمدهی این تغییرات و پیچیدگیها هم مربوط به انتقال فایلهای client-side و نحوهی کار با ابزارهای مدیریت پکیج میشود و البته تنظیماتی که در این بین باید انجام شوند. البته قسمتهای دیگری مانند تنظیمات bundling و connection string نیز با MVC 5 تفاوت هایی دارد و کار با آنها نیز بسیار ساده میباشد.
چقدر خوب میشد اگر IDE ویژوال استودیو دات نت اشیایی از نوع disposable را برای ما مشخص میکرد و در ادامه پیشنهاد استفاده از عبارت using را برای آنها میداد؟
سؤال: این مساله چه اهمیتی دارد؟
به مثال متداول زیر دقت بفرمائید:
string connString = "...";
SqlConnection conn = new SqlConnection(connString);
string cmdString = "select * from author where name=N'test'";
SqlCommand cmd = new SqlCommand(cmdString, conn);
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
while(reader.Read())
{
...
}
conn.Close();
Exception Details: System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
using (SqlConnection connection =
new SqlConnection(connectionString))
{
...
Agent Johnson در حقیقت پلاگینی است برای پلاگین دیگری به نام Resharper . این پلاگین را از آدرس زیر میتوان دریافت کرد (احتمالا به یک پروکسی نیاز خواهد بود ...):
http://code.google.com/p/agentjohnsonplugin/
البته بحث تشخیص اشیاء disposable یکی از تواناییهای آن است. سایر موارد را در آدرس فوق میتوانید مشاهده نمائید.
مقایسه مجوزهای سورس باز
Span در C# 7.2
نوعهای جدید <Span<T و <ReadOnlySpan<T در C# 7.2
نوعهای جدید <Span<T و <ReadOnlySpan<T جهت ارائهی ناحیههای اختیاری پیوستهای از حافظه، شبیه به آرایهها تدارک دیده شدهاند و هدف استفادهی از آنها، تولید برنامههای سمت سرور با کارآیی بالا است.
برای کار با این نوعها، هم نیاز به کامپایلر C# 7.2 است و هم نصب بستهی نیوگت System.Memory:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> <LangVersion>latest</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Memory" Version="4.4.0-preview1-25305-02" /> </ItemGroup> </Project>
Spanها و امکان دسترسی به انواع حافظه
Spanها میتوانند به حافظهی مدیریت شده، حافظهی بومی (native) و حافظهی اختصاص داده شدهی در Stack اشاره کنند. به عبارتی Spanها یک لایه انتزاعی، برفراز تمام انواع و اقسام حافظههایی هستند که میتوانند در اختیار توسعه دهندگان NET. باشند.
- البته اکثر توسعه دهندگان دات نت از حافظهی مدیریت شده استفاده میکنند. برای مثال Stack memory تنها از طریق کدهای unsafe و واژهی کلیدی stackalloc قابل تخصیص است. این نوع حافظه بسیار سریع است و همچنین بسیار کوچک؛ کمتر از یک مگابایت که به خوبی در CPU cache جا میشود. اما اگر در این بین حجم حافظهی تخصیصی بیشتر از یک مگابایت شود، بلافاصله استثنای StackOverflowException غیرقابل مدیریتی را به همراه خاتمهی فوری برنامه به همراه خواهد داشت. برای نمونه از این نوع حافظه در جهت مدیریت رخدادهای داخلی corefx زیاد استفاده میشود.
- حافظهی مدیریت شده، همان حافظهای است که توسط واژهی کلیدی new در برنامه، جهت ایجاد اشیاء، تخصیص داده میشود و طول عمر آن تحت مدیریت GC است.
- حافظهی مدیریت نشده یا بومی از دید GC مخفی است و توسط متدهایی مانند Marshal.AllocHGlobal و Marshal.AllocCoTaskMem در اختیار برنامه قرار میگیرند. این حافظه باید به صورت صریحی توسط توسعه دهنده به کمک متدهایی مانند Marshal.FreeHGlobal و Marshal.FreeCoTaskMem آزاد شود. وب سرور Kestrel مخصوص ASP.NET Core، از این روش جهت کار با آرایههای حجیم، جهت کاهش بار GC استفاده میکند.
مزیت کار با Spanها این است که دسترسی امن و type safeایی را به انواع حافظههای مهیا، جهت توسعه دهندگانی که عموما کدهای unsafe ایی را نمینویسند و با اشارهگرها به صورت مستقیم کار نمیکنند، میسر میکند. برای مثال تا پیش از معرفی Spanها، برای دسترسی به 1000 عنصر یک آرایهی 10 هزار عنصری و ارسال آن به یک متد، نیاز بود تا ابتدا یک کپی از این 1000 عنصر را تهیه کرد. این عملیات از لحاظ میزان مصرف حافظه و همچنین زمان انجام آن، بسیار هزینهبر است. با استفاده از <Span<T میتوان یک دید مجازی از آن آرایه را بدون اختصاص آرایهای و یا آرایههایی جدید، ارائه کرد.
مثالی از کاربرد Spanها جهت کاهش تعداد بار تخصیصهای حافظه
برای نمونه، متد IsValidName زیر، بررسی میکند که طول رشتهی دریافتی حداقل 2 باشد و حتما با یک حرف شروع شده باشد:
static class NameValidatorUsingString { public static bool IsValidName(string name) { if (name.Length < 2) return false; if (char.IsLetter(name[0])) return true; return false; } }
string fullName = "User 1"; string firstName = fullName.Substring(0, 4); NameValidatorUsingString.IsValidName(firstName);
همچنین اگر این اطلاعات را از طریق شبکه دریافت کرده باشیم، ممکن است به صورت آرایهای از حروف دریافت شوند:
char[] anotherFullName = { 'A', 'B' };
NameValidatorUsingString.IsValidName(anotherFullName);
NameValidatorUsingString.IsValidName(new string(anotherFullName));
اکنون در C# 7.2، بازنویسی این متد توسط ReadOnlySpan، به صورت ذیل است:
static class NameValidatorUsingSpan { public static bool IsValidName(ReadOnlySpan<char> name) { if (name.Length < 2) return false; if (char.IsLetter(name[0])) return true; return false; } }
ReadOnlySpan<char> fullName = "User 1".AsSpan(); ReadOnlySpan<char> firstName = fullName.Slice(0, 4); NameValidatorUsingSpan.IsValidName(firstName);
و یا اینبار امکان استفادهی از آرایهای از کاراکترها، بدون نیاز به تخصیص حافظهای جدید، برای بررسی اعتبار مقادیر دریافتی میسر است:
char[] anotherFullName = { 'A', 'B' }; NameValidatorUsingSpan.IsValidName(anotherFullName);
برای نمونه از یک چنین APIایی در پشت صحنهی کتابخانههایی مانند SignalR و یا Roslyn، برای بالا بردن کارآیی برنامه، با کاهش تعداد بار تخصیصهای حافظهی مورد نیاز، بسیار استفاده شدهاست. برای نمونه در NET Core 2.1.، حجم رشتههای تخصیص داده شدهی در فریم ورکهای وابسته، به این ترتیب به شدت کاهش یافتهاست.
مثالهایی از کار با API نوع Span
امکان ایجاد یک Span از یک array
var arr = new byte[10]; Span<byte> bytes = arr; // Implicit cast from T[] to Span<T>
Span<byte> slicedBytes = bytes.Slice(start: 5, length: 2); slicedBytes[0] = 42; slicedBytes[1] = 43; slicedBytes[2] = 44; // Throws IndexOutOfRangeException bytes[2] = 45; // OK
همچنین تغییرات بر روی Span (غیر read only) بر روی آرایهی اصلی نیز تاثیر گذار است. برای مثال در اینجا با تغییر bytes[2]، مقدار arr[2] نیز تغییر میکند.
و یا روش دیگر ایجاد Span استفاده از متد AsSpan است:
var array = new byte[100]; Span<byte> interiorRef1 = array.AsSpan().Slice(start: 20);
Span<byte> interiorRef2 = new Span<byte>(array: array, start: 20, length: array.Length - 20);
محدودیتهای کار با Spanها
- Span تنها یک نوع stack-only است.
- Spanها در بین تردها به اشتراک گذاشته نمیشوند. هر استک در یک زمان تنها توسط یک ترد قابل دسترسی است. بنابراین Spanها thread-safe هستند.
- طول عمر Spanها کوتاه است و قابلیت قرارگیری بر روی heap با طول عمر بیشتر را ندارند؛ یعنی:
- به صورت فیلد در یک نوع non-stackonly قابل تعریف نیستند:
class Impossible { Span<byte> field; }
- به عنوان پارامترهای متدهای async قابل استفاده نیستند. چون در این بین در پشت صحنه یک AsyncMethodBuilder تشکیل میشود که در قسمتی از آن، پارامترها بر روی heap قرار میگیرند.
- هرجائیکه عملیات boxing صورت گیرد، نتیجهی عملیات بر روی heap قرار میگیرد. بنابراین در یک چنین مواردی نمیتوان از Spanها استفاده کرد. برای مثال تعریف Func<T> valueProvider و سپس فراخوانی ()valueProvider.Invoke به همراه یک boxing است. بنابراین نمیتوان از spanها به عنوان نوع آرگومان جنریک استفاده کرد. این مورد هرچند کامپایل میشود، اما در زمان اجرا سبب خاتمهی برنامه خواهد شد و یا نمونهی دیگر، عدم امکان دسترسی به آنها توسط reflection invoke APIs است که سبب boxing میشود.
معرفی نوع <Memory<T
با توجه به محدودیتهای Span و خصوصا اینکه به عنوان پارامتر متدهای async قابل استفاده نیست (چون بر روی stack ذخیره میشوند)، نوع دیگری به نام <Memory<T نیز به همراه C# 7.2 ارائه شدهاست. البته این نوع هنوز به بستهی نیوگت فوق اضافه نشدهاست و به همراه ارائه نهایی NET Core 2.1. ارائه خواهد شد.
این نوع، محدودیت <Span<T را نداشته و قابلیت ذخیره سازی بر روی heap را دارا است.
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream) { int bytesRead = await stream.ReadAsync(buffer); return Checksum(buffer.Span.Slice(0, bytesRead)); // Or buffer.Slice(0, bytesRead).Span }
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو میشوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اونها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.
روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش میدونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit میشوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() ) { //Statement1 //Statement2 //Statement3 scope.Complete(); }
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() ) { if ( result == 0 ) { throw new ApplicationException(); } scope.Complete(); }
نکته 1: میزان Timeout در این تراکنشها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنشها میتوانید از کلاس TransactionManager استفاده کنید. به صورت زیر :
var defaultTimeout = TransactionManager.DefaultTimeout var maxTimeout = TransactionManager.MaximumTimeout
TransactionOptions option = new TransactionOptions(); option.Timeout = TimeSpan.MaxValue;
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
1 - Required : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده میشود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید میشود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمیشود.
using(TransactionScope scope1 = new TransactionScope()) { try { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress)) { //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب میشود } //شروع محدوده تراکنش } catch {} //Rest of scope1 }
1-این روش از تراکنشهای توزیع شده پشتیبانی میکند . یعنی میتونید از چند تا منبع داده استفاده کنید یا میتونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required)) { string strCmd = "SQL to Execute"; conn = new SqlClient.SqlConnection("Connection to DB1"); conn.Open() objCmd = new SqlClient.SqlCommand(strCmd, conn); objCmd.ExecuteNonQuery(); string strCmd2 = "SQL to Execute"; conn2 = new SqlClient.SqlConnection("Connection to DB2"); conn2.Open() objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2); objCmd2.ExecuteNonQuery(); }
3- با DataProviderهای متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنشهای تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required)) { ... } using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) { ... } using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress)) { ... } }
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنشهای موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنشهای استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)
موفق باشید.
ممنون - مطلب کاربردی و مفیدی بود
شخصا این Captcha را به انواع دیگر ترجیح میدم چرا که:
1- شخصی سازی شده
2- ارجاع به سایت واسطی نداره
3- هیچ گونه سرباری نداره
4- اعمال هر گونه تغییر و اصلاح مناسب با فرهنگ و کشور و یا حتی نوع سایت قابل انجام است
بازهم تشکر میکنم