方針
💽Blazorアプリで既存のPostgreSQLを利用 #1
💽Blazorアプリで既存の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による説明
- レコードの取得:
var categoryToUpdate = await DbContext.PatternCategories.FindAsync(category.Id);
- この行で、
Id
が一致するPatternCategory
レコードを取得します。データベースに該当レコードが存在しない場合は、categoryToUpdate
にはnull
が返されます。
- この行で、
- 値の変更:
if (categoryToUpdate != null) { categoryToUpdate.Name = category.Name; // Nameプロパティの値を変更 }
- 取得したレコードが存在する場合、そのレコードの
Name
プロパティに新しい値を代入します。
- 取得したレコードが存在する場合、そのレコードの
- データベースに変更を保存:
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
とマークされているエンティティだけをデータベースに保存します。この場合、categoryToUpdate
がDbContext
によって変更されていると認識されているため、データベースにその変更が反映されます。
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#を触った事が無い私でもここまで開発ができました。
次回はフィルタリングを実装していきます。
💽Blazorアプリで既存のPostgreSQLを利用 #5