المساعد الشخصي الرقمي

مشاهدة النسخة كاملة : الدرس الثالث : عالم ADO.net وما يتعلق به - الجزء الثالث - دورة .net 2008


khaledbelal
03-11-2010, 03:14 PM
بسم الله الرحمن الرحيم .
السلام عليكم ورحمة الله وبركاته .

في هذا الدرس سنحاول التعرف على ماهية الوضع المتصل Connected Layer في ADO.net .
يسمح لك الوضع المتصل Connected Layer والذي يعتمد على وجود اتصال مفعل بقاعدة بياناتك اثناء عمليات التعديل بالتعامل مع قاعدة بياناتك من خلال connection للاتصال و command لتنفيذ الاستعلامات اضافة إلى data reader الخاصة بال DataProvider الذي تنوي التعامل معه وفي حالتنا هذه هو ال SQL .

سنقوم الآن بعمل تطبيق بسيط لعرض كافة اسماء الموظفين في قائمة ، لذا قم بالمتابعة معنا خطوة بخطوة :

1- جلب المكتبات التي سوف نتعامل معها وهي في حالتنا هذه System.Data.SqlClient :
C#:

using System.Data.SqlClient;

vb.net:

Imports System.Data.SqlClient


2- التعامل مع Connection من اجل الوصول إلى قاعدة البيانات الخاصة بنا وفتحها :
C#:

SqlConnection cn = new SqlConnection();

cn.ConnectionString = @"Data Source=AHMED-PC\SQLEXPRESS;Initial Catalog=Employee;Integrated Security=True;Pooling=False";
cn.Open();
vb.net:

Dim cn As New SqlConnection()

cn.ConnectionString = "Data Source=AHMED-PC\SQLEXPRESS;Initial Catalog=Employee;Integrated Security=True;Pooling=False"
cn.Open()
*** كيف تعرف ال ConnectionString .
أ- هناك العديد من المواقع التي تقدم لك خدمة معرفة ال ConnectionString مثل موقع : www.connectionstrings.com .
ب- لو كنت تعمل من خلال قاعدة بيانات داخل بيئة عمل .net يمكنك معرفة ال ConnectioString لها باختيارها من قائمة Server Explorer ومن ثم مشاهدة ال Properties بالشكل التالي :

http://vb4arab.com/vb/uploaded/3/01216313959.jpg

ج- باستخدام اي من أدوات .net الجاهزة والتي سنتعرف على بعض منها في مراحل قادمة .

3- كتابة جملة استعلام بسيطة لطلب كل أسماء الموظفين الأولى + الثانية على شكل fullname بالاضافة إلى العمر - لو لم تفهم هذه الجملة راجع دروس TSQL - :
C#:

string strSQL = "Select [First Name]+[Last Name] as [Full Name], Age From Employee_info";
SqlCommand myCommand = new SqlCommand(strSQL, cn);
vb.net:

Dim strSQL As String = "Select [First Name]+[Last Name] as [Full Name], Age From Employee_info"
Dim myCommand As New SqlCommand(strSQL, cn)
4- تعريف DataReader لقراءة البيانات وتنفيذها على شكل ExecuteReader :

C#:

SqlDataReader myDataReader;
myDataReader = myCommand.ExecuteReader();
vb.net:

Dim myDataReader As SqlDataReader
myDataReader = myCommand.ExecuteReader()
5- الدوران على كافة الحقول ووضعها في ListBox1 :
C#:

string listItem = "";
while (myDataReader.Read())
{
listItem= "Full Name: " + myDataReader["Full Name"].ToString() + " Age: " + myDataReader["Age"].ToString();
listBox1.Items.Add(listItem);
}
vb.net:

Dim listItem As String = ""
While myDataReader.Read()
listItem = "Full Name: " + myDataReader("Full Name").ToString() + " Age: " + myDataReader("Age").ToString()
listBox1.Items.Add(listItem)
End While
سيكون ناتج تنفيذ هذا الكود شيئاً مثل هذا :

http://vb4arab.com/vb/uploaded/3/11216313959.jpg

6- اغلاق وسائل الاتصال وعدم تركها مفتوحة :
C#:

myDataReader.Close();

cn.Close();
vb.net:

myDataReader.Close()

cn.Close()
*** هناك حل آخر لفتح قاعدة البيانات واغلاقها مباشرة بعد انتهاء تنفيذ المهمة ، اثناء عملك ExecuteReader يمكنك تحديد CommandBehavior يحدث بعد اغلاقها ، يحتوي هذا ال Enum على الخيارات التالية :

http://vb4arab.com/vb/uploaded/3/21216313959.jpg

الخيار الأول هو فقط ما يهمنا ، في هذه الحالة سيكون شكل الكود بالشكل التالي :
C#:

myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConne ction);

vb.net:

myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConne ction)

هنا لن نقلق مجدداً بشأن اغلاق ال Coonection حيث سيتم اغلاقه تلقائياً بعد كتابة هذا السطر :
C#:

myDataReader.Close();


vb.net:

myDataReader.Close()

- طبعاً لا تنس أن بامكانك عمل نفس جملة الاستعلام Update او Delete أو Insert بدلاً من Select ، وحسب جملة الاستعلام المطلوبة .

- التعامل مع ConnectionStringBuilder :

يوفر لك هذا الكائن طريقة تفصيلية لبناء ال ConnectioString الخاص بك ، هذا المثال يوضح الأكثر استخداماً :
C#:

SqlConnectionStringBuilder cnStrBuilder = new SqlConnectionStringBuilder();
cnStrBuilder.InitialCatalog = "Employee";
cnStrBuilder.DataSource = @"(local)\SQLEXPRESS";
cnStrBuilder.ConnectTimeout = 30;
cnStrBuilder.Password = "124";
cnStrBuilder.UserID = "Ahmed";
SqlConnection cn = new SqlConnection();
cn.ConnectionString = cnStrBuilder.ConnectionString;
cn.Open();

vb.net:

Dim cnStrBuilder As New SqlConnectionStringBuilder()
cnStrBuilder.InitialCatalog = "Employee"
cnStrBuilder.DataSource = "(local)\SQLEXPRESS"
cnStrBuilder.ConnectTimeout = 30
cnStrBuilder.Password = "124"
cnStrBuilder.UserID = "Ahmed"
Dim cn As New SqlConnection()
cn.ConnectionString = cnStrBuilder.ConnectionString
cn.Open()


حيث تجد :

- InitialCatalog لتحديد اسم قاعدة البيانات
- DataSource لتحديد مسار قاعدة البيانات
- ConnectTimeout لتحديد الوقت الذي يمكن استغراقه من اجل محاولة الوصول إلى قاعدة البيانات او ايقاف العملية عند انتهاءه .
- Password كلمة مرور قاعدة البيانات إن وجدت .
- UserID اسم المستخدم لقاعدة البيانات إن وجد .

هناك العديد من العناصر الأخرى أيضاً يمكنك استعراضها من هنا :
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder_m embers.aspx

التعامل مع الفئة Command :

ال Command هي جملة الاستعلام التي نستخدمها لتنفيذ اي نوع من العمليات على قاعدة البيانات ، تنقسم إلى ثلاث انواع رئيسية :

- StoredProcedure
- TableDirect
- Text

في المثال السابق استخدمنا ال Command كجملة استعلام نصية مباشرة Text ، كانت بالشكل التالي مثلاً :
C#:

string strSQL = "Select * From Employee_Info";
SqlCommand myCommand = new SqlCommand(strSQL, cn);

vb.net:

Dim strSQL As String = "Select * From Employee_Info"
Dim myCommand As New SqlCommand(strSQL, cn)

أو بهذا الشكل :
c#:

SqlCommand testCommand = new SqlCommand();
testCommand.Connection = cn;
testCommand.CommandText = strSQL;

vb.net:

Dim testCommand As New SqlCommand()
testCommand.Connection = cn
testCommand.CommandText = strSQL

اثناء التنفيذ قمنا بربطها مباشرة باستخدام ExecuteReader ، في الواقع هناك عدة طرق عدة طرق للتنتفيذ :

ExecuteReader : في حالة كون الناتج عدد كبير من البيانات ، يتم تعريف DataReader وربط الناتج به لقراءته ، وهو ما تعرفنا عليه في مراحل سابقة .
ExecuteNonQuery : في حالة عدم وجود نواتج اصلاً ، مثل تعريف عملية update او Delete حيث الناتج الوحيد هو تنفيذ العملية من عدمه ، سيتم التعرف عليه لاحقاً .
ExecuteScalar : في حالة كون الناتج وحيد ، مثل الاستعلام عن فقط عن الاسم الأول للشخص صاحب الرقم القومي xxxxxx .
ExecuteXmlReader : تنفيذ الناتج واعادته على شكل XML يتم تعريف .XmlReader وربط الناتج به لقراءته ، يمكنك الرجوع إلى دروس XML لمعرفة المزيد عن XmlReader .

- استخدام ال Parameterized Command Objects :

كما لاحظت من الدرس الذي تحدثنا فيه عن ال SQL Injection ، فإن الطريقة التقليدية لجمل الاستعلام تظل خطرة ، لذا نبدا باستخدام Parameters لجمل الاستعلام لدينا ايضاً حتى لو لم نكن نتعامل مع Stored Procedure .

