ASP.NET Core で Entity Framework Core を使用してデータベースにアクセスする

ページ作成日 :

環境

Visual Studio
  • Visual Studio 2019
ASP.NET Core
  • 3.1 (MVC, Razor ページ)
SQL Server
  • 2019 Express

はじめに

本 Tips では ASP.NET Core の手法に従って Entity Framework Core を使用するための手順を説明するものとなっています。

データベースの作成、Entity Framework Core の使い方などは本 Tips の主眼ではないため、細かい説明はしません。 これらは ASP.NET Core 以外でも同じように使えます。

今回は SQL Server を別サーバーにインストールして、SQL Server 認証で接続しています。 ローカルインストール時などに使用する Windows 認証と違うところは接続文字列のみで、プログラムに違いはありません。

データベース・テーブルの作成

本 Tips では SQL Server を使用します。ローカル環境や任意のサーバーに SQL Server をインストールしておいてください。

テスト用のデータベースがない場合はデータベースを作成します。

以下のような SQL で作成する場合は SQL Server のバージョンパスやデータベース名を環境に合わせて変更してください。

USE [master]
GO

CREATE DATABASE [TestDatabase]
  CONTAINMENT = NONE
  ON  PRIMARY 
( NAME = N'TestDatabase', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQL\DATA\TestDatabase.mdf' , SIZE = 8192KB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB )
  LOG ON 
( NAME = N'TestDatabase_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQL\DATA\TestDatabase_log.ldf' , SIZE = 8192KB , MAXSIZE = 2048GB , FILEGROWTH = 65536KB )
  WITH CATALOG_COLLATION = DATABASE_DEFAULT
GO

テスト用のテーブルを作成します。更新や削除の為にプライマリキーを設定します。

SQL で作成する場合は以下のようにします。

USE [TestDatabase]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[User](
  [ID] [int] NOT NULL,
  [Name] [nvarchar](20) NOT NULL,
  [Password] [nvarchar](20) NOT NULL,
  [Age] [int] NULL,
  [Email] [nvarchar](200) NULL,
  [Birthday] [date] NULL,
  [UpdateDateTime] [datetime2](7) NULL,
  CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
  [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

表示用にレコードを追加します。

SQL で追加する場合は以下のようにします。

USE [TestDatabase]
GO
INSERT [dbo].[User] ([ID], [Name], [Password], [Age], [Email], [Birthday], [UpdateDateTime]) VALUES (1, N'氏名1', N'aaaa', 20, N'aaaa@example.com', CAST(N'2020-04-01' AS Date), CAST(N'2021-03-14T00:00:00.0000000' AS DateTime2))
GO
INSERT [dbo].[User] ([ID], [Name], [Password], [Age], [Email], [Birthday], [UpdateDateTime]) VALUES (2, N'氏名2', N'bbbb', 30, N'bbbb@example.com', CAST(N'2010-04-01' AS Date), CAST(N'2021-03-14T00:00:00.0000000' AS DateTime2))
GO

ASP.NET Core で Entity Framework を使えるようにする

この項目は MVC, Razor ページどちらでも共通です。

Entity Framework Core のパッケージインストール

プロジェクトを作成したら最初に Entity Framework Core を使用できるように NuGet からパッケージを取得します。 ここでは MVC を例に説明していますが、Razor ページでも操作は同じです。

プロジェクトから 依存関係 を右クリックして NuGet パッケージの管理 を選択します。

タブから 参照 を選択している状態で検索欄に EntityFrameworkCore と入力します。 すると一覧に Entity Framework Core 関連のパッケージが表示されます。

この中から今回は SQL Server を使用するので以下のパッケージをインストールします。

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.EntityFrameworkCore.SqlServer

ここでは Microsoft.EntityFrameworkCore を例にインストールしますので、他の2つも同様にインストールしてください。

インストール対象を選択して インストール ボタンをクリックします。バージョンは最新の安定板を選択します。

ダイアログは基本的に OK をクリックして問題ありません。

他の2つもインストールしてください。

パッケージは以下のようになっていると思います。

データベースのテーブル構成からモデル (プログラム) を作成する

Entity Framework Core でデータベースからデータを取得したり更新したりする場合は、 通常テーブル構成をもとにしたモデルとなるプログラムを作成する必要があります。 このモデルは別名エンティティと呼ばれることもあります。

モデルを作成する場合、モデル (プログラム) からデータベースのテーブルを作成する手法である コードファースト や、 テーブルからモデル(プログラム)を作成する手法である データベースファースト がありますが、 ここでは簡単に行える データベースファースト でテーブルから自動的にプログラムを作る方法について説明します。

ちなみにこれらの手順は ASP.NET Core とは関係ありませんので、コンソールやデスクトップアプリでも同様の手順で作成可能です。

まずは一度プロジェクトをビルドしてエラーがないことを確認します。エラーがあるとモデルを作成することができません。 すでにエラーがないことを確認済みであるのであればビルトする必要はありません。

Visual Studio から パッケージ マネージャー コンソール を開きます。 ない場合はメニューの「ツール」「NuGet パッケージ マネージャー」「パッケージ マネージャー コンソール」から開けます。

以下のようなウィンドウが表示されるので、右上の「既定のプロジェクト」がモデルを作成する対象のプロジェクトになっていることを確認します。 (複数プロジェクトを持っている場合に気を付ける必要があります)

入力欄に以下のテキストを入力します。環境によってパラメータが変わりますので、それぞれ以下の説明ともとに適時変えてください。(特に ******** となっている箇所)

Scaffold-DbContext -Provider Microsoft.EntityFrameworkCore.SqlServer -Connection "Data Source=********\SQLEXPRESS;Database=TestDatabase;user id=********;password=**********" -f -OutputDir "Models\Database" -Context "TestDatabaseDbContext" -UseDatabaseNames -DataAnnotations

各名称の頭に -Provider のようにハイフンがつくものがパラメータ名でその後ろがそのパラメータの値になります。

パラメータ 説明 パラメータ例
Provider SQL Server なら Microsoft.EntityFrameworkCore.SqlServer で固定です。 Microsoft.EntityFrameworkCore.SqlServer
Connection データベースに接続するための接続文字列です。接続文字列は他のアプリでも共通で使用できるので指定する内容については接続文字列の記法にしたがって記述してください。Windows 認証、SQL Server 認証どちらでも使用可能です。ちなみにモデルを作成するためだけに一時的に使用されるだけですので、この接続文字列についてはアプリ公開後のセキュリティなどを意識する必要はありません。パスワードに記号を入れている場合はエスケープに注意してください。 "Data Source=ServerName\SQLEXPRESS;Database=TestDatabase;user id=UserName;password=**********"
f すでにプログラムがあった場合でも強制的に上書きします。 <なし>
OutputDir コードを出力するフォルダパス。プロジェクトフォルダからの相対パス Models\Database
Context Entity Framework を使用する際のコンテキストクラス名 TestDatabaseDbContext
UseDatabaseNames 指定するとデータベースのテーブル名がそのままクラス名になります。指定しない場合は規則に従ってエンティティクラス名の大文字小文字、複数形が調整されます。 <なし>
DataAnnotations 指定すると列の型によって各プロパティに DataAnnotation 属性が自動で付加されます。データベースの型に沿って自動的に入力チェックを行いたい場合に少し便利です。 <なし>

Enter を押して実行すると以下のようになり、コードが自動生成されます。

User テーブルのモデルコードは以下のようになっています。

[Table("User")]
public partial class User
{
  [Key]
  public int ID { get; set; }
  [Required]
  [StringLength(20)]
  public string Name { get; set; }
  [Required]
  [StringLength(20)]
  public string Password { get; set; }
  public int? Age { get; set; }
  [StringLength(200)]
  public string Email { get; set; }
  [Column(TypeName = "date")]
  public DateTime? Birthday { get; set; }
  public DateTime? UpdateDateTime { get; set; }
}

ちなみに警告が表示されるのは、生成されたコンテキストクラスのコードに接続文字列がそのまま記載されるためです。 このコンテキストクラスの OnConfiguring メソッドはコードは生成された後に必ず削除してください。

接続文字列の登録

ここは ASP.NET Core 固有の処理となります。(カスタマイズすれば他の .NET Core アプリでも使用できますが ASP.NET Core の場合は既定で処理できます)

パッケージ マネージャー コンソールで指定した接続文字列はあくまでもモデルを生成するためだけに使用していたものでした。

Web アプリとしてデータベースに接続するための接続文字列は appsettings.json に記載します。 もしビルドや発行ごとに接続するデータベース先を変えたい場合は appsettings.Development.json などで分岐させることもできます。

また、こちらの接続文字列は運用として使用しますので、接続ユーザーなどはセキュリティなどを考慮して専用ユーザーにするなど対策を行いましょう。

appsettings.json に設定する場合は、ルートセクションの中に ConnectionStrings セクションを作成します (正確にはセクションではなくオブジェクト)。 その中にキーと値のセットを作成します。キーの名前はなんでもいいですが、後に指定する際に使用します。値には接続文字列を指定します。

{
  // 

  "ConnectionStrings": {
    "TestDatabaseDbContext": "Data Source=ServerName\\SQLEXPRESS;Database=TestDatabase;user id=UserName;password=********"
  }
}

プログラムで Entity Framework Core を使用できるようにする

ここは ASP.NET Core 固有の処理となります。(カスタマイズすれば他の .NET Core アプリでも使用できますが ASP.NET Core の場合は既定で処理できます)

ASP.NET Core ではコンソールやデスクトップアプリと異なり、各インスタンスごとに生存期間が設定されています。 例えば「Web サービスが起動されてから停止されるまで」「リクエストを受けてからレスポンスを返すまで」などがあります。 ASP.NET Core に置ける Entity Framework Core では既定で ServiceLifetime.Scoped が指定されますので、 特別な理由がない場合はこのままでよいでしょう。

プログラムでデータベースコンテキストにアクセスできるようにするには以下のように Startup.ConfigureServices に追加します。

// 追加 (自前のクラスなので名前空間は適時合わせてください)
using DatabaseEntityFrameworkCoreMvc.Models.Database;

// 追加
using Microsoft.EntityFrameworkCore;

public class Startup
{
  // 省略

  // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、コンテナーにサービスを追加します。
  public void ConfigureServices(IServiceCollection services)
  {
    // 省略

    // 追加
    services.AddDbContext<TestDatabaseDbContext>(
      options => options.UseSqlServer(Configuration.GetConnectionString("TestDatabaseDbContext")));
  }

  // 省略

services.AddDbContext<TestDatabaseDbContext> メソッドの型引数は作成したコンテキストクラスを指定します。

options.UseSqlServer メソッドは SQL Server を使用することを示します。

Configuration.GetConnectionString("TestDatabaseDbContext") では appsettings.json で登録したキー名を指定します。

ASP.NET Core MVC での使用例

ASP.NET Core MVC で Entity Framework Core を使用するにはいくつか方法があります。

コントローラーに DI (依存関係の挿入) で追加する

Startup.cs の services.AddDbContext メソッドで TestDatabaseDbContext を登録することによって コントローラーに挿入することができます。

DI の例としては HomeController のコンストラクタには引数として ILogger<HomeController> logger がありますが、 これは DI によって追加されているものです。同じように TestDatabaseDbContext も挿入することができます。

既存の HomeController に追加する場合は以下のようになります。もちろん他のコントローラーにも追加可能です。

using DatabaseEntityFrameworkCoreMvc.Models.Database;  // 追加

// 省略

public class HomeController : Controller
{
  private readonly ILogger<HomeController> _logger;
  
  // 追加
  private readonly TestDatabaseDbContext _dbContext;
  
  public HomeController(ILogger<HomeController> logger, TestDatabaseDbContext dbContext)  // 追加
  {
    _logger = logger;
    _dbContext = dbContext;  // 追加
  }
  
  // 省略
}

後は受け取ったコンテキストのインスタンスを各アクションで使用することによってデータベースにアクセスできます。

使用例

以下のコードはユーザーの一覧表示と新規登録を処理するコードです。 この先はいずれも ASP.NET Core, Entity Framework Core 各々のコードになりますので細かい説明は省きます。

Entity Framework Core で生成されたモデルはそのままアクションのモデルとしても使用できますし、 別途モデルを作成し Entity Framework Core のモデルをラップして使用することもできます。

また、ユーザーの要求が終わるとコンテキストは自動的に破棄されます。

HomeController.cs

public class HomeController : Controller
{
  private readonly TestDatabaseDbContext _dbContext;

  // 省略

  // ここから追加

  /// <summary>
  /// ユーザー一覧表示アクション。
  /// </summary>
  public IActionResult List()
  {
    // データベースから User 一覧を取得する
    var users = _dbContext.Users.ToList();
    
    // ビューに渡す
    return View(users);
  }
  
  /// <summary>
  /// ユーザー一新規登録画面。
  /// </summary>
  public IActionResult Create()
  {
    return View();
  }
  
  /// <summary>
  /// ユーザー新規登録処理。
  /// </summary>
  [HttpPost]
  public IActionResult Create(User user)
  {
    // エラーがある場合は登録画面に戻る
    if (ModelState.IsValid == false) View(user);
    
    // 更新日時設定
    user.UpdateDateTime = DateTime.Now;

    // 登録リストにユーザー追加
    _dbContext.Users.Add(user);

    // 登録を確定する
    _dbContext.SaveChanges();
    
    // 一覧画面に遷移
    return RedirectToAction(nameof(List));
  }

  // ここまで追加

  // 省略
}

画面を手動で作るのが面倒なのでスキャフォールディングで一覧画面を自動生成します。

自動生成された Views/Home/List.cshtml は以下のようになります。

@model IEnumerable<DatabaseEntityFrameworkCoreMvc.Models.Database.User>

@{
  ViewData["Title"] = "List";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>List</h1>

<p>
  <a asp-action="Create">Create New</a>
</p>
<table class="table">
  <thead>
    <tr>
      <th>
        @Html.DisplayNameFor(model => model.ID)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Name)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Password)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Age)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Email)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Birthday)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.UpdateDateTime)
      </th>
      <th></th>
    </tr>
  </thead>
  <tbody>
