ASP.NET Core と Entity Framework Core を学習しました。
エンティティから DB を生成する方法と初期データの投入についてまとめました。
簡略化のため、必要な using 名前空間;namespace {...} はコードから省略しています。

エンティティとデータベースコンテキストの作成

Models/Book.cs
public class Book
{
    public int ID { get; set; }
    [Required]
    [StringLength(100)]
    public string Title { get; set; }
    [StringLength(100)]
    public string Author { get; set; }
    [Range(1900, 2100)]
    public int PublishYear { get; set; }
}
  • 大雑把なイメージ
    • class:DB のレコード
    • property:DB のカラム
  • プロパティの上の [...]
  • ID
    • Entity Framework が自動的に主キーと認識する。
    • 連番で採番される。
    • 外部キーなど自分で採番したいときは以下のアノテーションをつけます。
      • [DatabaseGenerated(DatabaseGeneratedOption.None)]:自動連番をOFF
      • [Key]:キーとして認識
Data/BooksContext.cs
public class BooksContext : DbContext
{
    public BooksContext(DbContextOptions<BooksContext> options)
        : base(options)
    { }

    public DbSet<Book> Books { get; set; }
}
  • Entity Framework ではこのクラスを経由して LINQ to Entities で DB にアクセスする。
  • DbContext
    • コンテキストクラスは Microsoft.EntityFrameworkCore.DbContext から派生する。
  • DbSet<TEntity>
    • エンティティのコレクションを表す。テーブルにあたる。
    • コレクションだからか名前は複数形にするのが一般的なよう。

Startup.csConfigureServices を設定

Startup.cs
public void ConfigureServices(IServiceCollection services) => services
    .AddDbContext<BooksContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString(nameof(BooksContext))))
    .AddMvc();
  • fluent interfaces に興味があったのでメソッドチェーン記述。
    • サービス登録するメソッドは this を返す。(違うのもあるのでしょうか?)
  • services.AddDbContext<TContext>(...)
    • ASP.NET Core の DI コンテナーに型引数: BooksContext を登録する。
    • これで BooksContext を使うクラスのコンストラクタに BooksContext のインスタンスが渡される。
  • options.UseSqlServer
    • DB には SQL Server を使用する。
    • Configuration.GetConnectionString
      • appsettings.json から引数名の接続文字列を取得。

appsettings.json に接続文字列を設定する