لو أخذنا المثال التالي للتجربة :

C#:

string strSQL = "Select [First Name]+[Last Name] as [Full Name], Age From Employee where ID=" + ID;
SqlCommand myCommand = new SqlCommand(strSQL, cn);

vb.net:

Dim strSQL As String = "Select [First Name]+[Last Name] as [Full Name], Age From Employee where ID=" + ID
Dim myCommand As New SqlCommand(strSQL, cn)


ستجد اننا قادرين بمبادئ ال SQL Injection من اختراق هذا النظام بسهولة ، لذا البديل يكون باستخدام وتعريف SqlParameter:
C#:

string sql = string.Format("Select [First Name]+[Last Name] as [Full Name], Age From Employee where ID=@ID");
using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
// Fill params collection.
SqlParameter param = new SqlParameter();
param.ParameterName = "@ID";
param.Value = 10;
param.SqlDbType = SqlDbType.Int;
cmd.Parameters.Add(param);
// تنفيذ جملة الاستعلام بأي شكل هنا
cmd.ExecuteNonQuery();
}


vb.net:

Dim sql As String = String.Format("Select [First Name]+[Last Name] as [Full Name], Age From Employee where

ID=@ID")
Using cmd As New SqlCommand(sql, Me.sqlCn)
' Fill params collection.
Dim param As New SqlParameter()
param.ParameterName = "@ID"
param.Value = 10
param.SqlDbType = SqlDbType.Int
cmd.Parameters.Add(param)

' تنفيذ جملة الاستعلام بأي شكل هنا
cmd.ExecuteNonQuery()
End Using

هنا كما لاحظت قمنا بتمرير باميترات إلى جملة استعلام قمنا نحن بكتابتها ضمن البرنامج .

- الحالة الثانية التي لدينا وهي الشائعة الاستخدام هي حالة تعريف Stored Procedure ، لو افترضنا مثلاً جملة الاستعلام التي أنشأناها في أول درس لنا بالشكل التالي :


ALTER PROCEDURE dbo.GetAge
@condition int,
@firstname char(10) output
AS
SELECT @firstname=[First Name] from Employee_info where age > @condition


وقمنا بحفظها باسم GetAge ، الآن نريد استدعاءها من البرنامج ، يتم ذلك بالشكل التالي مثلاً :

C#:

using (SqlCommand cmd = new SqlCommand("GetAge", cn))
{
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter param = new SqlParameter();
param.ParameterName = "@condition";
param.SqlDbType = SqlDbType.Int;
param.Value = myAge;
param.Direction = ParameterDirection.Input;
cmd.Parameters.Add(param);

param = new SqlParameter();
param.ParameterName = "@firstnameName";
param.SqlDbType = SqlDbType.Char;
param.Size = 10;
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);

cmd.ExecuteNonQuery();
MessageBox.Show(cmd.Parameters["@firstName"].Value.ToString());
}

vb.net:

Using cmd As New SqlCommand("GetAge", cn)
cmd.CommandType = CommandType.StoredProcedure

Dim param As New SqlParameter()
param.ParameterName = "@condition"
param.SqlDbType = SqlDbType.Int
param.Value = myAge
param.Direction = ParameterDirection.Input
cmd.Parameters.Add(param)



param = New SqlParameter()
param.ParameterName = "@firstnameName"
param.SqlDbType = SqlDbType.[Char]
param.Size = 10
param.Direction = ParameterDirection.Output
cmd.Parameters.Add(param)



cmd.ExecuteNonQuery()
MessageBox.Show(cmd.Parameters("@firstName").Value.ToString())
End Using


كما لاحظت ، قمنا بتعريف نوع ال Command هنا نظراً لإن الافتراضي هو Text ، ومن ثم قمنا بتعريف متغير الدخول ومتغير الخروج أيضاً .

- التعامل مع DataReaders :

كما شاهدت في المثال السابق ، يمكن قراءة البيانات من DataReader بالشكل التالي مثلاً :
C#:

string listItem = "";
while (myDataReader.Read())
{
listItem= "Full Name: " + myDataReader["Full Name"].ToString() + " Age: " + myDataReader["Age"].ToString();
listBox1.Items.Add(listItem);
}

vb.net:

Dim listItem As String = ""
While myDataReader.Read()
listItem = "Full Name: " + myDataReader("Full Name").ToString() + " Age: " + myDataReader("Age").ToString()
listBox1.Items.Add(listItem)
End While


حيث تعود الدالة Read ب true ما دام هناك سجلات للقراءة ، وفي نفس الوقت مع كل استدعاء لها تنتقل إلى السجل التالي ...

طريقة القراءة تكون بتحديد الحقل المراد قراءته myDataReader["Age"] او بتحديد رقمه في الترتيب myDataReader[2] مثلاً .