@foreach (var item in Model) {
    <tr>
      <td>
        @Html.DisplayFor(modelItem => item.ID)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Name)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Password)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Age)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Email)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Birthday)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.UpdateDateTime)
      </td>
      <td>
        @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
        @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
        @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
      </td>
    </tr>
}
  </tbody>
</table>

Index.cshtml に一覧への画面遷移のリンクを追加します。

<!-- 省略 -->

<ul>
  <li><a asp-action="List">List</a></li>
</ul>

ユーザー登録画面も自動生成します。

自動生成された Views/Home/Create.cshtml は以下のようになります。 そのままだと使いにくいので少し修正しています。

@model DatabaseEntityFrameworkCoreMvc.Models.Database.User

@{
  ViewData["Title"] = "Create";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Create</h1>

<h4>User</h4>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <div class="form-group">
        <label asp-for="ID" class="control-label"></label>
        <input asp-for="ID" class="form-control" />
        <span asp-validation-for="ID" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Password" class="control-label"></label>
        <input asp-for="Password" class="form-control" type="password" /> <!-- type="password" 追加 -->
        <span asp-validation-for="Password" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Age" class="control-label"></label>
        <input asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Email" class="control-label"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Birthday" class="control-label"></label>
        <input asp-for="Birthday" class="form-control" type="date" /> <!-- type="date" 追加 -->
        <span asp-validation-for="Birthday" class="text-danger"></span>
      </div>
      @* 更新日時は入力しないのでコメントアウト *@
      @*
      <div class="form-group">
        <label asp-for="UpdateDateTime" class="control-label"></label>
        <input asp-for="UpdateDateTime" class="form-control "/>
        <span asp-validation-for="UpdateDateTime" class="text-danger"></span>
      </div>
      *@
      <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
      </div>
    </form>
  </div>
</div>

<div>
    <a asp-action="List">Back to List</a>  <!-- List に遷移するように変更 -->
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

実行して確認します。 ちなみにパスワードの登録などはセキュリティ的な対策は何もしていないので、運用する際は ASP.NET Core やデータベースへの登録の手法に従ってください。

もちろんデータベースにも反映されています。

本 Tips では編集や削除処理は作成していませんので、一覧の右にある「Edit | Details | Delete」をクリックしてもエラーになります。 Create と同じ手法で作成できますので試してみてください。一応サンプルコードには上記3つの処理も追加しています。

RequestServices からデータベースコンテキストを取得する

コントローラーのコンストラクタに DI で追加する場合、必要なコントローラー全てに追記しなければいけないためやや面倒です。 別の方法としては RequestServices から取得する方法もあります。

サンプルコードには含めていませんが、以下のように取得することが可能です。

/// <summary>
/// ユーザー一覧表示アクション。
/// </summary>
public IActionResult List()
{
  // RequestServices からデータベースコンテキストを取得する
  var dbContext = (TestDatabaseDbContext)HttpContext.RequestServices.GetService(typeof(TestDatabaseDbContext));
  
  // データベースから User 一覧を取得する
  var users = dbContext.Users.ToList();
  
  // ビューに渡す
  return View(users);
}

直接データベースコンテキストを作成する

コンソールやデスクトップアプリなどでも使われる手法です。 この方法でも問題なく使用できますが、 同時アクセスが多数起こりやすい Web アプリケーションにおいてトランザクションの範囲があいまいになりやすいのであまりお勧めはしません。 特に更新系はリクエスト単位にロールバックできない可能性があります。

この方法でデータベースにアクセスする場合、DbContext を派生させる手法を使うことになります。

まず StartupConfigurationstatic で扱えるように修正します。

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
    ConfigurationStatic = configuration;  // 追加
  }
  
  public IConfiguration Configuration { get; }
  public static IConfiguration ConfigurationStatic { get; private set; }  // 追加

  // 省略
}

