الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - نسخة قابلة للطباعة +- منتدى فيجوال بيسك لكل العرب | منتدى المبرمجين العرب (http://vb4arb.com/vb) +-- قسم : الأقسام التعليمية - المنتدى القديم (http://vb4arb.com/vb/forumdisplay.php?fid=90) +--- قسم : قسم دورات المنتدى (http://vb4arb.com/vb/forumdisplay.php?fid=113) +---- قسم : دورة .net 2008 . لنبدأ سوياً (http://vb4arb.com/vb/forumdisplay.php?fid=138) +---- الموضوع : الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث (/showthread.php?tid=5658) |
الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 كاتب الموضوع : أحمد جمال
بسم الله الرحمن الرحيم .السلام عليكم ورحمة الله وبركاته . في هذا الدرس سنحاول التعرف على ماهية الوضع المتصل 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(); كود : [SIZE=3] cn.ConnectionString = @"Data Source=AHMED-PC\SQLEXPRESS;Initial Catalog=Employee;Integrated Security=True;Pooling=False";[/SIZE] كود : Dim cn As New SqlConnection() كود : [SIZE=3]cn.ConnectionString = "Data Source=AHMED-PC\SQLEXPRESS;Initial Catalog=Employee;Integrated Security=True;Pooling=False" [/SIZE] أ- هناك العديد من المواقع التي تقدم لك خدمة معرفة ال ConnectionString مثل موقع : www.connectionstrings.com . ب- لو كنت تعمل من خلال قاعدة بيانات داخل بيئة عمل .net يمكنك معرفة ال ConnectioString لها باختيارها من قائمة Server Explorer ومن ثم مشاهدة ال Properties بالشكل التالي : ج- باستخدام اي من أدوات .net الجاهزة والتي سنتعرف على بعض منها في مراحل قادمة . 3- كتابة جملة استعلام بسيطة لطلب كل أسماء الموظفين الأولى + الثانية على شكل fullname بالاضافة إلى العمر - لو لم تفهم هذه الجملة راجع دروس TSQL - : C#: كود : [SIZE=3]string strSQL = "Select [First Name]+[Last Name] as [Full Name], Age From Employee_info";[/SIZE] كود : [SIZE=3]Dim strSQL As String = "Select [First Name]+[Last Name] as [Full Name], Age From Employee_info" C#: كود : [SIZE=3]SqlDataReader myDataReader;[/SIZE] كود : [SIZE=3]Dim myDataReader As SqlDataReader [/SIZE] C#: كود : [SIZE=3]string listItem = "";[/SIZE] كود : [SIZE=3]Dim listItem As String = "" [/SIZE] 6- اغلاق وسائل الاتصال وعدم تركها مفتوحة : C#: كود : myDataReader.Close(); كود : [SIZE=3] cn.Close();[/SIZE] كود : myDataReader.Close() كود : [SIZE=3] cn.Close()[/SIZE] الخيار الأول هو فقط ما يهمنا ، في هذه الحالة سيكون شكل الكود بالشكل التالي : C#: كود : [SIZE=3]myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);[/SIZE] كود : [SIZE=3]myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)[/SIZE] C#: كود : [SIZE=3] myDataReader.Close()[/SIZE] vb.net: myDataReader.Close() - طبعاً لا تنس أن بامكانك عمل نفس جملة الاستعلام Update او Delete أو Insert بدلاً من Select ، وحسب جملة الاستعلام المطلوبة . الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 - التعامل مع ConnectionStringBuilder : يوفر لك هذا الكائن طريقة تفصيلية لبناء ال ConnectioString الخاص بك ، هذا المثال يوضح الأكثر استخداماً : C#: كود : [SIZE=3]SqlConnectionStringBuilder cnStrBuilder = new SqlConnectionStringBuilder(); vb.net: كود : [SIZE=3]Dim cnStrBuilder As New SqlConnectionStringBuilder() حيث تجد : - InitialCatalog لتحديد اسم قاعدة البيانات - DataSource لتحديد مسار قاعدة البيانات - ConnectTimeout لتحديد الوقت الذي يمكن استغراقه من اجل محاولة الوصول إلى قاعدة البيانات او ايقاف العملية عند انتهاءه . - Password كلمة مرور قاعدة البيانات إن وجدت . - UserID اسم المستخدم لقاعدة البيانات إن وجد . هناك العديد من العناصر الأخرى أيضاً يمكنك استعراضها من هنا : http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder_m embers.aspx الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 التعامل مع الفئة Command : ال Command هي جملة الاستعلام التي نستخدمها لتنفيذ اي نوع من العمليات على قاعدة البيانات ، تنقسم إلى ثلاث انواع رئيسية : - StoredProcedure - TableDirect - Text في المثال السابق استخدمنا ال Command كجملة استعلام نصية مباشرة Text ، كانت بالشكل التالي مثلاً : C#: كود : string strSQL = "Select * From Employee_Info"; vb.net: كود : [SIZE=3]Dim strSQL As String = "Select * From Employee_Info" أو بهذا الشكل : c#: كود : [SIZE=3]SqlCommand testCommand = new SqlCommand(); vb.net: كود : [SIZE=3]Dim testCommand As New SqlCommand() اثناء التنفيذ قمنا بربطها مباشرة باستخدام ExecuteReader ، في الواقع هناك عدة طرق عدة طرق للتنتفيذ : ExecuteReader : في حالة كون الناتج عدد كبير من البيانات ، يتم تعريف DataReader وربط الناتج به لقراءته ، وهو ما تعرفنا عليه في مراحل سابقة . ExecuteNonQuery : في حالة عدم وجود نواتج اصلاً ، مثل تعريف عملية update او Delete حيث الناتج الوحيد هو تنفيذ العملية من عدمه ، سيتم التعرف عليه لاحقاً . ExecuteScalar : في حالة كون الناتج وحيد ، مثل الاستعلام عن فقط عن الاسم الأول للشخص صاحب الرقم القومي xxxxxx . ExecuteXmlReader : تنفيذ الناتج واعادته على شكل XML يتم تعريف .XmlReader وربط الناتج به لقراءته ، يمكنك الرجوع إلى دروس XML لمعرفة المزيد عن XmlReader . الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 - استخدام ال Parameterized Command Objects : كما لاحظت من الدرس الذي تحدثنا فيه عن ال SQL Injection ، فإن الطريقة التقليدية لجمل الاستعلام تظل خطرة ، لذا نبدا باستخدام Parameters لجمل الاستعلام لدينا ايضاً حتى لو لم نكن نتعامل مع Stored Procedure . لو أخذنا المثال التالي للتجربة : C#: كود : [SIZE=3]string strSQL = "Select [First Name]+[Last Name] as [Full Name], Age From Employee where ID=" + ID; vb.net: كود : [SIZE=3]Dim strSQL As String = "Select [First Name]+[Last Name] as [Full Name], Age From Employee where ID=" + ID ستجد اننا قادرين بمبادئ ال SQL Injection من اختراق هذا النظام بسهولة ، لذا البديل يكون باستخدام وتعريف SqlParameter: C#: كود : [SIZE=3]string sql = string.Format("Select [First Name]+[Last Name] as [Full Name], Age From Employee where [/SIZE][SIZE=3]ID=@ID[/SIZE][SIZE=3]"); vb.net: كود : Dim sql As String = String.Format("Select [First Name]+[Last Name] as [Full Name], Age From Employee where كود : [SIZE=3]ID=@ID[/SIZE][SIZE=3]") هنا كما لاحظت قمنا بتمرير باميترات إلى جملة استعلام قمنا نحن بكتابتها ضمن البرنامج . - الحالة الثانية التي لدينا وهي الشائعة الاستخدام هي حالة تعريف Stored Procedure ، لو افترضنا مثلاً جملة الاستعلام التي أنشأناها في أول درس لنا بالشكل التالي : كود : [SIZE=3]ALTER PROCEDURE dbo.GetAge[/SIZE] وقمنا بحفظها باسم GetAge ، الآن نريد استدعاءها من البرنامج ، يتم ذلك بالشكل التالي مثلاً : C#: كود : [SIZE=3]using (SqlCommand cmd = new SqlCommand("GetAge", cn)) vb.net: كود : Using cmd As New SqlCommand("GetAge", cn) كما لاحظت ، قمنا بتعريف نوع ال Command هنا نظراً لإن الافتراضي هو Text ، ومن ثم قمنا بتعريف متغير الدخول ومتغير الخروج أيضاً . الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 - التعامل مع DataReaders : كما شاهدت في المثال السابق ، يمكن قراءة البيانات من DataReader بالشكل التالي مثلاً : C#: كود : string listItem = ""; vb.net: كود : Dim listItem As String = "" حيث تعود الدالة Read ب true ما دام هناك سجلات للقراءة ، وفي نفس الوقت مع كل استدعاء لها تنتقل إلى السجل التالي ... طريقة القراءة تكون بتحديد الحقل المراد قراءته myDataReader["Age"] او بتحديد رقمه في الترتيب myDataReader[2] مثلاً . الخاصية FieldCount تعطينا عدد النتائج المعادة ، لذا يمكننا تنفيذ نفس العملية السابقة بالشكل التالي : c#: كود : for (int i = 0; i < myDataReader.FieldCount; i++) vb.net: كود : For i As Integer = 0 To myDataReader.FieldCount - 1 NextResult : تمكنك ال DataReader من تعريف جملتي استعلام لاعادة الناتج ، فمثلاً لاعادة اسماء الموظفين ثم اسماء المشاريع : C#: كود : [SIZE=3]string strSQL = "Select * From Employee_info;Select * from projects"; كود : [SIZE=3]Dim strSQL As String = "Select * From Employee_info;Select * from projects" الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 Data Access Layer : في التطبيقات الجدية ، لا يتم وضع الكود مع المظهر مع سيناريو وعمليات البرنامج اضافة لطبقة البيانات، بل يتم فصل كل منها في طبقة منفصلة وهو ما يعرف باسم Layers ، لمعرفة المزيد عن هذا الموضوع يمكنك مراجعة الرابط التالي : http://www.al-asiri.com/ShowRecord.aspx?Action=Open&id=cefa426c-d9e0-4625-a66b-87fd6082ff89 وهناك تطبيق ايضاً هنا : http://vb4arb.com/vb/showthread.php?824 في هذه المرحلة ، سنحاول عمل data layer تكون خاصة بالتعامل مع قواعد البيانات لتحقيق نقطتين مهمتين : - اعادة استخدامها اكثر من مرة . - في حالة وجود خطأ يتم تحديد مصدره بسهولة ، وفي حالة التعديل لا يتم التعديل سوى على هذه الطبقة . وفي هذه الحالة لن يتطرق مبرمج اي جزء من البرنامج إلى كيفية الاتصال بقاعدة البيانات ، حيث ان كل ما لديه هي مجموعة من الدوال للاتصال والحذف والتعديل وكل العمليات التي يريدها مع بارميترات مناسبة دون التدخل في كيفية الاتصال بقواعد البيانات تماماً . يمكنك عمل هذه الطبقة بعدة طرق ، ابسطها استخدام بعض البرامج الجاهزة التي تقوم بهذه العملية وتقوم بعمل استخراج لهذه ال layer حسب لغة البرمجة التي تحددها ، ايضاً يوفر لك الدوت نت طريقة لهذه العملية ، الطريقة الثانية هي الطريقة اليدوية ... في العجالة التالية سنتعرف على مثال بسيط لهذه العملية من اجل الموظفين ، يمكن تطبيق نفس المفاهيم على البرامج الجدية لاحقا . 1- عمليات فتح واغلاق قواعد البيانات : C#: كود : [SIZE=3]private SqlConnection cn = new SqlConnection(); vb.net: كود : Private cn As New SqlConnection() 2- سيناريو عملية الحذف . في هذه العملية سنتيح للمستخدم حذف الموظف باسمه ، او حذف الموظف برقمه ، او حذف الموظف مثلاً بدلالة العمر وبعدة اختيارات سواء اكبر او اقل او يساوي مثلاً مع استخدام مبدأ ال OverLoading ومبدأ عمل Enums ايضاً واللذان تم شرحهما في دروس سابقة . *** لاحظ ان مهمتك في هذه المرحلة هي عمل كل الدوال التي يمكن استخدامها في البرنامج تحت اي ظرف من الظروف : C#: كود : [SIZE=3]public void DeleteEmployee(int id) vb.net: كود : [SIZE=3]Public Sub DeleteEmployee(ByVal id As Integer) 3- سيناريو عمليات الاضافة والتعديل : بنفس الطريقة يمكن التعديل بعدة خيارات او الاضافة بعدة طرق . بالنسبة لعملية الاضافة سنجبره على ادخال الاسم الأول والأخير فقط . والتعديل ينبغي أيضاً أن يكون بنفس الصورة ، ولكن منعاً للاطالة سنعدل بدلالة الرقم الاسم الأول فقط . C#: كود : [SIZE=3]public void InsertEmployee(string fname, string lname, int age) vb.net: كود : [SIZE=3]Public Sub InsertEmployee(ByVal fname As String, ByVal lname As String, ByVal age As Integer) 4- سيناريو عمليات البحث . قبل البدء في سيناريو عملية البحث ، نود أن نشير إن الدوال السابقة ينقصها شيء هام وهي عملية ال Parameters لتلافي المشاكل الناتجة عن ال Sql Injection ، لكن كانت الامثلة السابقة للتوضيح فقط ، في عملية البحث الآن سنطبق ما تعلمناه لحل هذه المشكلة . سنجرب عملية بحث واحدة عن الاسم الأول والأخير للأشخاص برقم ID معين ، لا تنسى أنك مطالب في Data Layer بعمل كل الطلبات التي قد يحتاجها مبرمج عمليات البرنامج لكي لا يحتاج لكتابة حتى جملة استعلام واحدة . سنقوم اولاً بعمل Stored Procedure : كود : [SIZE=3]CREATE PROCEDURE GetFirstNameByID ومن ثم نقوم نعرف الدالة الخاصة بعملية البحث بالشكل التالي : C#: كود : [SIZE=3]public string SelectName(int id) vb.net: كود : myDataLayer example=new myDataLayer(); --- لاحقاً يمكنك فصل ال DataLayer حتى في dll منفصلة لضمان تشغيلها مع اكثر من تطبيق ، في الاستخدام لاحقاً وعلى افتراض ان الفئة Class لطبقة البيانات DataLayer تحمل الاسم myDataLayer ، ولتطبيق عملية مثل الحذف كل المطلوب منك هو سطرين مثل الآتي : C#: كود : [SIZE=3]Dim example As New myDataLayer() vb.net: Dim example As New myDataLayer() example.OpenConnection(myconnectionstring) example.DeleteEmployee(10) example.DeleteEmployee("Ahmed") وفقط !!! وبنفس النظام لو كانت dll قم باستيرادها في برنامجك ثم قم باستخدامها بنفس الطريقة . لو لاحظت ، بهذه الطريقة اصبح المبرمج لسيناريو البرنامج ولباقي عملياته بعيداً كل البعد عن قواعد البيانات ، كما ان تقسيم العمل أصبح أوضح وبالتالي اصبح بالامكان تدارك المشاكل بصورة أوضح . الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 Asynchronous Data Access : في هذه الجزئية سنحاول تعلم طريقة تمنعنا من عمل عدة عمليات على قواعد البيانات في نفس الوقت لمنع التضارب ، ومع ان المشكلة ستواجهنا بصورة اكبر في الوضع المنفصل Disconnected والذي هو موضوع درسنا القادم ، إلا اننا سنجرب الحل في هذه العجالة السريعة ، جرب المثال التالي : C#: كود : [SIZE=3]SqlConnection cn = new SqlConnection(); vb.net: كود : [SIZE=3]Dim cn As New SqlConnection() أول خطوة قمنا بها هي تعريف الوضع Asynchronous Processing=true في ال ConnectioString بهذه الطريقة اصبحنا قادرين على الاستفادة من الدوال التالية : BeginExecuteReader() و EndExecuteReader() BeginExecuteNonQuery() و EndExecuteNonQuery() BeginExecuteXmlReader() وEndExecuteXmlReader() سنجرب الآن على BeginExecuteReader و EndExecuteReader ، وسنبدأ بتأخير جملة الاستعلام الأولى لخمس ثوان مثلاً : c#: كود : [SIZE=3]string strSQL = "WaitFor Delay '00:00:05';Select * From Employee_info"; vb.net: كود : [SIZE=3]Dim strSQL As String = "WaitFor Delay '00:00:05';Select * From Employee_info" هنا سنقوم بتنفيذ عمليات أخرى على ثريد آخر : c#: كود : IAsyncResult itfAsynch; كود : [SIZE=3]Dim itfAsynch As IAsyncResult وتنفيذ بعض العمليات حتى الانتهاء من تنفيذ الثريد C#: كود : [SIZE=3]while (!itfAsynch.IsCompleted) vb.net: كود : While Not itfAsynch.IsCompleted الآن بما أننا خرجنا من ال Loop السابقة فهذا يعني انتهاء التنفيذ الأول ، الآن سنقوم بتنفيذ العملية التي نريدها . C#: كود : [SIZE=3]SqlDataReader myDataReader = myCommand.EndExecuteReader(itfAsynch); vb.net: كود : [SIZE=3]Dim myDataReader As SqlDataReader = myCommand.EndExecuteReader(itfAsynch) فقط ! تبقى نقطة واحدة قبل انهاء وضع ال Connected وهي نقطة ال Transactions ، وهي ما سنتحدث عنه لاحقاً . الدرس الثالث والستون - عالم ADO.net وما يتعلق به - الجزء الثالث - RaggiTech - 14-10-12 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 vb.net: كود : [SIZE=3]Public Interface IDbTransaction يضيف لنا ال SqlTransaction دالة جديدة هي Save والتي تتيح لنا حفظ نقطة يتم الرجوع إليها في حالة الفشل في اتمام العملية بدل الرجوع في العملية بالكامل - لو كان هناك اجزاء من العملية أو العملية على مراحل - . ابسط مثال على تطبيق هذه العملية ، هو افتراض وجود جدول يحتوي على ( اسم العميل - المبلغ المودع ) وعندما نقوم بعملية تحويل من حساب إلى آخر نقوم بعملية بالشكل التالي : C#: كود : [SIZE=3]SqlCommand cmdGet = new SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn); vb.net: كود : [SIZE=3]Dim cmdGet As New SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn) في هذه العملية ولأي سبب كان قد يتوقف الجزء الثاني من العملية ، لذا سنحاول اخبار نظام قاعدة البيانات بأننا سننفذ كلا العمليتين في نفس الوقت ، ولو لم يتم تنفيذ واحدة منهما سيتم الغاء الأخرى مباشرة . C#: كود : [SIZE=3]SqlCommand cmdGet = new SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn); vb.net: كود : [SIZE=3]Dim cmdGet As New SqlCommand("update customers set total=total-" + totalmoney.ToString() + " where ID" + custID.ToString(), cn) كما لاحظت ، نقوم ببدء عملية ال transaction ، وما لم يحدث أي خطأ فلن ندخل في الشرط ولذا سيتم تنفيذ دالة Commit لتنفيذ العمليتين واعتمادهما ، أما في حالة حدوث اي خطأ فسنقوم برمي throw Exception والذي ينقلنا مباشرة إلى Catch لنقوم هناك باستدعاء الدالة rollback من اجل الغاء جميع التأثيرات التي حدثت . لو كنت تود تجربة هذا المثال لمعرفة كيفية حدوثة ، جرب جعل قيمة throwEx=true وجرب ما يحدث . لو جربت مثالك الآن ستجد ان قيمة مبلغ العميل 1 مثلاً لم تتأثر ، أما في حالة عدم وجود خطأ في الجملة الثانية فستجد ان الكمية المحددة من المبلغ قد تم نقلها من عميل 1 إلى عميل 2 . *** طبعاً في تطبيقاتك الجدية لن يكون نظام الايداع والصرف بهذا الشكل ، بل في العادة سيكون هناك جدول او اكثر من جدول يحتوي على العمليات المجراه لكل عميل من ايداع وصرف وسحب وخلافه . إلى هنا نكون قد انتهينا من دروس هذا اليوم من أجل ADO.net والوضع المتصل ، في الدرس القادم سوف نتعرف سوية بإذن الله على الوضع المنفصل قبل ان يكون درسنا الأخير في عالم قواعد البيانات من أجل مبادئ LINQ . والله الموفق ... والسلام عليكم ورحمة الله وبركاته ... |