الخاصية FieldCount تعطينا عدد النتائج المعادة ، لذا يمكننا تنفيذ نفس العملية السابقة بالشكل التالي :
c#:

for (int i = 0; i < myDataReader.FieldCount; i++)
{
listItem= "Full Name: " + myDataReader["Full Name"].ToString() + " Age: " + myDataReader["Age"].ToString();
listBox1.Items.Add(listItem);
}

vb.net:

For i As Integer = 0 To myDataReader.FieldCount - 1
listItem = "Full Name: " + myDataReader("Full Name").ToString() + " Age: " + myDataReader("Age").ToString()
listBox1.Items.Add(listItem)
Next


NextResult :

تمكنك ال DataReader من تعريف جملتي استعلام لاعادة الناتج ، فمثلاً لاعادة اسماء الموظفين ثم اسماء المشاريع :
C#:

string strSQL = "Select * From Employee_info;Select * from projects";
SqlCommand myCommand = new SqlCommand(strSQL, cn);
do
{
while (myDataReader.Read())
{
for (int i = 0; i < myDataReader.FieldCount; i++)
listItem= "Data: " + myDataReader[0].ToString() ;
}
} while (myDataReader.NextResult());

vb.net:

Dim strSQL As String = "Select * From Employee_info;Select * from projects"
Dim myCommand As New SqlCommand(strSQL, cn)
Do
While myDataReader.Read()
For i As Integer = 0 To myDataReader.FieldCount - 1
listItem = "Data: " + myDataReader(0).ToString()
Next
End While
Loop While myDataReader.NextResult()

Data Access Layer :

في التطبيقات الجدية ، لا يتم وضع الكود مع المظهر مع سيناريو وعمليات البرنامج اضافة لطبقة البيانات، بل يتم فصل كل منها في طبقة منفصلة وهو ما يعرف باسم Layers ، لمعرفة المزيد عن هذا الموضوع يمكنك مراجعة الرابط التالي :
http://www.al-asiri.com/ShowRecord.aspx?Action=Open&id=cefa426c-d9e0-4625-a66b-87fd6082ff89
وهناك تطبيق ايضاً هنا :
http://vb4arab.com/vb/showthread.php?t=10969&highlight=%C7%E1%D8%C8%DE%C 7%CA

في هذه المرحلة ، سنحاول عمل data layer تكون خاصة بالتعامل مع قواعد البيانات لتحقيق نقطتين مهمتين :

- اعادة استخدامها اكثر من مرة .
- في حالة وجود خطأ يتم تحديد مصدره بسهولة ، وفي حالة التعديل لا يتم التعديل سوى على هذه الطبقة .

وفي هذه الحالة لن يتطرق مبرمج اي جزء من البرنامج إلى كيفية الاتصال بقاعدة البيانات ، حيث ان كل ما لديه هي مجموعة من الدوال للاتصال والحذف والتعديل وكل العمليات التي يريدها مع بارميترات مناسبة دون التدخل في كيفية الاتصال بقواعد البيانات تماماً .

يمكنك عمل هذه الطبقة بعدة طرق ، ابسطها استخدام بعض البرامج الجاهزة التي تقوم بهذه العملية وتقوم بعمل استخراج لهذه ال layer حسب لغة البرمجة التي تحددها ، ايضاً يوفر لك الدوت نت طريقة لهذه العملية ، الطريقة الثانية هي الطريقة اليدوية ...

في العجالة التالية سنتعرف على مثال بسيط لهذه العملية من اجل الموظفين ، يمكن تطبيق نفس المفاهيم على البرامج الجدية لاحقا .

1- عمليات فتح واغلاق قواعد البيانات :
C#:

private SqlConnection cn = new SqlConnection();
public void OpenConnection(string connectionString)
{
cn.ConnectionString = connectionString;
cn.Open();
}
public void CloseConnection()
{
cn.Close();
}

vb.net:

Private cn As New SqlConnection()
Public Sub OpenConnection(ByVal connectionString As String)
cn.ConnectionString = connectionString
cn.Open()
End Sub
Public Sub CloseConnection()
cn.Close()
End Sub

2- سيناريو عملية الحذف .
في هذه العملية سنتيح للمستخدم حذف الموظف باسمه ، او حذف الموظف برقمه ، او حذف الموظف مثلاً بدلالة العمر وبعدة اختيارات سواء اكبر او اقل او يساوي مثلاً مع استخدام مبدأ ال OverLoading ومبدأ عمل Enums ايضاً واللذان تم شرحهما في دروس سابقة .
*** لاحظ ان مهمتك في هذه المرحلة هي عمل كل الدوال التي يمكن استخدامها في البرنامج تحت اي ظرف من الظروف :
C#:

