استخدام Try Catch Block لاكتشاف الاخطاء
-
اكيد الأخطاء تحدث في عالم البرمجة وشو ما كنت مبرمج شاطر وقوي هذا لا يعني ان ما تغلط،بعض هذه الاخطاء سهلة ويمكن معالجتها بسهولة،وبعظها معقد يصعب معرفتها بسهولة. لذلك الصحيح نتبع هذه الأخطاء واكتشافها والعمل على حلها لغاية تحسين الأنظمة بشكل أساسي، بنتعلم في هذا الدرس كيف ممكن اكتشاف الاخطاء .
من الامثلة على الأخطاء التي قد تحدث
⦁ خطأ في اتصال قاعدة البيانات Database connection error.
⦁ خطأ timeout error - بسبب خطأ ما في الشبكة.
⦁ خطأ في التعليمات البرمجية.
⦁ مشاكل مادية في السيرفرات مثلا.
وغيرها الكثير من الأخطاء لذا من الأفضل معالجة هذه الأخطاء بطريقة مفيدة وعملية، تقدم MVC Core مجموعة من الأدوات لغاية هذا الغرض. سنتعرف هنا على هذه الأدوات والفائدة مها وكيفية استخدامها
طريقة Try Catch Block
يوجد عدة أنواع من الاخطاء Exceptions وخلونا نبدأ بتطييق امثلة لفهم الموضوع:
نبدأ بالخطأ من نوع SqlException
في هذا النوع SqlException يمكنك اكتشاف الأخطاء التي لها علاقة ب بقواعد البيانات، التي تحدث أثناء عمليات ADO.NET باستخدام Try Catch Block.
على سبيل المثال، شوف الكود في الاسفل حيث عملنا اضافة ل Try Catch Block في Delete action :
[HttpPost]
public IActionResult Delete(int id)
{
string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
string sql = $"Delete From Students Where Id={id}";
using (SqlCommand command = new SqlCommand(sql, connection))
{
connection.Open();
try
{
command.ExecuteNonQuery();
}
catch (SqlException ex)
{
ViewBag.Result = "Operation got error:" + ex.Message;
}
connection.Close();
}
}
return RedirectToAction("AllStudent");
}
نفذنا الكود باستخدام ()ExecuteNonQuery داخل try block، حيث إذا حدث أي exception ، فيمكن الإمساك به في الجزء catch . وعملنا تحديد لنوع الأخطاء من النوع SqlException هنا
داخل catch block، تم إضافة متغير من نوع SqlException باسم ex بحيث يتم عرض الخطأ داخل متغير ViewBag ، ويعدها بنعمل إظهار هذه رسالة باخطأ في View.
الكود الذي يعمل على ذلك:
catch (SqlException ex)
{
ViewBag.Result = "Operation got error:" + ex.Message;
}
طبعا اكيد ممكن نعمل طريقة مختلفه لمعرفة الاخطاء، فا مثلا ممكن نعمل جدول في قاعدة البيانات وفي حال حدوث اي مشكلة بنسجل الخطأ في قاعدة البيانات للرجوع الها ، او ممكن نبعث ايميل بتفاصيل الخطأ .
يمكن استخدام Exception فقط داخل الجزء catch وفي هاذ الجزء بتم امساك الخطأ بغض النظر عن النوع.
Database Transaction
Database Transaction هي مجموعة من العمليات التي يجب أن تنجح جميعها أو تفشل جميعها مره واحده.تعتبر Transactions ضرورية للتأكد من أن البيانات الموجودة في قاعدة البيانات آمنة وصحيحة ومتسقة. اكيد ما بصير بعض العمليات ينجح جزء وجزء يفشل وحتي نفهم الموضوع بشكل احسن خلونا نشوف المثال التالي:
من الأمثلة الكلاسيكية تحويل الأموال في البنك من حساب إلى آخر. لنفترض أن هناك حسابين لشخصين تم تسميتهما
حساب X وحساب Y.
فا لنفرض ان X عمل تحويل الأموال الى Y في هذه العملية اكيد بكون في حركتين هما:
- خصم الأموال من حساب X
- إضافة الأموال الى حساب Y
لنفرض ان تم تحويل الاموال من حساب x وتمت العمليه بنجاح، لكن اضافة الاموال في حساب y فشلت. فا اكيد الان احنا في مشكلة لأنه غير منطقي وغير صحيح خصم الأموال من الحساب الأول دون إضافتها إلى الحساب الثاني.
طيب بمثل هيك حالة شو الحل.
الحل هو تطبيق العملية دفعه واحده. بمعني يا بتنجح كل العمليات او بتم الغاء العمليات كلها في حال اي فشل. طيب في حالة فشل العملية،هون دور عمل rolled back للعملية بالكامل واعادتها إلى حالتها الأصلية. من ناحية أخرى، إذا نجحت جميع الخطوات، يتم عمل committed المعاملة.
SqlTransaction class
في ADO.NET يتم دعم هذه transaction الي هي مشتقة من SqlTransaction class والموجودة ضمن namespace System.Data.SqlClient.
بقدر نحكي ان SqlTransaction class يحتوي على الاوامر التالية بهذا الخصوص:
- ()Commit حيث يتم عمل commits، بمعنى اعتماد تنفيذ الحركات على قاعدة البيانات.
- ()Rollback حيث يتم عمل rollbacks في حال الفشل وعدم تنفيذ الحركات(بمجرد حدوث اي فشل يتم الغاء العمليه كاملة
طيب كيف بتم تنفيذ SqlTransaction
سنقوم بتنفيذ transaction صغيرة باستخدام SqlTransaction class. في قاعدة البيانات، خلونا نعمل إضافة جدولين في قاعدة البيانات الأول باسم StudentInvoices والثاني باسم StudentCourses الغرض من هذه الجداول هو عندما يقوم الطالب بالتسجيل بكورس معين سيتم إضافة فاتورة للطالب في جدول StudentInvoices وبنفس الوقت يجب إضافة الطالب الى جدول StudentCourse، في هذه الحالة يتم التأكد من دفع مبلع الكورس واضافة الطالب الى هذا الكورس
إضافة الجداول :
جدول StudentInvoices وبتكون من :
- InvoiceId وهو primary key.
- StudentId الذي يربط هذا الجدول مع جدول الطلاب السابق.
- InvoiceNum يحتوي رقم الفاتورة
- Price يحتوي على قيمة الفاتورة
- Status تسجيل الحالة في حالة تم الدفع بنجاح او فشل
- Note لغاية تسجيل أي ملاحظات.
- CreatedDate تاريخ انشاء الفاتورة
- AddBy تسجيل رقم الشخص الذي انشي الفاتورة
انتقل الى MSSQ ثم أضف الكود التالي لإنشاء هذا الجدول.
USE [StudentsAcademy-Test]
GO
/****** Object: Table [dbo].[StudentInvoices] Script Date: 01/10/2021 13:40:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[StudentInvoices](
[InvoiceId] [int] IDENTITY(1,1) NOT NULL,
[StudentId] [int] NULL,
[InvoiceNum] [nvarchar](10) NULL,
[Price] [money] NULL,
[Status] [int] NULL,
[Note] [nvarchar](500) NULL,
[CreatedDate] [datetime] NULL,
[AddBy] [int] NULL,
CONSTRAINT [PK_StudentInvoices] PRIMARY KEY CLUSTERED
(
[InvoiceId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[StudentInvoices] WITH CHECK ADD CONSTRAINT [FK_StudentInvoices_Students] FOREIGN KEY([StudentId])
REFERENCES [dbo].[Students] ([StudentId])
GO
ALTER TABLE [dbo].[StudentInvoices] CHECK CONSTRAINT [FK_StudentInvoices_Students]
GO
جدول StudentCourses وبتكون من :
- StudentCourseId وهو primary key.
- StudentId الذي يربط هذا الجدول مع جدول الطلاب السابق.
- CourseId الذي يربط هذا الجدول مع جدول Courses.
- InvoiceId يحتوي على رقم الفاتورة الخاصة بهاذ الكورس
- CreatedDate تاريخ انشاء هذا الكورس
- AddBy إضافة عن طريق .
- Status حالة الفاتورة بحيث يتم تحديد حالة الفاتورة اذا كانت صالحة ام غير ذلك.
انتقل الى MSSQ ثم أضف الكود التالي لإنشاء هذا الجدول.
USE [StudentsAcademy-Test]
GO
/****** Object: Table [dbo].[StudentCourses] Script Date: 01/10/2021 13:47:56 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[StudentCourses](
[StudentCourseId] [int] IDENTITY(1,1) NOT NULL,
[StudentId] [int] NULL,
[CourseId] [int] NULL,
[InvoiceId] [int] NULL,
[CreatedDate] [datetime] NULL,
[AddBy] [int] NULL,
[Status] [int] NULL,
CONSTRAINT [PK_StudentCourses] PRIMARY KEY CLUSTERED
(
[StudentCourseId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[StudentCourses] WITH CHECK ADD CONSTRAINT [FK_StudentCourses_Courses] FOREIGN KEY([CourseId])
REFERENCES [dbo].[Courses] ([CoursesId])
GO
ALTER TABLE [dbo].[StudentCourses] CHECK CONSTRAINT [FK_StudentCourses_Courses]
GO
ALTER TABLE [dbo].[StudentCourses] WITH CHECK ADD CONSTRAINT [FK_StudentCourses_Students] FOREIGN KEY([StudentId])
REFERENCES [dbo].[Students] ([StudentId])
GO
ALTER TABLE [dbo].[StudentCourses] CHECK CONSTRAINT [FK_StudentCourses_Students]
GO
يقوم الطالب هنا باختيار كورس ثم يجب تسديد قيمة فاتورة هذا الكورس، في حال نجاح السداد يجب إضافة هذا الكورس الى ملف الطالب، في حال الفشل يجب الخروح وعدم اكمال العملية.
في هذا الدرس سنقوم بإضافة هذه الاكواد مباشرة من SQL Query ولاحقا سنقوم بربط جميع هذه الصفحات مع بعض.
تاكد من إضافة بعض السجلات في جدول Courses

تمام الان دور نعمل اضافة action لاضافة الكورس لذا :
انتقل إلى StudentsController وأضف action جديد باسم AddCourse ، وبعد هيك بنضيف الكود التالي:
public IActionResult AddCourse()
{
return View();
}
[HttpPost]
public IActionResult AddCourse(bool throwEx, int StudentId, int CourseID)
{
string result = "";
string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
var cmdAddStudentInvoice = new SqlCommand($"declare @CourseID int=1,@StudentId int=1 Declare @InvoiceID int INSERT INTO StudentInvoices(StudentId, Status, CreatedDate, AddBy) VALUES(@StudentId, 1, getdate(), 2)set @InvoiceID = SCOPE_IDENTITY(); select @InvoiceID ", connection);
var cmdAddStudentCourse = new SqlCommand($"declare @CourseID int=1,@StudentId int=1 INSERT INTO StudentCourses(StudentId, CourseId,InvoiceId, CreatedDate, AddBy, Status) VALUES(@StudentId, @CourseID,@InvoiceId, GETDATE(), 1, 1)", connection);
connection.Open();
// We will get this from the connection object.
SqlTransaction tx = null;
try
{
tx = connection.BeginTransaction();
// Enlist the commands into this transaction.
cmdAddStudentInvoice.Transaction = tx;
cmdAddStudentCourse.Transaction = tx;
// Execute the commands.
int InvoiceID= cmdAddStudentInvoice.ExecuteNonQuery();
SqlParameter parameter = new SqlParameter
{
ParameterName = "@InvoiceID",
Value = InvoiceID,
SqlDbType = SqlDbType.Int,
Size = 200
};
cmdAddStudentCourse.Parameters.Add(parameter);
cmdAddStudentCourse.ExecuteNonQuery();
// Simulate error.
if (throwEx)
{
throw new Exception("Sorry! Database error! Transaction failed");
}
// Commit it!
tx.Commit();
result = "Success";
}
catch (Exception ex)
{
// Any error will roll back transaction. Using the new conditional access operator to check for null.
tx?.Rollback();
result = ex.Message;
}
finally
{
connection.Close();
}
}
return View((object)result);
}
نفهم الكود :
لاحظ أن action يحتوي على parameter من نوع bool باسم throwEx الذي نحصل عليه من select control.
نظرًا لأن الحركة ستشتمل على عمليتين عملية دفع مبلغ الفاتورة واضافتها ثم إضافة الكورس، لذلك سنقوم بإنشاء جملتين من نوع SqlCommand objects
- إضافة فاتورة للكورس ودفع المبلغ حيث ترجع رقم الفاتورة من هذه العملية:
الكود الذي يعمل على ذلك :
var cmdAddStudentInvoice = new SqlCommand($"declare @CourseID int=1,@StudentId int=1 Declare @InvoiceID int INSERT INTO StudentInvoices(StudentId, Status, CreatedDate, AddBy) VALUES(@StudentId, 1, getdate(), 2)set @InvoiceID = SCOPE_IDENTITY(); select @InvoiceID ", connection);
عرفنا هنا رقم الكورس ورقم الطالب داخل الكود بشكل ثابت (الغرض فهم المثال هنا) سنتعلم لاحقا كيفية الربط بين الصفحات.
- إضافة كورس للطالب مربوط مع رقم الفاتورة التي تم الحصول عليها من الكود السابق:
الكود الذي يعمل على ذلك:
var cmdAddStudentCourse = new SqlCommand($"declare @CourseID int=1,@StudentId int=1 INSERT INTO StudentCourses(StudentId, CourseId,InvoiceId, CreatedDate, AddBy, Status) VALUES(@StudentId, @CourseID,@InvoiceId, GETDATE(), 1, 1)", connection);
في هذا الكود اضفنا parameter باسم InvoiceId@ الي بيرجع من إضافة رقم الفاتورة من الكود السابق.
قمنا بتعريف متغير من نوع SqlTransaction باسم tx
بعد ذلك، داخل try block، بدأنا تنفيذ العمليات database transaction
الكود الذي يعمل على ذلك:
tx = connection.BeginTransaction();
الذي يعطي SqlTransaction object.
بعد ذلك قمنا بتعيين SqlTransaction object الناتج هذا على Transaction ل SqlCommand objects سابقًا:
الكود الذي يعمل على ذلك :
cmdAddStudentInvoice.Transaction = tx;
cmdAddStudentCourse.Transaction = tx;
لاحظ ان الجملة الأولى cmdAddStudentInvoice تعيد رقم الفاتورة كنتيجة للتنفيذ حيث قمنا بحفظها بمتغير باسم InvoiceID
الكود الذي يعمل على ذلك:
int InvoiceID= cmdAddStudentInvoice.ExecuteNonQuery();
ثم اضفنا هذا المتغير ك Parameter الى الامر الثاني لإضافة الكورس
الكود الذي يعمل على ذلك:
SqlParameter parameter = new SqlParameter
{
ParameterName = "@InvoiceID",
Value = InvoiceID,
SqlDbType = SqlDbType.Int,
Size = 200
};
cmdAddStudentCourse.Parameters.Add(parameter);
ثم تم تنفيذ إضافة الكورس برقم الفاتورة
الكود الذي يعمل على ذلك:
cmdAddStudentCourse.ExecuteNonQuery();
إذا لم يحدث أي Exception فسيتم تنفيذ commit للعملية بالكامل.
الكود:
tx.Commit();
في المتغير throwEx يتم إرسال قيمة true او false فقط لغرض التجربة بحيث يتم التحقق من القيمة (يحتوي على قيمة HTML Select). إذا كان يحتوي قيمة true ،فيتم استدعاء الكود:
if (throwEx)
{
throw new Exception("Sorry! Database error! Transaction failed");
}
وإذا حدث اي exception ، فسيتم استدعاء catch block. ويتم عمل ()Rollback للعملية ،ولن يتم تنفيذ أي شيء في هذه العملية ثم يتم ارجاع رسالة الخطأ في المتغير result :
tx?.Rollback();
result = ex.Message;
واكيد نحنا بحاجة الى اضافة View قبل تشغيل الصفحة لغرض ارسال الامر الى Action
إضافة View
قم بإنشاء View باسم AddCourse.cshtml داخل مجلد Views ► Students ثم أضاف الكود التالي :
model string
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Add Nre Course with Invoice</title>
<link rel="stylesheet" asp-href-include="lib/bootstrap/css/bootstrap.min.css" />
</head>
<body>
<div class="container-fluid">
<h1>Add Course Math with 100 $ Price to Hatem</h1>
@if (Model != null)
{
<h2 class="alert alert-danger">@Model</h2>
}
<form method="post">
<div class="form-group">
<label>Throw Excepton</label>
<select name="throwEx" class="form-control">
<option>false</option>
<option>true</option>
</select>
</div>
<div class="text-center panel-body">
<button type="submit" class="btn btn-sm btn-primary">Create Invoice</button>
</div>
</form>
</div>
</body>
</html>
يحتوي هذا View على model من نوع string ويتم استخدامه لإظهار نتيجة transaction داخل علامة h2:
الكود
@if (Model != null)
{
<h2 class="alert alert-danger">@Model</h2>
}
تم إضافة select control داخل form باسم throwEx. من خلال هذا control، يمكن التحكم بتنفيذ العملية او ارجاع خطأ.
<select name="throwEx" class="form-control">
<option>false</option>
<option>true</option>
</select>
قم بتشغيل المشروع بالانتقال الى الرابط
https://localhost:44382/students/AddCourse

في حال كانت قيمة Throw Exception تساوي True فستكون النتيجة خطأ كالتالي:

في حال كانت غير ذلك فسيتم تنفيذ النتيجة واضافة فاتورة وكورس الى الجداول المطلوبة.
اترك تعليقك