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 のカラム
-
- プロパティの上の
[...]
- モデルの検証や DB の制約を C# のアノテーションとして記述できる。
- 詳しくは:ASP.NET Core MVC でのモデル検証の概要 - Microsoft Docs
-
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.cs
の ConfigureServices
を設定
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
のインスタンスが渡される。
- ASP.NET Core の DI コンテナーに型引数:
-
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
- DB (スキーマ)名。今回はコンテキストクラスと同じ
-
Trusted_Connection=True;
- 信頼された接続。ユーザー名属性とパスワード属性を接続文字列から省ける。
-
MultipleActiveResultSets=true;
- 1度の DB 接続で複数のクエリを投げることを許可する。
マイグレーションファイルと DB の生成
パッケージマネージャーコンソール
# マイグレーションファイルを作成
PM> Add-Migration Initial
# DB更新または生成
PM> Update-Database
-
Add-Migration [引数]
-
Update-Database
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
も非同期にする。- 参考:メイン関数(エントリーポイント)を非同期にする
- C# 7.1以降を使用しない場合、
SeedingAsync
を同期メソッドにするかGetAwaiter().GetResult()
する必要がある。
-
GetRequiredService<TServiceType>
- エントリーポイントではコンストラクターによる依存性の注入ができないのでこれで
BooksContext
のインスタンスを取得する。
- エントリーポイントではコンストラクターによる依存性の注入ができないのでこれで
初期データを入れる
- Program.cs が走ればいいので、F5 するだけで登録される。
- ※
DbInitializer.SeedingAsync
で既にデータがある場合はデータをインサートしないようにしたので、データがある場合にはデータを全部消すか、先に Object explorer からスキーマを削除してUpdate-Database
する。
新しいフィールドの追加や制約の変更
- エンティティクラスを変更や変更して
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 のスキーマを比べて移行に必要なマイグレーションコードを新しく生成する。
読んでいただきありがとうございました。
LocalDB での開発は、他人を気にせずデータ入れ替えられるのが良いと思いました。