public void DeleteEmployee(int id)
{
string sql = string.Format("Delete from Employee where ID = {0}",
id);
using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
try
{
cmd.ExecuteNonQuery();
}
catch(SqlException ex)
{
Exception error = new Exception("some error occures: ", ex);
throw error;
}
}
}

public void DeleteEmployee(string name)
{
string sql = string.Format("Delete from Employee where [First Name] = '{0}'",
name);
using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
try
{
cmd.ExecuteNonQuery();
}
catch(SqlException ex)
{
Exception error = new Exception("some error occures: ", ex);
throw error;
}
}
}

enum deletecondition
{
morethan=0,
lessthan=1,
equal=2
}
public void DeleteEmployee(int age,deletecondition delcondition)
{
string sql="";
if(delcondition == deletecondition.morethan)
sql = string.Format("Delete from Employee where age > {0}",age);
else if(delcondition == deletecondition.lessthan)
sql = string.Format("Delete from Employee where age < {0}",age);
else
sql = string.Format("Delete from Employee where age = {0}",age);

using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
try
{
cmd.ExecuteNonQuery();
}
catch(SqlException ex)
{
Exception error = new Exception("some error occures: ", ex);
throw error;
}
}
}

vb.net:

Public Sub DeleteEmployee(ByVal id As Integer)
Dim sql As String = String.Format("Delete from Employee where ID = '{0}'", id)
Using cmd As New SqlCommand(sql, Me.sqlCn)
Try
cmd.ExecuteNonQuery()
Catch ex As SqlException
Dim [error] As New Exception("some error occures: ", ex)
Throw [error]
End Try
End Using
End Sub
Public Sub DeleteEmployee(ByVal name As String)
Dim sql As String = String.Format("Delete from Employee where [First Name] = '{0}'", name)
Using cmd As New SqlCommand(sql, Me.sqlCn)
Try
cmd.ExecuteNonQuery()
Catch ex As SqlException
Dim [error] As New Exception("some error occures: ", ex)
Throw [error]
End Try
End Using
End Sub
enum deletecondition
{
morethan=0,
lessthan=1,
equal=2
}

Public Sub DeleteEmployee(ByVal age As Integer, ByVal delcondition As deletecondition)
Dim sql As String = ""
If delcondition = deletecondition.morethan Then
sql = String.Format("Delete from Employee where age > {0}", age)
ElseIf delcondition = deletecondition.lessthan Then
sql = String.Format("Delete from Employee where age < {0}", age)
Else
sql = String.Format("Delete from Employee where age = {0}", age)
End If


Using cmd As New SqlCommand(sql, Me.sqlCn)
Try
cmd.ExecuteNonQuery()
Catch ex As SqlException
Dim [error] As New Exception("some error occures: ", ex)
Throw [error]
End Try
End Using
End Sub


3- سيناريو عمليات الاضافة والتعديل :

بنفس الطريقة يمكن التعديل بعدة خيارات او الاضافة بعدة طرق .
بالنسبة لعملية الاضافة سنجبره على ادخال الاسم الأول والأخير فقط .
والتعديل ينبغي أيضاً أن يكون بنفس الصورة ، ولكن منعاً للاطالة سنعدل بدلالة الرقم الاسم الأول فقط .
C#:

public void InsertEmployee(string fname, string lname, int age)
{
// Format and execute SQL statement.
string sql = string.Format("Insert Into Employee_info" +
"([First Name], [Last Name]) Values" +
"('{0}', '{1}')",fname, lname);
using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
cmd.ExecuteNonQuery();
}
}

public void UpdateEmployee(int id, string newFirstName)
{
string sql =
string.Format("Update Employee Set [First Name] = '{0}' Where ID = '{1}'",
newFirstName, id);
using(SqlCommand cmd = new SqlCommand(sql, this.sqlCn))
{
cmd.ExecuteNonQuery();
}
}

vb.net:

Public Sub InsertEmployee(ByVal fname As String, ByVal lname As String, ByVal age As Integer)
' Format and execute SQL statement.
Dim sql As String = String.Format("Insert Into Employee_info" + "([First Name], [Last Name]) Values" + "('{0}', '{1}')", fname, lname)

Using cmd As New SqlCommand(sql, Me.sqlCn)
cmd.ExecuteNonQuery()
End Using
End Sub

Public Sub UpdateEmployee(ByVal id As Integer, ByVal newFirstName As String)

Dim sql As String = String.Format("Update Employee Set [First Name] = '{0}' Where ID = '{1}'", newFirstName, id)
Using cmd As New SqlCommand(sql, Me.sqlCn)
cmd.ExecuteNonQuery()
End Using
End Sub