TestDatabaseDbContext を派生させた TestDatabaseDbContextEx クラスを作成し、appsettings.json の接続文字列を設定するようにします。 接続文字列を設定できるのであれば、コンストラクタ経由で渡すなど別の方法をとっても構いません。

using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;

namespace DatabaseEntityFrameworkCoreMvc.Models.Database
{
  public partial class TestDatabaseDbContextEx : TestDatabaseDbContext
  {
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      if (!optionsBuilder.IsConfigured)
      {
        optionsBuilder.UseSqlServer(Startup.ConfigurationStatic.GetConnectionString("TestDatabaseDbContext"));
      }
    }
  }
}

データベースにアクセスする場合は、派生したクラスのインスタンスを生成して使用します。

/// <summary>
/// ユーザー一覧表示アクション。
/// </summary>
public IActionResult List()
{
  // 派生クラスのデータベースコンテキストのインスタンスを生成する
  using var dbContext = new TestDatabaseDbContextEx();
  
  // データベースから User 一覧を取得する
  var users = dbContext.Users.ToList();
  
  // ビューに渡す
  return View(users);
}

ASP.NET Core Razor ページでの使用例

ASP.NET Core Razor ページで Entity Framework Core を使用するにはいくつか方法があります。

ページモデルに DI (依存関係の挿入) で追加する

