في هذه التدوينة سنستخدم Entity Framework Code First Migrations لعمل بعض التغييرات في model classes بحيث تطبق هذه التغييرات على قاعدة البيانات. أي تعديل قاعدة البيانات عن طريق كود سي شارب.
بشكل افتراضي عند استخدام Entity Framework Code First لإنشاء قاعدة البيانات بشكل أوتوماتيكي كما فعلنا سابقا في هذه السلسلة فإن Code First تضيف جدول إلى قاعدة البيانات للتحقق فيما إذا كانت بنية قاعدة البيانات متزامنة مع model classes فإذا لم يكن هناك تزامن فإن Entity Framework تعطي خطأ و هذا يجعل من السهل تعقب المشاكل في وقت التطوير.
هذه المقالة جزء من سلسلة لتعلم أساسيات ASP.NET MVC للمبتدئين:
- المقالة الأولى: أول موقع ويب لك باستخدام ASP.NET MVC
- المقالة الثانية: إضافة متحكمات Controllers لموقع ASP.NET MVC
- المقالة الثالثة: إضافة واجهة View لموقع ASP.NET MVC
- المقالة الرابعة: إضافة Model لموقع ASP.NET MVC
- المقالة الخامسة: إضافة Connection String والعمل مع قواعد البيانات LocalDB
- المقالة السادسة: الوصول إلى البيانات عن طريق المتحكم Controller
- المقالة السابعة: شرح وتجريب تابع التعديل Edit Method
- المقالة الثامنة: إضافة ميزة البحث إلى موقع ASP.NET MVC
- المقالة التاسعة: إضافة حقل جديد لقاعدة البيانات انطلاقاً من كود سي شارب Code First Method
- المقالة العاشرة: إضافة التحقق Validation إلى MVC Model وتطبيق مبدأ DRY
- المقالة الحادية عشر: اختبار وشرح توابع Details و Delete
إعداد Code First Migrations للنموذج
في Solution Explorer قم بالنقر بزر الفأرة اليميني على الملف Books.mdf وقم باختيار Delete لحذف قاعدة البيانات
في حال عدم ظهور الملف Books.mdf قم بالنقر على أيقونة Show All Files كما هو موضح
قم بعمل Build للتطبيق للتأكد من عدم وجود أخطاء
من القائمة Tools قم بالنقر على NuGet Package Manager ثم قم باخيتار Package Manager Console
في نافذة Package Manager Console عند المؤشر <PM قم بإدخال التعليمة التالية
Enable-Migrations -ContextTypeName ArabicArchive.Models.BookDBContext
إن تعليمة Enable-Migrations تضيف ملف Configuration.cs في المجلد الجديد Migrations
يقوم Visual Studio بفتح الملف Configuration.cs
قم باستبدال الكود في التابع Seed في ملف Configuration.cs بالكود التالي
protected override void Seed(ArabicArchive.Models.BookDBContext context)
{
context.Types.AddOrUpdate(t => t.Title,
new Models.Type
{
Title = "Programming",
Description = "Books in computer programming field"
},
new Models.Type
{
Title = "Mathematics",
Description = "Books in math field"
}
);
context.SaveChanges();
context.Books.AddOrUpdate(b => b.Title,
new Book
{
Title = "Clean Code",
Description = "A Handbook of Agile Software",
PublishDate = DateTime.Parse("2009-01-01"),
Author = "Robert C. Martin",
GoodreadsRate=4.5,
NumberOfPages=413,
TypeId=context.Types.Single(t => t.Title=="Programming").Id
},
new Book
{
Title = "HTML and CSS",
Description = "Design and Build Websites",
PublishDate = DateTime.Parse("2011-01-01"),
Author = "Jon Duckett",
GoodreadsRate = 4.5,
NumberOfPages = 493,
TypeId = context.Types.Single(t => t.Title == "Programming").Id
},
new Book
{
Title = "Calculus",
Description = "Early Transcendentals",
PublishDate = DateTime.Parse("2016-01-01"),
Author = "James Stewart",
GoodreadsRate = 4,
NumberOfPages = 1180,
TypeId = context.Types.Single(t => t.Title == "Mathematics").Id
},
new Book
{
Title = "Clean Code 2",
Description = "A Handbook of Agile Software",
PublishDate = DateTime.Parse("2010-01-01"),
Author = "Robert C. Martin",
GoodreadsRate = 4,
NumberOfPages = 500,
TypeId = context.Types.Single(t => t.Title == "Programming").Id
}
);
}
ملاحظة:بعد إضافة أنواع الكتب نقوم باستدعاء التابع ()context.SaveChanges ليتم إضافة أنواع الكتب أولاً إلى قاعدة البيانات بعد ذلك نضيف الكتب إلى القاعدة
قم بالنقر بزر الفأرة اليميني على الخط الأحمر تحت كلمة Book ثم اختر using ArabicArchive.Models
بعذ ذلك يتم إضافة عبارة using التالية:
using Models;
ملاحظة:
إن Code First Migrations تستدعي التابع Seed بعد كل Migration وهذا التابع يحدث الأسطر إذا كانت موجودة مسبقاً أو يضيف أسطر جديدة في حال لم تكن موجودة.
إن التابع AddOrUpdate في الكود التالي ينفذ عملية upsert
context.Books.AddOrUpdate(b => b.Title, new Book { Title = "Clean Code", Description = "A Handbook of Agile Software", PublishDate = DateTime.Parse("2009-01-01"), Author = "Robert C. Martin", GoodreadsRate=4.5, NumberOfPages=413, TypeId=context.Types.Single(t => t.Title=="Programming").Id }
ولأن تابع Seed ينفذ مع كل عملية migration فإننا لانستطيع إضافة البيانات فقط لأن الأسطر التي نحاول إضافتها موجودة مسبقاً حيث يتم إضافتها عند أول عملية migration والتي تؤدي إلى إنشاء قاعدة البيانات.
إن عملية upsert تمنع الأخطاء التي يمكن أن تحدث عند محاولة إضافة سطر موجود مسبقاً حيث أنها تقوم بإجراء التعديلات على البيانات التي قمنا بها أثناء اختبار التطبيق.
في بعض الحالات عند تعديل البيانات أثناء الاختبار نريد أن تبقى هذه التعديلات بعد تحديث قاعدة البيانات وبالتالي نحتاج لتنفيذ تعليمة إضافة شرطية حيث أنها تقوم بإضافة سطر إذا لم يكن موجود مسبقاً. إن أول بارامتر ممرر للتابع AddOrUpdate يحدد الخاصية التي نريد استخدامها لفحص فيما إذا كان السطر موجود.
من أجل بيانات الاختبار للكتاب قمنا بتحديد الخاصية Title حيث أن كل عنوان في اللائحة غير مكرر:
context.Books.AddOrUpdate(b => b.Title,
في هذا الكود نفترض أن العناوين للكتب هي غير مكررة فإذا قمنا بشكل يدوي بإضافة عنوان مكرر فإننا سنحصل على exception التالي في المرة التالية التي نقوم بها بعمل migration
Sequence contains more than one element
قم بعمل Build للمشروع بالضغط على CTRL-SHIFT-B
الخطوة التالية هي إضافة DbMigration class من أجل initial migration
إن هذه migration تضيف قاعدة بيانات جديدة ولهذا السبب قمنا بحذف الملف Books.mdf في الخطوة السابقة
في نافذة Package Manager Console قم بإدخال الأمر add-migration Initial من أجل إضافة initial migration
إن الاسم Initial هو اسم ملف ال migration الذي سيتم إضافته عند تنفيذ الأمر السابق ويمكن استخدام أي اسم نريده في التسمية:
بعد تنفيذ الأمر السابق بنجاح فإن Code First Migrations تضيف ملف class في مجلد Migrations بحيث يكون اسم الملف {DateStamp_Initial.cs} وهذا الclass يحتوي على الكود اللازم لتوليد بنية قاعدة البيانات.
إن اسم ملف migration مسبوق بختم زمني للمساعدة في عملية الترتيب لعمليات migrations التي قمنا بها
قم بفتح الملف DateStamp_Initial.cs حيث يحتوي على التعليمات اللازمة لإضافة الجدول Books والجدول Types إلى قاعدة البيانات. عند تحديث قاعدة البيانات من خلال التعليمات التالية فإن الملف {DateStamp_Initial.cs} سينفذ ويضيف بنية قاعدة البيانات بعد ذلك سينفذ تابع Seed لإضافة بيانات الاختبار للقاعدة.
في Package Manager Console قم بإدخال الأمر update-database لإضافة قاعدة البيانات وتنفيذ تابع Seed:
في حال الحصول على خطأ بأن الجدول موجود مسبقاً ولايمكن إعادة إضافته فيكون هذا الخطأ بسبب تشغيل التطبيق بعد حذف قاعدة البيانات وقبل تنفيذ الأمر update-database.
في هذه الحالة قم بحذف الملف Books.mdf وقم بتنفيذ الأمر update-database وفي حال استمرار الحصول على نفس الخطأ قم بحذف المجلد migrations مع محتوياته وقم بإعادة تنفيذ الخطوات التي تم ذكرها في بداية هذه المقالة.
قم بتشغيل التطبيق وانتقل إلى الرابط Books/ فنلاحظ أن seed data تم إضافتها إلى قاعدة البيانات:
إضافة الخاصية Rating إلى Book Model
سنبدأ أولاً بإضافة الخاصية الجديدة Rating إلى Book class ، قم بفتح الملف Models\Book.cs وقم بإضافة الخاصية Rating كما هو موضح
public string Rating { set; get; }
الكود التالي هو الكود الكامل ل Book class
public class Book
{
public int Id { set; get; }
public string Title { set; get; }
public string Description { set; get; }
[Display(Name = "Publish Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime PublishDate { set; get; }
public string Author { set; get; }
public int NumberOfPages { set; get; }
public double GoodreadsRate { set; get; }
public string Rating { set; get; }
public int TypeId { set; get; }
public virtual Type Type { set; get; }
}
قم بعمل Build للتطبيق بالضغط على Ctrl+Shift+B
بما أننا قمنا بإضافة خاصية جديدة إلى Book class فإننا نحتاج أيضاً إلى تحديث الخاصية bind لتابع Create و تابع Edit في ملف BooksController.cs لإضافة الخاصية Rating
[Bind(Include = "Id,Title,Description,PublishDate,Author,NumberOfPages,GoodreadsRate,TypeId,Rating")]
أيضاً يجب أن نقوم بتحديث view templates لعرض و إضافة وتحديث الخاصية Rating
قم بفتح الملف Views\Books\Index.cshtml\ وقم بإضافة العمود <th>Rating</th> بعد العمود GoodreadsRate
ثم قم بإضافة العمود <td> في نهاية template لعرض قيمة الخاصية Rating للعنصر (item.Rating@)
الكود التالي هو للملف Index.cshtml
@model IEnumerable<ArabicArchive.Models.Book> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("Create New", "Create") @using (Html.BeginForm("Index","Books",FormMethod.Get)) { <p> Type:@Html.DropDownList("BookTypeId", "All") Title: @Html.TextBox("SearchString") <br /> <input type="submit" value="Filter" /> </p> } </p> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Type.Title) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Description) </th> <th> @Html.DisplayNameFor(model => model.PublishDate) </th> <th> @Html.DisplayNameFor(model => model.Author) </th> <th> @Html.DisplayNameFor(model => model.NumberOfPages) </th> <th> @Html.DisplayNameFor(model => model.GoodreadsRate) </th> <th> @Html.DisplayNameFor(model => model.Rating) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Type.Title) </td> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Description) </td> <td> @Html.DisplayFor(modelItem => item.PublishDate) </td> <td> @Html.DisplayFor(modelItem => item.Author) </td> <td> @Html.DisplayFor(modelItem => item.NumberOfPages) </td> <td> @Html.DisplayFor(modelItem => item.GoodreadsRate) </td> <td> @Html.DisplayFor(modelItem => item.Rating) </td> <td> @Html.ActionLink("Edit", "Edit", new { id=item.Id }) | @Html.ActionLink("Details", "Details", new { id=item.Id }) | @Html.ActionLink("Delete", "Delete", new { id=item.Id }) </td> </tr> } </table>
قم بفتح الملف Views\Movies\Create.cshtml\ وقم بإضافة الحقل Rating كما هو موضح بالكود الملون التالي
حيث يقوم هذا الكود بإضافة text box لإدخال ال Rating عند إضافة عند إضافة كتاب جديد
@Html.LabelFor(model => model.GoodreadsRate, htmlAttributes: new { @class = “control-label col-md-2” })
</div>
@Html.LabelFor(model => model.Rating, htmlAttributes: new { @class = “control-label col-md-2” })
</div>
@Html.LabelFor(model => model.TypeId, “Type”, htmlAttributes: new { @class = “control-label col-md-2” })
</div>
</div> </div> }
@section Scripts { @Scripts.Render(“~/bundles/jqueryval”) }
بهذه الإضافات التي قمنا بها تم تحيدث الكود بحيث يدعم الخاصية Rating الجديدة
قم بتشغيل التطبيق وانتقل إلى الرابط Books/ وعندها سنرى الخطأ التالي
ويكون نص الخطأ
The model backing the ‘BookDBContext’ context has changed since the database was created. Consider using Code First Migrations to update the database(https://go.microsoft.com/fwlink/?LinkId=238269).
سبب ظهور هذا الخطأ لأننا قمنا بتحديث Book model class في التطبيق وهو الآن مختلف عن بنية الجدول Books في قاعدة البيانات حيث أنه لايوجد عمود Rating في جدول قاعدة البيانات.
هناك عدة طرق لحل هذا الخطأ:
الطريقة الأولى: جعل Entity Framework وبشكل أتوماتيكي تحذف وتعيد إنشاء قاعدة البيانات بالاعتماد على البنية الجديدة ل model class ، هذه الطريقة مريحة جداً في المراحل الأولى من دورة التطوير عندما نقوم بالتطوير النشط على قاعدة بيانات الاختبار، فهي تسمح لنا بسرعة تطوير model وبنية قاعدة البيانات معاً
السيئة لهذه الطريقة هي أننا نفقد المعطيات الموجودة في قاعدة البيانات حيث أننا لانستخدم هذه الطريقة على قاعدة البيانات التي تحتوي بيانات التطبيق الفعلية.
يتم في هذه الطريقة استخدام initializer ليتم إدخال بيانات الاختبار إلى قاعدة البيانات بشكل أوتوماتيكي للمزيد من المعلومات قم بالدخول للرابط التالي ASP.NET MVC/Entity Framework tutorial
الطريقة الثانية: تعديل بنية قاعدة البيانات وبشكل صريح بحيث تطابق model classes
فائدة هذه الطريقة هي المحافظة على البيانات الموجودة مسبقاً في قاعدة البيانات
نستطيع القيام بالتعديل إما بشكل يدوي أو عن طريق إنشاء database change script
الطريقة الثالثة: هي باستخدام Code First Migrations لتحديث بنية قاعدة البيانات
في هذا الدرس سنستخدم Code First Migrations
قم بتحديث Seed method بحيث يتم إضافة قيمة في العمود الجديد
قم بفتح الملف Migrations\Configuration.cs وقم بإضافة الحقل Rating لكل book object
new Book
{
Title = "Clean Code",
Description = "A Handbook of Agile Software",
PublishDate = DateTime.Parse("2009-01-01"),
Author = "Robert C. Martin",
GoodreadsRate = 4.5,
NumberOfPages = 413,
Rating="G",
TypeId = context.Types.Single(t => t.Title == "Programming").Id
},
قم بعمل Build للتطبيق وقم بفتح نافذة Package Manager Console
و قم بإدخال الأمر التالي add-migration Rating
إن الأمر add-migration يخبر migration framework لاكتشاف book model الحالي ومقارنته مع بنية الجدول book في قاعدة البيانات وإضافة الكود اللازم لتحديث قاعدة البيانات بحيث تطابق ال model الجديد
إن الأسم Rating هو اسم ملف ال migration الذي سيتم إضافته عند تنفيذ الأمر السابق ويمكن استخدام أي اسم نريده في التسمية ويفضل استخدام اسم ذو معنى يدل على migration التي قمنا بها
عند انتهاء تنفيذ الأمر السابق يقوم Visual Studio بفتح ملف class وهو عبارة عن class يرث من DbMIgration class ونلاحظ في تابع Up الكود لإضافة عامود جديد
public partial class Rating : DbMigration { public override void Up() { AddColumn("dbo.Books", "Rating", c => c.String()); } public override void Down() { DropColumn("dbo.Books", "Rating"); } }
قم بعمل Build للتطبيق وقم بإدخال الأمر update-database في نافذة Package Manager Console
الصورة التالية تعرض خرج نافذة Package Manager Console
قم بتشغيل التطبيق وانتقل إلى الرابط Books/ فنلاحظ ظهور الحقل Rating
قم بالنقر على الرابط Create New لإضافة كتاب جديد مع ملاحظة أننا نستطيع إضافة Rating للكتاب الجديد
قم بالنقر على الزر Create لإضافة الكتاب فنلاحظ ظهور الكتاب الجديد مع الخاصية Rating في لائحة الكتب
إن المشروع الآن يستخدم migrations وبالتالي لن نقوم بحذف قاعدة البيانات وإعادة إنشاؤها عندما نريد إضافة حقل جديد
في الدرس القادم سوف نقوم بعمل تعديلات على بنية قاعدة البيانات واستخدام migrations لتعديل قاعدة البيانات
يجب أيضاً أن نقوم بإضافة الخاصية Rating إلى Edit view و Details view و Delete view
يمكن إدخال الأمر update-database في نافذة Package Manager Console وبالتالي لن يتم تنفيذ أي كود migration بسبب أن بنية قاعدة البيانات تطابق model classes
إن تنفيذ الأمر update-database سينفذ التابع Seed مرة ثانية
في هذا الدرس قمنا بتعديل model objects و مزامنة قاعدة البيانات مع model
في الدرس القادم سوف نضيف validation logic إلى model classes وتفعيل بعض قواعد العمل الإجبارية.
تغذية راجعة
اتمنى عدم التردد في الاستفسار عن أي مفهوم تم ذكره في هذا التدوينة، وأرجو تجربة ماورد في التدوينة بشكل فعلي وعدم الاكتفاء بالقراءة لتحقيق أكبر فائدة ممكنة، وكالعادة سأكون سعيد إن شاركتموني تجربتكم وتصويباتكم في حال وجود أخطاء كتابية.
هذه المقالة مستندة إلى سلسلة دروس مايكروسوفت الرسمية للـ ASP.NET MVC، وذلك لترتيب الدروس المناسب واعتقادي بسلاستها وأهمية نقلها إلى العربية بأسلوب مناسب وتجربة تتوافق مع الأدوات المتاحة لنا والمتوفرة في منطقتنا.