4- سيناريو عمليات البحث .
قبل البدء في سيناريو عملية البحث ، نود أن نشير إن الدوال السابقة ينقصها شيء هام وهي عملية ال Parameters لتلافي المشاكل الناتجة عن ال Sql Injection ، لكن كانت الامثلة السابقة للتوضيح فقط ، في عملية البحث الآن سنطبق ما تعلمناه لحل هذه المشكلة .
سنجرب عملية بحث واحدة عن الاسم الأول والأخير للأشخاص برقم ID معين ، لا تنسى أنك مطالب في Data Layer بعمل كل الطلبات التي قد يحتاجها مبرمج عمليات البرنامج لكي لا يحتاج لكتابة حتى جملة استعلام واحدة .

سنقوم اولاً بعمل Stored Procedure :


CREATE PROCEDURE GetFirstNameByID
@id int,
@fName char(10) output
AS
SELECT @fName=[First Name] from Employee_info where ID > @id

ومن ثم نقوم نعرف الدالة الخاصة بعملية البحث بالشكل التالي :
C#:

public string SelectName(int id)
{
using (SqlCommand cmd = new SqlCommand("GetFirstNameByID", cn))
{
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter param = new SqlParameter();
param.ParameterName = "@id";
param.SqlDbType = SqlDbType.Int;
param.Value = ID;
param.Direction = ParameterDirection.Input;
cmd.Parameters.Add(param);
param = new SqlParameter();
param.ParameterName = "@fName";
param.SqlDbType = SqlDbType.Char;
param.Size = 10;
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
return cmd.Parameters["@fName"].Value.ToString();
}
return carPetName;
}


vb.net:

Public Sub InsertEmployee(ByVal fname As String, ByVal lname As String, ByVal age As Integer)
' Format and execute SQL statement.
Dim sql As String = String.Format("Insert Into Employee_info" + "([First Name], [Last Name]) Values" + "('{0}', '{1}')", fname, lname)

Using cmd As New SqlCommand(sql, Me.sqlCn)
cmd.ExecuteNonQuery()
End Using
End Sub

Public Sub UpdateEmployee(ByVal id As Integer, ByVal newFirstName As String)

Dim sql As String = String.Format("Update Employee Set [First Name] = '{0}' Where ID = '{1}'", newFirstName, id)
Using cmd As New SqlCommand(sql, Me.sqlCn)
cmd.ExecuteNonQuery()
End Using
End Sub


--- لاحقاً يمكنك فصل ال DataLayer حتى في dll منفصلة لضمان تشغيلها مع اكثر من تطبيق ، في الاستخدام لاحقاً وعلى افتراض ان الفئة Class لطبقة البيانات DataLayer تحمل الاسم myDataLayer ، ولتطبيق عملية مثل الحذف كل المطلوب منك هو سطرين مثل الآتي :
C#:

myDataLayer example=new myDataLayer();
example.OpenConnection(myconnectionstring);
example.DeleteEmployee(10);
example.DeleteEmployee("Ahmed");


vb.net:

Dim example As New myDataLayer()
example.OpenConnection(myconnectionstring)
example.DeleteEmployee(10)
example.DeleteEmployee("Ahmed")


وفقط !!!

وبنفس النظام لو كانت dll قم باستيرادها في برنامجك ثم قم باستخدامها بنفس الطريقة .
لو لاحظت ، بهذه الطريقة اصبح المبرمج لسيناريو البرنامج ولباقي عملياته بعيداً كل البعد عن قواعد البيانات ، كما ان تقسيم العمل أصبح أوضح وبالتالي اصبح بالامكان تدارك المشاكل بصورة أوضح .

Asynchronous Data Access :

في هذه الجزئية سنحاول تعلم طريقة تمنعنا من عمل عدة عمليات على قواعد البيانات في نفس الوقت لمنع التضارب ، ومع ان المشكلة ستواجهنا بصورة اكبر في الوضع المنفصل Disconnected والذي هو موضوع درسنا القادم ، إلا اننا سنجرب الحل في هذه العجالة السريعة ، جرب المثال التالي :

C#:

SqlConnection cn = new SqlConnection();
cn.ConnectionString =
@"Data Source=(local)\SQLEXPRESS;Integrated Security=SSPI;" +
"Initial Catalog=AutoLot;Asynchronous Processing=true";
cn.Open();


vb.net:

Dim cn As New SqlConnection()
cn.ConnectionString = "Data Source=(local)\SQLEXPRESS;Integrated Security=SSPI;" + "Initial Catalog=AutoLot;Asynchronous Processing=true"
cn.Open()


أول خطوة قمنا بها هي تعريف الوضع Asynchronous Processing=true في ال ConnectioString بهذه الطريقة اصبحنا قادرين على الاستفادة من الدوال التالية :