Startup.cs の services.AddDbContext メソッドで TestDatabaseDbContext を登録することによって ページモデルに挿入することができます。

DI の例としては IndexModel のコンストラクタには引数として ILogger<IndexModel> logger がありますが、 これは DI によって追加されているものです。同じように TestDatabaseDbContext も挿入することができます。

既存の IndexModel に追加する場合は以下のようになります。もちろん他のページモデルにも追加可能です。

using DatabaseEntityFrameworkCoreMvc.Models.Database;  // 追加

// 省略

public class IndexModel : PageModel
{
  private readonly ILogger<IndexModel> _logger;
  
  // 追加
  private readonly TestDatabaseDbContext _dbContext;
  
  public IndexModel(ILogger<IndexModel> logger, TestDatabaseDbContext dbContext)  // 追加
  {
    _logger = logger;
    _dbContext = dbContext;  // 追加
  }
  
  // 省略
}

後は受け取ったコンテキストのインスタンスを各処理で使用することによってデータベースにアクセスできます。

使用例

以下のコードはユーザーの一覧表示と新規登録を処理するコードです。 いずれも ASP.NET Core, Entity Framework Core 各々のコードになりますので細かい説明は省きます。 また、ユーザーの要求が終わるとコンテキストは自動的に破棄されます。

