【ASP.NET】Web未経験者がASPのMSDNチュートリアルやってみる（DB操作）

筆者のステータスはこんな感じ。

- VB.NET経験あり
- C#独学
- Web未経験（何それおいしいの？）

時代はWebだということで、手を出してみます。
手を出すチュートリアルはコチラ。

https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/?view=aspnetcore-2.1&preserve-view=true

今回はMSDNのチュートリアル「データベースの操作」を進めます。

https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/sql?view=aspnetcore-2.1&tabs=visual-studio

※チュートリアルのパート3は説明会なので飛ばしました。

尚、前回実施した「【ASP.NET】Web未経験者がASPのMSDNチュートリアルやってみる（モデルの追加）」の続きから始めます。

https://qiita.com/green_fox/items/fe4cb5c48dbe12c6ea36

#作成の流れ
まずは今回の作成の流れを確認します。MSDNのチュートリアルに沿って次の様に進めます。

1. データベース接続
2. データベースのシード
3. シード初期化子の追加
4. 実行

今回のチュートリアルも簡単そうに見えますね。

## 1.データベース接続
まずはデータベース接続の話。
MSDNのチュートリアルでは、RazorPagesMovieContext オブジェクトの説明が記載されています。
>RazorPagesMovieContext オブジェクトは、データベースへの接続と、データベース レコードへの Movie オブジェクトのマッピングのタスクを処理します。 データベース コンテキストは、Startup.cs の メソッドで依存性の注入コンテナーに登録されます。

唐突に出て来るRazorPagesMovieContextオブジェクトというのは、DbContextクラスの派生クラスであるRazorPagesMovieContextクラスのことですね。
Startup.csの中を見ると、
```c#
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<RazorPagesMovieContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));
}
```

と記載されています。
services.AddDbContext はEntityFrameworkServiceCollectionExtensionsクラスの中に定義されています。
じゃあ、DBに接続する接続文字列はどこに書くのかというと、appsettings.jsonファイルの中で記載します。

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "RazorPagesMovieContext": "Server=(localdb)\\mssqllocaldb;Database=RazorPagesMovieContext-918ff605-1ec6-41e0-92e5-1c1a816338c5;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}
```
ConnectionStringsの後ろにキーを渡して、接続文字列を記載します。

実際にローカルに作られたDBを見てみます。
Visual Studio のメニューバー「表示」から「SQL Server オブジェクト エクスプローラー」を選択します。
![20220225_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/ee102fea-53e1-ec51-a721-34daeff4def5.png)
すると、Visual Studio の画面左側に、新しく「SQL Server オブジェクト エクスプローラー」が表示されます。
![20220225_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/01129765-337e-dd37-198b-7129a7d06420.png)
次に、「(localdb)\MSSQLLocalDB」→「データベース」→「RazorPagesMovieContext-xxxx（キー）」→「テーブル」の順に開き、「dbo.Movie」を右クリックして「デザイナーの表示」を選択します。
![20220225_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/b1997f76-d65a-3d1c-ba97-b8b6336974f7.png)
すると、テーブルの定義が表示されます。
![20220225_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/6cf695f5-965c-8c23-2a7f-4906b103b87d.png)
この定義はMovie.csで記述されているプロパティを元に、EntityFramework が自動で生成したものです。
Movie.csの最初に書かれているプロパティが主キーに設定されます。
```c#
namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }     //EntityFramework が自動で主キーに設定
        public string Title { get; set; }

        [DataType(DataType.Date)]public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}
```

次にテーブルのデータを見てみます。
「dbo.Movie」を右クリックして「データの表示」を選択します。
![20220225_005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/e942ab25-d0b8-e1eb-86c8-bfc49f16b81a.png)

すると、テーブルのデータが表示されます。
![20220225_006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/e58c5f28-c09c-7a79-9fc7-24179e4f22d6.png)
チュートリアルで作成したデータはここで見ることが出来ます。

## 2.データベースのシード
引き続き、MSDNのチュートリアルに沿って進めます。
次に出て来るのは「データベースのシード」について。

いきなり出てきましたシード。シードとは英語で種という意味ですが、ここでは初期のサンプルデータのことを指します。
RazorPagesMovie プロジェクトに「SeedData」フォルダを作成し、その中に「SeedData」クラスを作成します。
「SeedData」フォルダを作成して…
![20220225_007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/aaad25a0-5df1-cee3-5838-c23881b16fac.png)
![20220225_008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/755dfd4e-01ba-23c5-d2a4-2687231c79a9.png)
「SeedData」クラスを作成（「SeedData.cs」ファイルが作成されます）。
![20220225_009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/382321fb-b20f-a897-6018-f617c266f781.png)
![20220225_010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/65877d51-b0c6-2b05-9814-e20c71a60942.png)
![20220225_011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/ecd4acad-3d28-b7ca-878e-c1605949e423.png)
そして、「SeedData.cs」ファイルに以下を記述。
```c#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
using System;
using System.Linq;