BeginExecuteReader() و EndExecuteReader()
BeginExecuteNonQuery() و EndExecuteNonQuery()
BeginExecuteXmlReader() وEndExecuteXmlReader()

سنجرب الآن على BeginExecuteReader و EndExecuteReader ، وسنبدأ بتأخير جملة الاستعلام الأولى لخمس ثوان مثلاً :
c#:

string strSQL = "WaitFor Delay '00:00:05';Select * From Employee_info";
SqlCommand myCommand = new SqlCommand(strSQL, cn);

vb.net:

Dim strSQL As String = "WaitFor Delay '00:00:05';Select * From Employee_info"
Dim myCommand As New SqlCommand(strSQL, cn)


هنا سنقوم بتنفيذ عمليات أخرى على ثريد آخر :
c#:

IAsyncResult itfAsynch;
itfAsynch = myCommand.BeginExecuteReader(CommandBehavior.Close Connection);
vb.net:

Dim itfAsynch As IAsyncResult
itfAsynch = myCommand.BeginExecuteReader(CommandBehavior.Close Connection)


وتنفيذ بعض العمليات حتى الانتهاء من تنفيذ الثريد
C#:

while (!itfAsynch.IsCompleted)
{
// تغيير قيمة مؤشر مثلاً .
Thread.Sleep(1000);
}

vb.net:

While Not itfAsynch.IsCompleted
' تغيير قيمة مؤشر مثلاً .
Thread.Sleep(1000)
End While


الآن بما أننا خرجنا من ال Loop السابقة فهذا يعني انتهاء التنفيذ الأول ، الآن سنقوم بتنفيذ العملية التي نريدها .
C#:

SqlDataReader myDataReader = myCommand.EndExecuteReader(itfAsynch);
while (myDataReader.Read())
{
MessageBox.Show(myDataReader[1].ToString());
}
myDataReader.Close();


vb.net:

Dim myDataReader As SqlDataReader = myCommand.EndExecuteReader(itfAsynch)
While myDataReader.Read()
MessageBox.Show(myDataReader(0).ToString())
End While
myDataReader.Close()


فقط !
تبقى نقطة واحدة قبل انهاء وضع ال Connected وهي نقطة ال Transactions ، وهي ما سنتحدث عنه لاحقاً .

Transactions :

هذه هي النقطة الأخيرة التي سنتحدث عنها لهذا اليوم ، تعريف هذه العملية باختصار شديد هو وجود مجموعة من العمليات لا بد أن تتم سوية أو تتوقف سوية ، المثال الأشهر لهذه العملية هي عمليات التحويل البنكية من عميل1 إلى عميل2 .

لذا خطوات العمل في قواعد البيانات لا بد أن تكون بالشكل التالي :
- سحب 500 دولار من حساب عميل 1 .
- ايداع 500 دولار في حساب عميل 2 .

وهاتان العمليتان لابد ان تتم سوية ، بمعنى لو تمت العملية الأولى ولم تتم العملية الثانية لانقطاع الاتصال مثلاً فهذا غير مقبول ، لذا لا بد ان يتم اعتماد العمليتين أو حذف اي عملية منهم تتم دون الأخرى وهو ما يعرف باسم roll back .

إذا قمنا بعمل transaction سيتم تعريف هذه المجموعة من العمليات في نظام قاعدة البيانات DBMS على شكل وحدة واحدة بحيث يتم تنفيذها سوية او عدم تنفيذها سوية .
الفئة المسؤولة عن هذه العملية لدينا هي الفئة SqlTransaction الموجودة ضمن مجال الأسماء System.Data.SqlClient ، هناك فئات أخرى يمكنها تطبيق هذه العملية ضمن مجال أسماء .net ايضاً مثل :

System.EnterpriseServices : تتيح لنا هذه الفئة الاتصال مع مكونات COM+ التي تقدم لنا الدعم في هذه العملية .
System.Transactions : تتيح لنا هذه الفئة بناء تطبيقات تدعم ال transactions .
WCF و WWF : تمكننا هاتان الفئتان من تطبيق مبادئ ال transaction ايضاً .

حتى في نظم قواعد البيانات يمكنك القيام بهذه المهمة عن طريق تعريف Stored Procedure يقوم بعمل transactions عن طريق TRANSACTIONو ROLLBACKو COMMIT ، يمكنك البدء في هذا النوع من هنا :
http://msdn.microsoft.com/en-us/library/ms187844.aspx

في ADO.net لدينا الفئة DBTransaction التي تطبق الواجهة IDbTransaction interface والذي يحتوي على الدوال الرئيسية التالية :
C#:

public interface IDbTransaction : IDisposable
{
IDbConnection Connection { get; }
IsolationLevel IsolationLevel { get; }
void Commit();
void Rollback();
}