一覧画面用のページモデルを作成します。

List.cshtml.cs を以下のようにします。

using System.Collections.Generic;
using System.Linq;
using DatabaseEntityFrameworkCoreRazor.Models.Database;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace DatabaseEntityFrameworkCoreRazor.Pages
{
  public class ListModel : PageModel
  {
    private readonly TestDatabaseDbContext _dbContext;
    
    /// <summary>
    /// 一覧に表示するためのユーザー一覧を格納します。
    /// </summary>
    public List<User> Users { get; set; }
    
    /// <summary>
    /// DI で TestDatabaseDbContext を受け取ります。
    /// </summary>
    /// <param name="dbContext"></param>
    public ListModel(TestDatabaseDbContext dbContext)
    {
      _dbContext = dbContext;
    }
    
    /// <summary>
    /// ページにアクセスされたときに呼ばれます。
    /// </summary>
    public void OnGet()
    {
      // データベースからユーザー一覧を取得します。
      Users = _dbContext.Users.ToList();
    }
  }
}

List.cshtml は以下のように作成します。

@page
@model DatabaseEntityFrameworkCoreRazor.Pages.ListModel
@{
}

<h1>List</h1>

<p>
  <a asp-page="Create">Create New</a>
</p>
<table class="table">
  <thead>
    <tr>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].ID)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].Name)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].Password)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].Age)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].Email)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].Birthday)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.Users[0].UpdateDateTime)
      </th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    @foreach (var item in Model.Users)
    {
      <tr>
        <td>
          @Html.DisplayFor(modelItem => item.ID)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.Password)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.Age)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.Birthday)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.UpdateDateTime)
        </td>
        <td>
          <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
          <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
          <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
        </td>
      </tr>
    }
  </tbody>