namespace RazorPagesMovie.Models
{
    public static class SeedData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            using (var context = new RazorPagesMovieContext(
                serviceProvider.GetRequiredService<
                    DbContextOptions<RazorPagesMovieContext>>()))
            {
                if (context.Movie.Any())
                {
                    return;   // DB has been seeded
                }

                context.Movie.AddRange(
                    new Movie
                    {
                        Title = "When Harry Met Sally",
                        ReleaseDate = DateTime.Parse("1989-2-12"),
                        Genre = "Romantic Comedy",
                        Price = 7.99M
                    },

                    new Movie
                    {
                        Title = "Ghostbusters ",
                        ReleaseDate = DateTime.Parse("1984-3-13"),
                        Genre = "Comedy",
                        Price = 8.99M
                    },

                    new Movie
                    {
                        Title = "Ghostbusters 2",
                        ReleaseDate = DateTime.Parse("1986-2-23"),
                        Genre = "Comedy",
                        Price = 9.99M
                    },

                    new Movie
                    {
                        Title = "Rio Bravo",
                        ReleaseDate = DateTime.Parse("1959-4-15"),
                        Genre = "Western",
                        Price = 3.99M
                    }
                );
                context.SaveChanges();
            }
        }
    }
}
```
「SeedData」クラスの記述を見て見ると、Initialize メソッドでcontext.Movie.AddRangeを実行し、引数には、プロパティを用意している「Movie」クラスのインスタンスを生成し、それぞれのプロパティに値をセットしていることが分かります。

つまり、「SeedData」クラスが使われると、自動的に初期データが用意されるはずです。
ただし、今回追加したクラスのため、この「SeedData」クラスを呼び出す必要があります。

## 3.シード初期化子の追加
先ほど作成した「SeedData」クラスを呼び出す記述を、「Program」クラスの Main メソッドに追加します。
チュートリアルだと差分が分かりにくいので、コメントアウトで追加・修正個所を記載しておきました。

```c#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;     //追加
using RazorPagesMovie.Models;                       //追加

namespace RazorPagesMovie
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //CreateHostBuilder(args).Build().Run();        //元々の記述
            var host = CreateHostBuilder(args).Build();     //修正

            //追加 start
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;

                try
                {
                    SeedData.Initialize(services);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred seeding the DB.");
                }
            }

            host.Run();
            //追加 end
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
```
Main メソッド内に 先ほど追加した「SeedData」クラスの Initialize メソッドが呼び出されているので、シードの生成が可能となります。

## 4.実行
では、一度ビルドして実行してみます。
実行して、https://localhost:xxxx/movies に入ってみると…
![20220225_012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/7f6d4c6e-6956-28a2-7459-bd6664c165ea.png)
残念ながら出来てませんね。
完成したからとりあえず実行した人はこうなります（私はなりました）。

さて、MSDNのチュートリアルに戻ると、「データベース内のレコードを削除しなさい」とあります。
なので、ブラウザから「Delete」をクリックして削除しておきます。
![20220225_013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/41f2c9de-7565-2427-4478-8cf98c635209.png)
せっかく作ったデータが消えてしまいましたね。

ただ、本題はシードデータを追加する事です。
アプリを動かした状態で、システムトレイの「IIS Express」アイコンを右クリックします。
![20220225_014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/89e7d5d3-4135-3f9b-9852-d93037a8708a.png)
コンテキストメニューから「終了」（もしくは「サイトの停止」）を選択します。
![20220225_015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/e8ee1273-9cab-c609-8029-12ef204c2a0e.png)
すると、「すべてのワーカープロセスを停止して終了しますか？」とポップアップが出るので、迷わず「はい」を選択。
![20220225_016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/5de4d000-ebe8-5ead-35a6-07916ca4a96d.png)
そしてF5キーを押して、https://localhost:xxxx/movies に入ってみると…
![20220225_017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/f44b23c9-8442-a59b-6d38-01695919abc3.png)
シードのデータが作成されていることがわかります。
# 参考
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/sql?view=aspnetcore-2.1&tabs=visual-studio