46
54

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【C#】コードファーストで DB に 初期データ入れる

Last updated at Posted at 2018-04-08
1 / 2

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 での開発は、他人を気にせずデータ入れ替えられるのが良いと思いました。

46
54
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
54

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?