データをDBに更新する際に失敗した場合の処置としてRollback
を実装する方法を記載します。
フロントエンドの実装
Views/AdminUser/AdminEmployeeMember.cshtml
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.OptionTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
@model List<ASPNETSQLServer.Models.AdminEmployeeViewModel>
@{
ViewData["Title"] = "従業員名簿";
}
<form asp-action="SubmitEmployeeList" method="post">
<div class="text-center">
<h2>従業員名簿</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>従業員ID</th>
<th>部署</th>
<th>課</th>
<th>役職</th>
<th>従業員名</th>
<th>性別</th>
<th>IPアドレス</th>
<th>割り当て</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.Count; i++)
{
<tr>
<!-- hidden ID -->
<td>
@Model[i].employeeId
<input type="hidden" name="Employees[@i].employeeId" value="@Model[i].employeeId" />
</td>
<td>
<select class="form-select" name="Employees[@i].Department">
<option value="人事部" selected="@(Model[i].Department == "人事部")">人事部</option>
<option value="経理部" selected="@(Model[i].Department == "経理部")">経理部</option>
<option value="総務部" selected="@(Model[i].Department == "総務部")">総務部</option>
<option value="営業部" selected="@(Model[i].Department == "営業部")">営業部</option>
<option value="技術部" selected="@(Model[i].Department == "技術部")">技術部</option>
<option value="法務部" selected="@(Model[i].Department == "法務部")">法務部</option>
<option value="情報システム部" selected="@(Model[i].Department == "情報システム部")">情報システム部</option>
</select>
</td>
<td>
<select class="form-select" name="Employees[@i].Section">
<option value="人事課" selected="@(Model[i].Section == "人事課")">人事課</option>
<option value="経理課" selected="@(Model[i].Section == "経理課")">経理課</option>
<option value="総務課" selected="@(Model[i].Section == "総務課")">総務課</option>
<option value="営業課" selected="@(Model[i].Section == "営業課")">営業課</option>
<option value="技術課" selected="@(Model[i].Section == "技術課")">技術課</option>
<option value="法務課" selected="@(Model[i].Section == "法務課")">法務課</option>
<option value="情報システム課" selected="@(Model[i].Section == "情報システム課")">情報システム課</option>
</select>
</td>
<td>
<select class="form-select" name="Employees[@i].JobClass">
<option value="執行役員" selected="@(Model[i].JobClass == "執行役員")">執行役員</option>
<option value="部長" selected="@(Model[i].JobClass == "部長")">部長</option>
<option value="課長" selected="@(Model[i].JobClass == "課長")">課長</option>
<option value="主任" selected="@(Model[i].JobClass == "主任")">主任</option>
<option value="一般社員" selected="@(Model[i].JobClass == "一般社員")">一般社員</option>
<option value="契約社員" selected="@(Model[i].JobClass == "契約社員")">契約社員</option>
<option value="パート・アルバイト" selected="@(Model[i].JobClass == "パート・アルバイト")">パート・アルバイト</option>
</select>
</td>
<td>
<input type="text" class="form-control" name="Employees[@i].EmployeeName" value="@Model[i].EmployeeName" />
</td>
<td>
<input type="radio" name="Employees[@i].Sex" value="男性" @(Model[i].Sex == "男性" ? "checked" : "") /> 男
<input type="radio" name="Employees[@i].Sex" value="女性" @(Model[i].Sex == "女性" ? "checked" : "") /> 女
</td>
<td>
<input type="text" class="form-control" name="Employees[@i].Ipadress" value="@Model[i].Ipadress" />
</td>
<td class="text-center">
<input type="checkbox" name="Employees[@i].IpaddressAlocation" value="true" @(Model[i].IpaddressAlocation ? "checked" : "") />
<input type="hidden" name="Employees[@i].IpaddressAlocation" value="false" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-primary">一括送信</button>
</div>
</form>
バックエンドの実装
例えば、バックエンドの実装で、下記の図のようにトランザクションを誤った位置に貼ってしまいトランザクションエラーが起こった場合にロールバックを実行してDBの一貫性を保ちます。
トランザクション−ロールバック機構は下記のように書きます。
sample.cs
SqlTransaction transaction = connection.BeginTransaction();
command.Transaction = transaction;
//~(略)~
Try{
//更新処理
}
catch(Exception e)
{
Console.WriteLine(e);
_logger.LogError($"予期せぬエラーが発生しました。エラーメッセージ:{e.Message}");
transaction.Rollback();
}
トランザクションエラーが発生するコードは下記になります。
sample.cs
[HttpPost]
public IActionResult SubmitEmployeeList(List<AdminEmployeeViewModel> Employees)
{
string updateQuery = @"UPDATE [test].[dbo].[employee] WITH (ROWLOCK)
SET
department = @department,
section = @section,
jobclass = @jobclass,
name = @name,
ipaddress = @ipaddress,
sex = @sex,
ipaddressalocation = @ipaddressalocation
WHERE
employeeId = @employeeId
";
using (SqlConnection connection = new SqlConnection(_connectString))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
SqlCommand command = connection.CreateCommand();
command.Transaction = transaction;
try
{
foreach (var emp in Employees)
{
using (command = new SqlCommand(updateQuery, connection))
{
command.Parameters.Add("@department", SqlDbType.NVarChar).Value = emp.Department;
command.Parameters.Add("@section", SqlDbType.NVarChar).Value = emp.Section;
command.Parameters.Add("@jobclass", SqlDbType.NVarChar).Value = emp.JobClass;
command.Parameters.Add("@name", SqlDbType.NVarChar).Value = emp.EmployeeName;
command.Parameters.Add("@ipaddress", SqlDbType.NVarChar).Value = emp.Ipadress;
command.Parameters.Add("@sex", SqlDbType.NVarChar).Value = emp.Sex;
command.Parameters.Add("@ipaddressalocation", SqlDbType.Bit).Value = emp.IpaddressAlocation;
command.Parameters.Add("@employeeId", SqlDbType.Int).Value = emp.employeeId;
// 行ロックを実行
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"従業員ID: {emp.employeeId} の更新:{rowsAffected}件");
}
transaction.Commit();
TempData["Message"] = "一括更新に成功しました。";
}
// トランザクション実行 本当は、はこの箇所に配置するのが正しい
//transaction.Commit();
//TempData["Message"] = "一括更新に成功しました。";
}
catch (DbException dbexception)
{
Console.WriteLine(dbexception);
_logger.LogError($"データベース接続に失敗しました。エラーメッセージ:{dbexception.Message}");
transaction.Rollback();
}
catch (Exception e)
{
Console.WriteLine(e);
_logger.LogError($"予期せぬエラーが発生しました。エラーメッセージ:{e.Message}");
transaction.Rollback();
}
}
// 完了後リダイレクトまたは再表示
return RedirectToAction("AdminEmployeeMember");
}