vb.net:

Public Interface IDbTransaction
Inherits IDisposable
ReadOnly Property Connection() As IDbConnection
ReadOnly Property IsolationLevel() As IsolationLevel
Sub Commit()
Sub Rollback()
End Interface


يضيف لنا ال SqlTransaction دالة جديدة هي Save والتي تتيح لنا حفظ نقطة يتم الرجوع إليها في حالة الفشل في اتمام العملية بدل الرجوع في العملية بالكامل - لو كان هناك اجزاء من العملية أو العملية على مراحل - .
ابسط مثال على تطبيق هذه العملية ، هو افتراض وجود جدول يحتوي على ( اسم العميل - المبلغ المودع ) وعندما نقوم بعملية تحويل من حساب إلى آخر نقوم بعملية بالشكل التالي :
C#:

SqlCommand cmdGet = new SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn);
cmdSelect.ExecuteNonQuery();
SqlCommand cmdSet = new SqlCommand("update customers set total=total+" + totalmoney.ToString() + " where ID" + SuppID.ToString(), cn);
cmdSelect.ExecuteNonQuery();


vb.net:

Dim cmdGet As New SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn)
cmdSelect.ExecuteNonQuery()

Dim cmdSet As New SqlCommand("update customers set total=total+" + totalmoney.ToString() + " where ID" + SuppID.ToString(), cn)
cmdSelect.ExecuteNonQuery()

في هذه العملية ولأي سبب كان قد يتوقف الجزء الثاني من العملية ، لذا سنحاول اخبار نظام قاعدة البيانات بأننا سننفذ كلا العمليتين في نفس الوقت ، ولو لم يتم تنفيذ واحدة منهما سيتم الغاء الأخرى مباشرة .
C#:

SqlCommand cmdGet = new SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn);
SqlCommand cmdSet = new SqlCommand("update customers set total=total+" + totalmoney.ToString() + " where ID" + SuppID.ToString(), cn);
SqlTransaction sqltr = null;
try
{
sqltr = sqlCn.BeginTransaction();
cmdGet.Transaction = sqltr;
cmdSet.Transaction = sqltr;
cmdGet.ExecuteNonQuery();
cmdSet.ExecuteNonQuery();
if (throwEx)
{
throw new ApplicationException("all operation canceld, some errors occures");
}
sqltr.Commit();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
tx.Rollback();
}
}


vb.net:

Dim cmdGet As New SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn)
Dim cmdSet As New SqlCommand("update customers set total=total+" + totalmoney.ToString() + " where ID" + SuppID.ToString(), cn)
Dim sqltr As SqlTransaction = Nothing
Try
sqltr = sqlCn.BeginTransaction()

cmdGet.Transaction = sqltr
cmdSet.Transaction = sqltr

cmdGet.ExecuteNonQuery()
cmdSet.ExecuteNonQuery()

If throwEx Then
Throw New ApplicationException("all operation canceld, some errors occures")
End If

sqltr.Commit()
Catch ex As Exception
MessageBox.Show(ex.Message)
tx.Rollback()
End Try


كما لاحظت ، نقوم ببدء عملية ال transaction ، وما لم يحدث أي خطأ فلن ندخل في الشرط ولذا سيتم تنفيذ دالة Commit لتنفيذ العمليتين واعتمادهما ، أما في حالة حدوث اي خطأ فسنقوم برمي throw Exception والذي ينقلنا مباشرة إلى Catch لنقوم هناك باستدعاء الدالة rollback من اجل الغاء جميع التأثيرات التي حدثت .

لو كنت تود تجربة هذا المثال لمعرفة كيفية حدوثة ، جرب جعل قيمة throwEx=true وجرب ما يحدث .

لو جربت مثالك الآن ستجد ان قيمة مبلغ العميل 1 مثلاً لم تتأثر ، أما في حالة عدم وجود خطأ في الجملة الثانية فستجد ان الكمية المحددة من المبلغ قد تم نقلها من عميل 1 إلى عميل 2 .
*** طبعاً في تطبيقاتك الجدية لن يكون نظام الايداع والصرف بهذا الشكل ، بل في العادة سيكون هناك جدول او اكثر من جدول يحتوي على العمليات المجراه لكل عميل من ايداع وصرف وسحب وخلافه .

إلى هنا نكون قد انتهينا من دروس هذا اليوم من أجل ADO.net والوضع المتصل ، في الدرس القادم سوف نتعرف سوية بإذن الله على الوضع المنفصل قبل ان يكون درسنا الأخير في عالم قواعد البيانات من أجل مبادئ LINQ .

والله الموفق ...
والسلام عليكم ورحمة الله وبركاته ...