C# - C Sharp: Tạo Migration
Khi phát triển các ứng dụng, model có thể thay đổi thường xuyên khi các yêu cầu mới được làm sáng tỏ. Cơ sở dữ liệu cần được giữ đồng bộ với model. Tính năng migration của Entity Framework Core (EF Core) cho phép bạn thực hiện các thay đổi đối với model của mình và sau đó truyền các thay đổi đó sang lược đồ cơ sở dữ liệu của bạn. Migration cũng có thể được sử dụng để tạo cơ sở dữ liệu nếu nó không tồn tại.
Định cấu hình model
Migration của EF Core dựa trên một tập hợp các quy ước. Các quy ước chi phối cách các đối tượng DbSet
được ánh xạ tới các bảng trong cơ sở dữ liệu, cách các thuộc tính được ánh xạ tới các cột, cách các kiểu dữ liệu .NET được ánh xạ tới các kiểu phù hợp do nhà cung cấp cơ sở dữ liệu đưa ra cũng như cách các khóa và chỉ mục được tạo và quản lý. Đôi khi các quy ước không đáp ứng nhu cầu của bạn hoặc EF Core không thể xác định ý định của bạn. Trong những trường hợp này, bạn có thể sử dụng cấu hình để cho EF Core biết bạn muốn gì.
Cấu hình có thể được áp dụng theo hai cách: thông qua việc trang trí các lớp và property bằng các thuộc tính hoặc bằng cách sử dụng Fluent API. Các attribute chỉ cung cấp một tập hợp con các tùy chọn cấu hình. Do đó, đối với bất kỳ mô hình phức tạp hợp lý nào, bạn có thể cần phải dựa vào API thông thạo để định cấu hình. Do đó, sẽ hợp lý khi sử dụng API thông thạo cho tất cả cấu hình, do đó giữ cho mã cấu hình của bạn nhất quán và do đó dễ suy luận hơn và nó chỉ nằm ở một nơi.
Vậy bạn nên đặt mã cấu hình API thông thạo của mình ở đâu?
Bạn có hai lựa chọn: bạn có thể đặt nó trực tiếp trong phương thức OnModelCreating
của lớp DbContext
; hoặc bạn có thể đặt mã cấu hình trên các lớp riêng biệt cho mỗi thực thể. Ví dụ này sẽ minh họa cách tiếp cận thứ hai, vì đó là cách được đề xuất để quản lý điều này.
Thêm một thư mục mới vào thư mục Data và đặt tên là Configurations. Sau đó, thêm tệp lớp C# mới có tên ProductConfiguration.cs vào thư mục Configurations. Thay thế nội dung bằng nội dung sau:
using Bakery.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Bakery.Data { public class ProductConfiguration : IEntityTypeConfiguration<Product> { public void Configure(EntityTypeBuilder<Product> builder) { builder.Property(p => p.ImageName).HasColumnName("ImageFileName"); } } }
Lớp này thực thi interface IEntityTypeConfiguration<TEntity>
, trong đó có một phương thức là Configure
. Configurations được xác định trong phương thức này. Trong trường hợp này, property ImageName
được ánh xạ tới cột có tên "ImageFileName". Hành vi mặc định là ánh xạ tới các cột có tên giống như property.
Lớp cấu hình được đăng ký theo phương thức OnModelCreating
của BakeryContext
như được hiển thị trong phần được tô sáng dưới đây:
public class BakeryContext : DbContext { public DbSet<Product> Products { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite(@"Data source=Bakery.db"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new ProductConfiguration()); } }
Tạo dữ liệu hạt giống (Seed Data)
Mẫu Bakery ban đầu bao gồm một cơ sở dữ liệu được điền sẵn dữ liệu sản phẩm. Khi bạn tạo cơ sở dữ liệu lần đầu tiên, cơ sở dữ liệu sẽ trống - trừ khi bạn "gieo" dữ liệu vào cơ sở dữ liệu tại thời điểm thực hiện quá trình di chuyển. Trong phần này, bạn sẽ sử dụng API do EF Core cung cấp (kể từ phiên bản 2.1) để đạt được điều này.
Tạo một file lớp C# mới trong thư mục Data và đặt tên là ModelBuilderExtensions.cs. Thay thế nội dung bằng đoạn mã sau:
using Bakery.Models; using Microsoft.EntityFrameworkCore; namespace Bakery.Data { public static class ModelBuilderExtensions { public static ModelBuilder Seed(this ModelBuilder modelBuilder){ modelBuilder.Entity<Product>().HasData( new Product { Id = 1, Name = "Carrot Cake", Description = "A scrumptious mini-carrot cake encrusted with sliced almonds", Price = 8.99m, ImageName = "carrot_cake.jpg" }, new Product { Id = 2, Name = "Lemon Tart", Description = "A delicious lemon tart with fresh meringue cooked to perfection", Price = 9.99m, ImageName = "lemon_tart.jpg" }, new Product { Id = 3, Name = "Cupcakes", Description = "Delectable vanilla and chocolate cupcakes", Price = 5.99m, ImageName = "cupcakes.jpg" }, new Product { Id = 4, Name = "Bread", Description = "Fresh baked French-style bread", Price = 1.49m, ImageName = "bread.jpg" }, new Product { Id = 5, Name = "Pear Tart", Description = "A glazed pear tart topped with sliced almonds and a dash of cinnamon", Price = 5.99m, ImageName = "pear_tart.jpg" }, new Product { Id = 6, Name = "Chocolate Cake", Description = "Rich chocolate frosting cover this chocolate lover's dream", Price = 8.99m, ImageName = "chocolate_cake.jpg" } ); return modelBuilder; } } }
Phương thức Seed
là một phương thức mở rộng trên kiểu ModelBuilder
, được truyền vào phương thức OnModelCreating
mà bạn đã làm việc trước đó. Phần thân của phương thức sử dụng phương thức HasData
được giới thiệu trong EF Core 2.1 để định cấu hình thực thể được chỉ định để có dữ liệu gốc. Các giá trị được cung cấp cho mỗi thực thể phản ánh các giá trị từ mẫu ban đầu và bao gồm các giá trị chính. SQLite chấp nhận các giá trị trong một cột autoincrement
nếu chúng được cung cấp như một phần của câu lệnh INSERT
. Mặt khác, SQL Server sẽ bật IDENTITY_INSERT trên bảng đích và sau đó tắt lại sau khi dữ liệu gốc đã được thêm vào.
Bản thân phương thức hạt giống (seed) được gọi trong phương thức OnModelCreating
. Nó trả về một thể hiện của ModelBuilderType
, vì vậy nó có thể được kết nối với các lệnh gọi khác cũng trả về kiểu ModelBuilder
. ApplyConfiguration
phù hợp với yêu cầu này, vì vậy bạn có thể xâu chuỗi phương thức Seed
đó:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new ProductConfiguration()).Seed(); }
Tạo migration
Bây giờ cấu hình mô hình đã hoàn tất và việc cung cấp dữ liệu hạt giống đã được thực hiện, bạn có thể tạo quá trình di chuyển thực tế. Nhưng trước khi thực hiện, bạn cần cài đặt các công cụ hỗ trợ code migration. Thực hiện lệnh sau từ terminal:
dotnet add package Microsoft.EntityFrameworkCore.Design
Tiếp theo là lệnh này để tạo migration
dotnet ef migrations add CreateDatabase --output-dir Data/Migrations
Lệnh trên tạo ra một migration được gọi là CreateDatabase. Các file dành cho quá trình migration được tạo trong một thư mục mới được tạo có tên là Migrations trong thư mục Data (như được chỉ định bởi giá trị được truyền đến switch output-dir
):
Tệp đầu tiên chứa mã C# được dịch thành câu lệnh SQL để được thực thi, trong khi tệp snapshot chứa biểu diễn C# của mô hình hiện tại. Điều này được sử dụng bởi các lần di chuyển tiếp theo để tính toán các thay đổi cần thiết để cập nhật lược đồ của cơ sở dữ liệu.
Thực hiện migration
Ta đã định cấu hình và tạo migration, giờ đến lúc thực hiện nó. Tại terminal, gõ như sau và nhấn enter để thực hiện lệnh:
dotnet ef database update
Khi bạn nhận được xác nhận rằng migration đã được áp dụng, bạn sẽ thấy tệp Bakery.db đã được tạo trong thư mục gốc của trang web:
Bạn có thể mở nó bằng một tiện ích phù hợp. Ở đây tôi sử dụng Trình duyệt DB cho SQLite. Bạn có thể khám phá các bảng được tạo. Bảng __ EFMigrationsHistory chứa thông tin chi tiết của mỗi lần migration. Bảng sản phẩm chứa dữ liệu mà bạn đã tạo cho nó:
Kết luận
Bạn đã học cách định cấu hình mô hình trước khi migration, tạo dữ liệu gốc, sau đó tạo và thực hiện migration. Bây giờ bạn đã có một cơ sở dữ liệu chứa dữ liệu, bạn có thể bắt đầu làm việc với nó.