</table>

List ページに遷移できるように Index.cshtml に追記します。

<!-- 追加 -->
<ul>
  <li><a asp-page="List">List</a></li>
</ul>

ユーザー登録画面も作成します。

Create.cshtml.cs コードを以下のように作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DatabaseEntityFrameworkCoreRazor.Models.Database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace DatabaseEntityFrameworkCoreRazor.Pages
{
  public class CreateModel : PageModel
  {
    private readonly TestDatabaseDbContext _dbContext;

    /// <summary>
    /// 登録するユーザー情報を格納します。
    /// </summary>
    [BindProperty]
    public User UserInfo { get; set; }

    /// <summary>
    /// DI で TestDatabaseDbContext を受け取ります。
    /// </summary>
    /// <param name="dbContext"></param>
    public CreateModel(TestDatabaseDbContext dbContext)
    {
      _dbContext = dbContext;
    }

    /// <summary>
    /// ページにアクセスされたときに呼ばれます。
    /// </summary>
    public void OnGet()
    {
      // 画面表示時は何もしません。
    }

    /// <summary>
    /// POST が送信されたときに呼ばれる。
    /// </summary>
    public IActionResult OnPost()
    {
      // エラーがある場合は登録画面に戻る
      if (ModelState.IsValid == false) return Page();

      // 更新日時設定
      UserInfo.UpdateDateTime = DateTime.Now;

      // 登録リストにユーザー追加
      _dbContext.Users.Add(UserInfo);

      // 登録を確定する
      _dbContext.SaveChanges();

      // 一覧画面に遷移
      return RedirectToPage("List");
    }
  }
}

Create.cshtml を以下のように作成します。

@page
@model DatabaseEntityFrameworkCoreRazor.Pages.CreateModel
@{
}

<h1>Create</h1>

