セツゾク

Hayato Shimada Portfolio & Blog

Site cover image

💽 Blazorアプリで既存のPostgreSQLを利用 #2

方針

💽Arrow icon of a page linkBlazorアプリで既存のPostgreSQLを利用 #1

💽Arrow icon of a page linkBlazorアプリで既存のPostgreSQLを利用 #5

前回はBlazorアプリのセットアップから、データベース連携、基本的なデータの表示までを実装しました。

今回は新たにpattern_categoryテーブルを追加して、そのテーブルにデータを追加、編集する処理を実装します。

patternsテーブルをユーザーが自由に分類してフィルタリングする処理を次の回で実装するための前準備になりますね。

pattern_categoryはデータベースに存在しないので、新たにCREATEしてから、再度マイグレーションを実行する方針です。

pattern_categoryテーブルの追加

pgAdmin 4側でcategoryテーブルをクリエイトします。

idとnameだけの簡単なテーブルです。

patternsにカラムを追加

外部キーとして厳密性を求めてもいいのですが、フィルタ機能のみの利用なので設定しません。

Blazorアプリ側でのマイグレーション

マイグレーションは前回も実行しましたが、テーブルを追加したり、カラムを追加したりPostgres側で変更をしたので、再度マイグレーションを行います。

やったことないので、バックアップ推奨です。

Scaffold-DbContext "Host=192.168.***.***;Database=YourDataBase;Username=postgres;Password=YourPass" Npgsql.EntityFrameworkCore.PostgreSQL -OutputDir Models

実行後にエラーが出たので確認してみます。

Build started...
Build succeeded.
To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
The following file(s) already exist in directory 'C:\Users\shimada.KANAMORI-SYSTEM\Desktop\KDX_Database\BlazorApp1\Models': kdx_metalContext.cs,Core.cs,CysRef.cs,DefectivesRef.cs,Employee.cs,EmployeeType.cs,FrameStatesRef.cs,FramesRef.cs,Material.cs,Message.cs,Mold.cs,Pattern.cs,PatternMaterial.cs,PatternType.cs,PlacesRef.cs,Pouring.cs,PouringGate.cs,ProsRef.cs,ProsTime.cs,Shift.cs,SpotsRef.cs. Use the Force flag to overwrite these files.
PM> 

どうやらすでにファイルが保存されているためにエラーが発生しているようなので、-Forceコマンドで強制的に上書きします。

Scaffold-DbContext "Host=192.168.***.***;Database=YourDataBase;Username=postgres;Password=YourPass" Npgsql.EntityFrameworkCore.PostgreSQL -OutputDir Models -Force

Modelsディレクトリにファイルが上書きされて、PatternCategoryがアプリ内で宣言されました。

カテゴリの編集ページ追加

ユーザーがカテゴリを自由に追加、編集できるようにpattern_categoryテーブルを操作するためのページを作成します。

Pagesディレクトリにcategory.razorコンポーネントを追加し、テーブルを閲覧できるようにコードを書きます。

前回作ったPlacesページをコピーしても良いかもしれません。

@page "/category"
@using BlazorApp1.Models
@inject kdx_metalContext DbContext
@using Microsoft.EntityFrameworkCore

<h3>Places Reference Data</h3>

@if (patternCategory == null)
{
    <p>Loading...</p>
}
else if (patternCategory.Count == 0)
{
    <p>No data available.</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var category in patternCategory)
            {
                <tr>
                    <td>@category.Id</td>
                    <td>@category.Name</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private List<PatternCategory> patternCategory;

    protected override async Task OnInitializedAsync()
    {
        // データベースからpattern_categoryテーブルのデータを取得
        patternCategory = await DbContext.PatternCategories.ToListAsync();
    }
}

No data available.と表示されてしまったので、pgAdmin側でレコードを追加して、再度実行してみましょう。

データベースから正常にデータを読み出すことができました。

データの編集処理を実装する

Nameカラムを変更する処理を実装してみましょう。

まず、NameをInput属性に変更して、データバインドを行います。

次に、Action列を追加し、Saveボタンを追加します。

@page "/category"
@using BlazorApp1.Models
@inject kdx_metalContext DbContext
@using Microsoft.EntityFrameworkCore

<h3>Pattern Categories</h3>

@if (patternCategory == null)
{
    <p>Loading...</p>
}
else if (patternCategory.Count == 0)
{
    <p>No data available.</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var category in patternCategory)
            {
                <tr>
                    <td>@category.Id</td>
                    <td>
                        <input type="text" class="form-control" @bind="category.Name" /> <!-- Nameのインプットボックス -->
                    </td>
                    <td>
                        <button class="btn btn-info" @onclick="() => SaveCategory(category)">Save</button> <!-- Saveボタンで変更を保存 -->
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

ボタンを押した際の変更処理にあたる、SaveCategory関数を@code内に書きます。

@code {
    private List<PatternCategory> patternCategory;

    protected override async Task OnInitializedAsync()
    {
        // データベースからpattern_categoryテーブルのデータを取得
        patternCategory = await DbContext.PatternCategories.ToListAsync();
    }

    // カテゴリの変更をデータベースに保存
    private async Task SaveCategory(PatternCategory category)
    {
        var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id);
        if (categoryToUpdate != null)
        {
            categoryToUpdate.Name = category.Name;
            await DbContext.SaveChangesAsync();
        }
    }
}
// カテゴリの変更をデータベースに保存
    private async Task SaveCategory(PatternCategory category)
    {
        var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id);
        if (categoryToUpdate != null)
        {
            categoryToUpdate.Name = category.Name;
            await DbContext.SaveChangesAsync();
        }
    }

SaveCategory関数では@foreach処理でcategoryとして展開されたリストを受け取ります。