appsettings.json
{
  ...略
  "ConnectionStrings": {
    "BooksContext": "Server=(localdb)\\mssqllocaldb;Database=BooksContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}
  • Server=(localdb)\\mssqllocaldb;
    • SQL Server LocalDB を使用する。
    • 名前の通りローカル開発環境で動作する簡易 DB。
  • Database=<schema_name>
    • DB (スキーマ)名。今回はコンテキストクラスと同じ BooksContext-1
  • Trusted_Connection=True;
    • 信頼された接続。ユーザー名属性とパスワード属性を接続文字列から省ける。
  • MultipleActiveResultSets=true;
    • 1度の DB 接続で複数のクエリを投げることを許可する。

マイグレーションファイルと DB の生成

パッケージマネージャーコンソール
    # マイグレーションファイルを作成
PM> Add-Migration Initial
    # DB更新または生成
PM> Update-Database
  • Add-Migration [引数]
    • Migrations フォルダとマイグレーションファイルが作られる。
      • 20180407053355_Initial.cs
        • 20180407053355_Initial.Designer.cs
      • BooksContextModelSnapshot.cs
    • image.png
  • Update-Database
    • マイグレーションファイルの Up メソッドの内容で DB を更新(作成)する。
    • image.png

DB 初期データ投入用のクラス作成とその呼び出し

Data/DbInitializer.cs
public class DbInitializer
{
    public static async Task SeedingAsync(BooksContext context)
    {
        await context.Database.EnsureCreatedAsync();
        if (await context.Books.AnyAsync())
            return;
        await context.Books.AddRangeAsync(
            new Book { Title = "アンドロイドは電気羊の夢を見るか? ", Author = "フィリップ・K・ディック", PublishYear = 1968 },
            new Book { Title = "1984年", Author = "ジョージ・オーウェル", PublishYear = 1949 },
            new Book { Title = "幼年期の終り", Author = "アーサー・C・クラーク", PublishYear = 1953 },
            new Book { Title = "アルジャーノンに花束を", Author = "ダニエル・キイス", PublishYear = 1959 },
            new Book { Title = "月は無慈悲な夜の女王", Author = "ロバート・A・ハイライン", PublishYear = 1966 },
            new Book { Title = "われはロボット", Author = "アイザック・アシモフ", PublishYear = 1950 },
            new Book { Title = "虐殺器官", Author = "伊藤計劃", PublishYear = 2007 });
        await context.SaveChangesAsync();
    }
}
  • Database.EnsureCreatedAsync
    • データベースにテーブルがない場合は作成する。
    • ※ただし、マイグレーションファイルは使用されない
  • Books.AnyAsync
    • すでにデータが存在する場合には return し処理しない。
  • Books.AddRangeAsync
    • 複数同時にデータを追加できる。何度も Add するよりこっちのほうが簡潔。
    • まだクエリが作られているだけ。
  • SaveChangesAsync
    • 遅延実行だからここで SQL が投げられて commit される。
Program.cs
public static async Task Main(string[] args)
{
    var host = BuildWebHost(args);
    using (var scope = host.Services.CreateScope())
    {
        var provider = scope.ServiceProvider;
        try
        {
            var context = provider.GetRequiredService<BooksContext>();
            await DbInitializer.SeedingAsync(context);
        }
        catch (Exception ex)
        {
            var logger = provider.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "データベース初期化中にエラーが発生しました。");
        }
    }
    await host.RunAsync();
}
  • SeedingAsync を非同期にしたため、Main も非同期にする。
  • GetRequiredService<TServiceType>
    • エントリーポイントではコンストラクターによる依存性の注入ができないのでこれで BooksContext のインスタンスを取得する。

初期データを入れる

  • Program.cs が走ればいいので、F5 するだけで登録される。
  • DbInitializer.SeedingAsync で既にデータがある場合はデータをインサートしないようにしたので、データがある場合にはデータを全部消すか、先に Object explorer からスキーマを削除して Update-Database する。
  • image.png

新しいフィールドの追加や制約の変更

  • エンティティクラスを変更や変更して Add-Migration <migration name> > Update-Database するだけでOK
    • 基本的にフィールドの追加や削除だけでなく既存フィールドの制約等もこれで変更できる。
  • 必要に応じてサンプルデータも変更する。
public class Book
{
    public int ID { get; set; }
    [Required]
    [StringLength(150)]
    public string Title { get; set; }
    [StringLength(150)]
    public string Author { get; set; }
    [Range(1900, 2100)]
    public int PublishYear { get; set; }
    [Required]
    [StringLength(100)]
    public string OriginalTitle { get; set; }
}
  • OriginalTitle を追加
public class DbInitializer
{
    public static async Task SeedingAsync(BooksContext context)
    {
        await context.Database.EnsureCreatedAsync();
        if (await context.Books.AnyAsync())
            return;
        await context.Books.AddRangeAsync(
            new Book { Title = "アンドロイドは電気羊の夢を見るか? ", Author = "フィリップ・K・ディック", PublishYear = 1968, OriginalTitle = "Do Androids Dream of Electric Sheep?" },
            new Book { Title = "1984年", Author = "ジョージ・オーウェル", PublishYear = 1949, OriginalTitle = "Nineteen Eighty-Four" },
            new Book { Title = "幼年期の終り", Author = "アーサー・C・クラーク", PublishYear = 1953, OriginalTitle= "Childhood's End" },
            new Book { Title = "アルジャーノンに花束を", Author = "ダニエル・キイス", PublishYear = 1959, OriginalTitle= "Flowers for Algernon" },
            new Book { Title = "月は無慈悲な夜の女王", Author = "ロバート・A・ハイライン", PublishYear = 1966, OriginalTitle= "The Moon Is a Harsh Mistress" },
            new Book { Title = "われはロボット", Author = "アイザック・アシモフ", PublishYear = 1950, OriginalTitle= "I, Robot " },
            new Book { Title = "虐殺器官", Author = "伊藤計劃", PublishYear = 2007,OriginalTitle= "虐殺器官" });
        await context.SaveChangesAsync();
    }
}
  • モデルの変更に合わせてデータも変更。
パッケージマネージャーコンソール
PM> Add-Migration OriginalTitle
PM> Update-Database
  • Add-Migration
    • エンティティ、コンテキストと DB のスキーマを比べて移行に必要なマイグレーションコードを新しく生成する。

DB のデータを削除してから実行すると登録されます。
image.png

読んでいただきありがとうございました。
LocalDB での開発は、他人を気にせずデータ入れ替えられるのが良いと思いました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.