EF Core یک تکنولوژی جدید برای دسترسی به پایگاه داده می باشد. در اصل این تکنولوژی طراحی شده است تا توسعه دهندگان قادر باشند Domain Class های خود را طراحی نموده و بعد با آن ها به عنوان جداول رابطه ای کار کنند. EF Core یک ORM یا object-relational mapper است که امکان کار با بانک های اطلاعاتی مختلف را از طریق اشیاء NET. میسر می کند. توسط EF Core قسمت عمده کدهای مستقیم کار با بانک اطلاعاتی حذف شده و تبدیل به کدهای NET. می شوند. کار با اشیاء NET. و LINQ، مزایایی نظیر تحت نظر قرار گرفتن کدها توسط کامپایلر و برخورداری از ابزارهای Refactoring پیشرفته را میسر می کند.
EF Core از دو روش پشتیبانی می کند؛
- Code-First
- Database-First
EF Core عمدتا رویکرد Code-First را دنبال می کند. در رویکرد Code-First، EF Core API پایگاه داده و جداول را با استفاده از migration بر اساس قراردادها و پیکربندی های موجود در Domain class های شما ایجاد می کند.
چگونه EF Core را نصب کنیم؟
ابتدا باید Nuget Package های زیر نصب شوند. چون پکیج SqlServer به سه پکیج دیگر dependency دارد با نصب آن به صورت اتوماتیک پکیج های دیگر نیز نصب می شوند. در غیر این صورت باید آنها را به صورت دستی نصب کرد. این Nuget Package ها عبارت اند از:
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Relational
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Tools
چگونه EF Core را Configue کنیم؟
اولین قدم ایجاد کلاسی است که از کلاس بسیار مهم DBContext استفاده می کند. در واقع از آن ارث بری می کند. در کد زیر نام این کلاس را AppDbContext گذاشته ایم.
1 2 3 4 5 6 7 8 |
//AppDbContext Class public class AppDbContext:DbContext { public AppDbContext(DbContextOptions options):base(options) { //some code } } |
بعد از آن برای هر مدلی که بخواهیم از آن در EF استفاده کنیم باید یک property به شکل زیر، در این کلاس بنویسیم:
1 2 |
//property in the AppDbContext Class public DbSet Students { get; set; } |
برای اعمال تنظیمات در EF Core باید در کلاس Startup و در متد ConfigureServices کد زیر را بنویسیم:
1 2 3 |
//Add this service to the ConfigureServices method services.AddDbContextPool(options => options.UseSqlServer(Configuration.GetConnectionString("StudentDbConnection"))); |
اگر دقت کنید متوجه می شوید که ConnectionString ای به نام StudentDBConnection وجود ندارد. به سراغ فایل appsettings.json می رویم و کد زیر را وارد می کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "StudentDBConnection": "server=(localdb)\\MSSQLLocalDb;database=StudentDB;Trusted_Connection=true" } } |
پیاده سازی Repository Pattern
بسیار نیاز است که در پروژه ها از این الگوی طراحی استفاده شود. الگوی طراحی Repository به عنوان یک واسط بین اپلیکیشن و پایگاه داده عمل می کند. (در بعضی مواقع بین اپلیکیشن و یک ORM) با این کار Business Logic از Data Access جدا می شود. مهمترین مزیت Repository Pattern این است که می توان در هر لحظه پایگاه داده پروژه را تغییر داد. چطور؟
در اینترفیس IRepository متدهای مربوط به عملیات اصلی مانند Create, Read, Update, Delete نوشته می شود. بعد از آن باید کلاسی بنویسیم تا این متد ها را پیاده سازی کند. می توانیم یک یا چند کلاس بنویسیم و هر کدام از آنها، حین پیاده سازی این اینترفیس از پایگاه های داده مختلف یا ORM های مختلف استفاده کنند.
پس به راحتی می توان با یک سوییچ ساده، پایگاه داده ی پروژه را مثلا از SQL Server به Oracle تغییر داد بدون آنکه کدهای پروژه دستخوش تغییر شوند.
اینترفیس IRepository به شکل زیر است.
1 2 3 4 5 6 7 8 |
public interface IStudentRepository { StudentModel GetStudent(int id); StudentModel Add(StudentModel student); IEnumerable GetAllStudents(); StudentModel Update(StudentModel studentChanges); StudentModel Delete(int id); } |
حال باید کلاسی به نام مثلا SqlStudentRepository به قسمت Model اضافه کنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class SqlStudentRepository : IStudentRepository { private readonly AppDbContext context; public SqlStudentRepository(AppDbContext context) { this.context = context; } public StudentModel Add(StudentModel student) { context.Students.Add(student); context.SaveChanges(); return student; } public StudentModel Delete(int id) { var student = context.Students.Find(id); if (student != null) { context.Remove(student); context.SaveChanges(); } return student; } public IEnumerable GetAllStudents() { return context.Students; } public StudentModel GetStudent(int id) { return context.Students.Find(id); } public StudentModel Update(StudentModel studentChanges) { var student = context.Students.Attach(studentChanges); student.State = Microsoft.EntityFrameworkCore.EntityState.Modified; context.SaveChanges(); return studentChanges; } } |
اگر پروژه را اجرا کنیم به خطا بر می خوریم چون دیتابیسی به نام StudentDB وجود ندارد. برای ایجاد دیتابیس باید ابتدا Migrate کرد. Migration چیست؟
Migration
Migration راهی برای هماهنگ کردن پایگاه داده با مدل های Entity Framework Core است. مثلا هنگامی که در EF Core یک دیتابیس یا یک مدل می نویسید هیچ تغییری در دیتابیس اعمال نمی شود. با اجرای عملیات Migration دیتابیسی که در EF Core نوشته اید ایجاد می شود و به ازای هر مدلی که در DbContext نوشته شود هم یک Table ساخته می شود. اگر اینها از قبل وجود داشته باشند فقط بروزرسانی می شوند. یک قابلیت بسیار مهم Migration وجود History است. بعد از هر بار Migrate کردن یک سری اطلاعات در History ایجاد می شوند و با استفاده از آنها می توان دیتابیس را به وضعیت Migrate های قبلی برگرداند.
برای آشنایی با دستورات Migration باید در Package Manager Console کد زیر را وارد کنیم تا لیستی از دستورات کاربردی Migration نشان داده شوند.
PM> Get-Help about_entityframeworkcore
کد زیر یک Migration را ایجاد می کند. با این کار، تمام تغییرات EF Core در History ثبت می شود ولی هنوز در دیتابیس اعمال نمی شود.
PM> Add-Migration
با کد زیر، تعییرات موجود در Migration در دیتابیس اعمال می شود.
PM> Update-Database
اگر برنامه را اجرا کنیم بدون خطا اجرا می شود ولی هنوز دیتایی در دیتابیس وجود ندارد. می توان در اپلیکیشن، فرم هایی ساخت تا کاربر به ورود اطلاعات بپردازد. یکی از راه های افزودن دیتای اولیه، استفاده از Seed Data می باشد.
Seed Data
با استفاده از Seed Data می توان در دیتابیس، داده های اولیه افزود. با این ویژگی به محض ایجاد دیتابیس، یکی سری اطلاعات حیاتی هم در دیتابیس وارد می شوند.
1 2 3 4 5 6 7 8 9 10 11 |
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().HasData( new StudentModel { Id = 1, FirstName = "Reza", LastName = "Mahmudi" } ); } |
اگر دیتاها زیاد باشند استفاده از روش بالا توصیه نمی شود. به جای آن می توان یک کلاس به نام ModelBuilderExtensions در پوشه ی Model نوشت.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static class ModelBuilderExtensions { public static void Seed(this ModelBuilder modelBuilder) { modelBuilder.Entity().HasData( new StudentModel { Id = 1, FirstName = "Reza", LastName = "Mahmudi" } ); } } |
و در کلاس AppDbContext
1 2 3 4 |
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Seed(); } |
سینک کردن Database با Domain Data Model
اگر بعدا تغییراتی در مدل اعمال کنیم(مثلا یک property اضافه کنیم) و عملیات Add-Migration را انجام دهیم و نگاهی به کلاس Migration بیاندازیم، چیزی شبیه به کلاس زیر می بینیم
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public partial class firstMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Students", columns: table => new { Id = table.Column(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), FirstName = table.Column(nullable: true), LastName = table.Column(nullable: true) }, constraints: table => { table.PrimaryKey("PK_Students", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Students"); } } |
متد Up تغییراتی که در مدل ایجاد کرده ایم را اعمال می کند. متد Down تغییرات را Undo می کند. با هر بار Update فایل زیر به روز می شود که وظیفه اش نگهداری Snapshot ای از وضعیت فعلی Model است.
با دستور زیر می توان آخرین Migration را حذف کرد و وضعیت را به حالت قبل از Migration حذف شده تغییر داد
PM> Remove-Migration