変数categoryToUpdateを宣言し、データベースからcategory.Idでソートされた情報を受け取ります。

categoryToUpdateがnullでなければ、categoryToUpdate.Nameを入力された値(category.Name)に変更後に、再度データベースに接続して変更を適用します。

やっていることは大まかにわかるのですが、詳しく動きを調べてみます。

ChatGPTによる説明
  1. レコードの取得:
    var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id);
    • この行で、Id が一致する PatternCategory レコードを取得します。データベースに該当レコードが存在しない場合は、categoryToUpdate には null が返されます。
  2. 値の変更:
    if (categoryToUpdate != null)
    {
        categoryToUpdate.Name = category.Name; // Nameプロパティの値を変更
    }
    • 取得したレコードが存在する場合、そのレコードの Name プロパティに新しい値を代入します。
  3. データベースに変更を保存:
    await DbContext.SaveChangesAsync();
    • 変更されたエンティティをデータベースに保存します。この時点でEntity Frameworkは、追跡しているエンティティの変更点を検出し、SQLクエリを自動的に生成してデータベースを更新します。

var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id); で変数を定義する理由は、データベースから特定のレコードを取得し、そのレコードのプロパティを変更して、追跡されたエンティティを通じてデータベースを更新するためです。これにより、データベースとアプリケーションの間で効率的かつ一貫したデータのやり取りが可能になります。

SaveChangesAsyncに引数が指定されていない違和感

かなり脱線が続きますが、DbContext.SaveChangesAsync();のところで、何故categoryToUpdateの変更が反映されるのか疑問に感じたので、調べてみます。

var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id);

FindAsync メソッドで categoryToUpdate を取得すると、そのエンティティは DbContext によって追跡されます。このエンティティのプロパティに対して変更を加えると、EF Core はそのエンティティを Modified(変更された) としてマークします。

SaveChangesAsync() は変更されたエンティティのみを更新:

  • SaveChangesAsync() を呼び出すと、EF Core は追跡しているエンティティの中で Modified とマークされているエンティティだけをデータベースに保存します。この場合、categoryToUpdateDbContext によって変更されていると認識されているため、データベースにその変更が反映されます。

patternCategoryが変更されない理由

pattern_category全体のデータをリストとして持っているpatternCategory変数もDbContextによって追跡されているはずですが、このコード内では変更が行われていない為、変更点だけがデータベースに適用されるという処理のようです。

EF Coreの機能とのことですが、高機能すぎて違和感がすごい。

データ編集処理を確認してみる

脱線が続いてしまいましたが、データ編集処理を実際に確認してみます。

sample→製品カテゴリ1に名称を変更してSaveボタンを押します。

データベース側での反映も確認できました。

レコードの追加処理を実装する

入力フィールドの作成

ページ上部にレコードを追加できるように入力フィールドを作成します。

@page "/category"
@using BlazorApp1.Models
@inject kdx_metalContext DbContext
@using Microsoft.EntityFrameworkCore

<h3>Pattern Categories</h3>

<!-- 新しいカテゴリを追加するための入力フィールドとボタン -->
<div class="form-inline mb-3">
    <label>New Category Name:</label>
    <input type="text" @bind="newCategoryName" class="form-control mx-2" placeholder="Enter new category name..." />
    <button class="btn btn-success" @onclick="AddCategory">Add</button>
</div>

idをデータベース側でインクリメントさせる

新しいカテゴリを追加する処理を実装したいのですが、idは自動でインクリメントさせたいので、patten_categoryをsmallserialで再定義する為、いったんテーブルを削除して再設定します。

pgAdmin上でsmallserialとしてデータ型を定義するとnextvalの設定が追加され、シーケンスにpattern_category_id_seqが新たに設定されています。

テーブルの状態が更新されたので再度マイグレーションを行います。

Scaffold-DbContext "Host=192.168.***.***;Database=YourDataBase;Username=postgres;Password=YourPass" Npgsql.EntityFrameworkCore.PostgreSQL -OutputDir Models -Force

レコード追加の処理を実装

Input側で入力されたデータは@bind="newCategoryName"としてデータバインドされています。

newCategoryという空のテーブルを新たに宣言して、Nameプロパティに入力値を代入します。

DbConext.PatternCategories.Add(newCategory)でデータベースにカテゴリを追加後、

DbContextが変更されたことを確認して、await DbContext.SaveChangesAsync();が実行されます。

// 新しいカテゴリを追加
private async Task AddCategory()
{
    if (!string.IsNullOrWhiteSpace(newCategoryName)) // 空でないことを確認
    {
        var newCategory = new PatternCategory
            {
                Name = newCategoryName
            };

        // データベースに新しいカテゴリを追加
        DbContext.PatternCategories.Add(newCategory);
        await DbContext.SaveChangesAsync();

        // 新しいカテゴリをリストに追加してUIを更新
        patternCategory.Add(newCategory);

        // 入力フィールドをクリア
        newCategoryName = string.Empty;
    }
}

追加されたことを確認してみる

新たに3つのテーブルを作成してみましたが、変更はクライアント側で即時確認できるようになっていますね。idのインクリメント処理もOKのようです。

まとめ

EF Coreの処理に慣れが必要ですが、詳しい仕組みは置いといて、構文で覚えれば問題なさそうです。

「勉強しなきゃプログラミングはできない。」のではなく、作りたいものを作りながら学ぶ姿勢の方が吸収も早いしいいですね。

コードのほとんどはChatGPTが書いているので、C#を触った事が無い私でもここまで開発ができました。

次回はフィルタリングを実装していきます。

💽Arrow icon of a page linkBlazorアプリで既存のPostgreSQLを利用 #5