はじめに
先日、Xamarin.iOS で EntityFrameworkCore が使えることがわかった。
そうなると、データベースのマイグレーションがやりたくなるのは自然な流れ。 今度はマイグレーションを試してみた。
マイグレーションファイルを生成したいが
マイグレーションファイルを手書きするのは人がやることじゃないので、
ツールで生成したいところ。
Microsoft.EntityFrameworkCore.Tools をパッケージ参照すれば、
生成コマンド Add-Migration
が使えるようになる。
ただ、Xamarion.iOS プラットフォームをサポートしていなかったので、Add-Migration
を実行するとエラー発生。
Xamarin.iOS プロジェクトにパッケージを追加できたから、期待してしまったじゃないか。
仕方ないのでマイグレーションファイル生成用に .NET Core プロジェクトを作成
.NET Core コンソールアプリケーションをプロジェクトに追加し、 DbContext はそちらに用意する。
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using System; namespace HelloXamarin.Core { public class Stamp { public string Id { get; set; } = Guid.NewGuid().ToString(); public DateTime StampedAt { get; set; } = DateTime.UtcNow; } public class ApplicationDbContext : DbContext { readonly string databasePath; public ApplicationDbContext(string databasePath) : base() { this.databasePath = databasePath; Stamps = Set<Stamp>(); } public DbSet<Stamp> Stamps { get; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // SQLite を使う optionsBuilder.UseSqlite( connectionString: $"Filename={databasePath}"); base.OnConfiguring(optionsBuilder); } } public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> { public ApplicationDbContext CreateDbContext(string[] args) { //var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>(); //optionsBuilder.UseSqlite("Data Source=test.db"); return new ApplicationDbContext("test.db"); } } }
マイグレーションファイルを作成
.NET Core プロジェクトをスタートアッププロジェクトにして
Add-Migration CreateInitialSchema
を実行。
すると、下記のような内容の 20180406052413_CreateInitialSchema.cs が出力された。
using Microsoft.EntityFrameworkCore.Migrations; using System; using System.Collections.Generic; namespace HelloXamarin.Core.Migrations { public partial class CreateInitialSchema : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Stamps", columns: table => new { Id = table.Column<string>(nullable: false), StampedAt = table.Column<DateTime>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Stamps", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Stamps"); } } }
他にも、20180406052413_CreateInitialSchema.Designer.cs と ApplicationDbContextModelSnapshot.cs も生成されたが、 誌面の都合で省略。
アプリ起動時にマイグレーションを実行するように修正
ApplicationDbContext とマイグレーション用のソースコード一式を、 Xamarin.iOS プロジェクトにリンクとして追加する。
using Foundation; using HelloXamarin.Core; using Microsoft.EntityFrameworkCore; using System; using System.IO; using System.Linq; using System.Threading.Tasks; using UIKit; namespace HelloXamarin { public class Application { static void Main(string[] args) { UIApplication.Main(args, null, "AppDelegate"); } } [Register("AppDelegate")] public class AppDelegate : UIApplicationDelegate { public override UIWindow Window { get; set; } public ApplicationDbContext DbContext { get; private set; } public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { // Xamarin.iOS で SQLite を使えるようにする SQLitePCL.Batteries_V2.Init(); // データベースがなければ作る var dbPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "..", "Library", "helloxamarin.db"); DbContext = new ApplicationDbContext(dbPath); DbContext.Database.Migrate(); Window = new UIWindow(UIScreen.MainScreen.Bounds); Window.RootViewController = new UINavigationController( new MainViewController(DbContext)); Window.MakeKeyAndVisible(); return true; } public override void WillTerminate(UIApplication application) { DbContext?.Dispose(); } } public class MainViewController : UITableViewController { readonly ApplicationDbContext _dbContext; Stamp[] _stamps = new Stamp[0]; UIBarButtonItem _addButton; public MainViewController(ApplicationDbContext dbContext) : base(UITableViewStyle.Plain) { Title = "Hello Xamarin"; _dbContext = dbContext; _addButton = new UIBarButtonItem( UIBarButtonSystemItem.Add, HandleAddButtonClick); } async void HandleAddButtonClick(object sender, EventArgs e) { await AddStampAsync(); await LoadStampsAsync(); TableView.InsertRows( new[] { NSIndexPath.FromItemSection(0, 0) }, UITableViewRowAnimation.Fade); } public override async void ViewDidLoad() { base.ViewDidLoad(); NavigationItem.RightBarButtonItem = _addButton; await LoadStampsAsync(); } public override nint RowsInSection(UITableView tableView, nint section) { return _stamps.Length; } public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = new UITableViewCell(UITableViewCellStyle.Default, "StampCell"); var stamp = _stamps[indexPath.Row]; cell.TextLabel.Text = stamp.StampedAt.ToString(); return cell; } async Task AddStampAsync() { _dbContext.Stamps.Add(new Stamp()); await _dbContext.SaveChangesAsync(); } async Task LoadStampsAsync() { _stamps = await _dbContext.Stamps .OrderByDescending(s => s.StampedAt) .ToArrayAsync(); } } }
アプリを起動するとマイグレーションが実行された!
おわりに
EntityFrameworkCore のマイグレーションが使えるのは良かったが、 現状だとそのためにわざわざ .NET Core などの別プロジェクトを用意しないといけないので面倒。 正直使うかどうか悩ましいな。