<h4>User</h4>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <div class="form-group">
        <label asp-for="UserInfo.ID" class="control-label"></label>
        <input asp-for="UserInfo.ID" class="form-control" />
        <span asp-validation-for="UserInfo.ID" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UserInfo.Name" class="control-label"></label>
        <input asp-for="UserInfo.Name" class="form-control" />
        <span asp-validation-for="UserInfo.Name" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UserInfo.Password" class="control-label"></label>
        <input asp-for="UserInfo.Password" class="form-control" type="password" />
        <span asp-validation-for="UserInfo.Password" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UserInfo.Age" class="control-label"></label>
        <input asp-for="UserInfo.Age" class="form-control" />
        <span asp-validation-for="UserInfo.Age" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UserInfo.Email" class="control-label"></label>
        <input asp-for="UserInfo.Email" class="form-control" />
        <span asp-validation-for="UserInfo.Email" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UserInfo.Birthday" class="control-label"></label>
        <input asp-for="UserInfo.Birthday" class="form-control" type="date" />
        <span asp-validation-for="UserInfo.Birthday" class="text-danger"></span>
      </div>
      @*
        <div class="form-group">
          <label asp-for="UpdateDateTime" class="control-label"></label>
          <input asp-for="UpdateDateTime" class="form-control" />
          <span asp-validation-for="UpdateDateTime" class="text-danger"></span>
        </div>
      *@
      <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
      </div>
    </form>
  </div>
</div>

<div>
  <a asp-page="List">Back to List</a>
</div>

実行して確認します。 ちなみにパスワードの登録などはセキュリティ的な対策は何もしていないので、運用する際は ASP.NET Core やデータベースへの登録の手法に従ってください。

もちろんデータベースにも反映されています。

編集や削除処理は作成していませんので、一覧の右にある「Edit | Details | Delete」をクリックしてもエラーになります。 Create と同じ手法で作成できますので試してみてください。一応サンプルコードには上記3つの処理も追加しています。

RequestServices からデータベースコンテキストを取得する

ページモデルのコンストラクタに DI で追加する場合、必要なページモデル全てに追記しなければいけないためやや面倒です。 別の方法としては RequestServices から取得する方法もあります。

サンプルコードには含めていませんが、以下のように取得することが可能です。

/// <summary>
/// ページにアクセスされたときに呼ばれます。
/// </summary>
public void OnGet()
{
  // RequestServices からデータベースコンテキストを取得する
  var dbContext = (TestDatabaseDbContext)HttpContext.RequestServices.GetService(typeof(TestDatabaseDbContext));
  
  // データベースからユーザー一覧を取得します。
  Users = dbContext.Users.ToList();
}

直接データベースコンテキストを作成する

コンソールやデスクトップアプリなどでも使われる手法です。 この方法でも問題なく使用できますが、 同時アクセスが多数起こりやすい Web アプリケーションにおいてトランザクションの範囲があいまいになりやすいのであまりお勧めはしません。 特に更新系はリクエスト単位にロールバックできない可能性があります。

この方法でデータベースにアクセスする場合、DbContext を派生させる手法を使うことになります。

まず StartupConfigurationstatic で扱えるように修正します。

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
    ConfigurationStatic = configuration;  // 追加
  }
  
  public IConfiguration Configuration { get; }
  public static IConfiguration ConfigurationStatic { get; private set; }  // 追加

  // 省略
}

TestDatabaseDbContext を派生させた TestDatabaseDbContextEx クラスを作成し、appsettings.json の接続文字列を設定するようにします。 接続文字列を設定できるのであれば、コンストラクタ経由で渡すなど別の方法をとっても構いません。

using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;

namespace DatabaseEntityFrameworkCoreRazor.Models.Database
{
  public partial class TestDatabaseDbContextEx : TestDatabaseDbContext
  {
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      if (!optionsBuilder.IsConfigured)
      {
        optionsBuilder.UseSqlServer(Startup.ConfigurationStatic.GetConnectionString("TestDatabaseDbContext"));
      }
    }
  }
}

データベースにアクセスする場合は、派生したクラスのインスタンスを生成して使用します。

/// <summary>
/// ページにアクセスされたときに呼ばれます。
/// </summary>
public void OnGet()
{
  // 派生クラスのデータベースコンテキストのインスタンスを生成する
  using var dbContext = new TestDatabaseDbContextEx();
    
  // データベースからユーザー一覧を取得します。
  Users = dbContext.Users.ToList();
}