1.新しいプロジェクトの作成
2.NuGetパッケージの追加
追加導入するパッケージは3つ
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.SqlServer
3.DB接続文字列をappsettings.jsonに登録
{
"ConnectionStrings": {
"DefaultConnection": "Server=.\\SQLEXPRESS;Database=MovieDb;Trusted_Connection=true;TrustServerCertificate=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
- .\SQLEXPRESSの「.」はlocalhost
- Trusted_Connection=true
→ Windows認証で接続 - TrustServerCertificate=true
→サーバー 証明書の検証をスキップ(信頼する)
4.テーブル構造(class)の作成
\Models\Movie.cs
namespace Blazor_EF_CRUD.Models
{
public class Movie
{
public int Id { get; set; }
public required string Title { get; set; }
public int ReleaseYear { get; set; }
}
}
名前が Id または [クラス名]Idのプロパティが自動的にプライマリーキーになる。
5.DbContext継承クラスの作成
\Context\DataContext.cs
using Blazor_EF_CRUD.Models;
using Microsoft.EntityFrameworkCore;
namespace Blazor_EF_CRUD.Context
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options)
: base(options)
{
}
public required DbSet<Movie> Movies { get; set; }
}
}
DbContext継承クラスに登録されているDbSet(ここではMovieクラス)がマイグレーション時にテーブルとして作成される。
6.テーブルアクセスの作成
6.1 インターフェース
\Services\IMovieService.cs
using Blazor_EF_CRUD.Models;
namespace Blazor_EF_CRUD.Services
{
public interface IMovieService
{
// ------------ READ ------------
// 全件取得
Task<List<Movie>> GetAllMovies();
// IDで1件取得
Task<Movie?> GetMovieById(int id);
// ------------ CREATE ------------
Task<Movie> AddMovie(Movie movie);
// ------------ UPDATE ------------
Task<Movie> EditMovie(int id, Movie movie);
// ------------ DELETE ------------
Task DeleteMovie(int id);
}
}
6.2 サービス
\Services\MovieService.cs
using Blazor_EF_CRUD.Context;
using Blazor_EF_CRUD.Models;
using Microsoft.EntityFrameworkCore;
namespace Blazor_EF_CRUD.Services
{
public class MovieService : IMovieService
{
private readonly DataContext _context;
public MovieService(DataContext context)
{
_context = context;
}
// ------------ READ ------------
// 全件取得
public async Task<List<Movie>> GetAllMovies()
{
var movies = await _context.Movies.ToListAsync();
return movies;
}
// IDで1件取得
public async Task<Movie?> GetMovieById(int id)
{
var movie = await _context.Movies.FindAsync(id);
return movie;
}
// ------------ CREATE ------------
public async Task<Movie> AddMovie(Movie movie)
{
_context.Movies.Add(movie);
await _context.SaveChangesAsync();
return movie;
}
// ------------ UPDATE ------------
public async Task<Movie> EditMovie(int id, Movie newMovie)
{
var dbMovie = await _context.Movies.FindAsync(id);
if (dbMovie == null)
{
throw new Exception($"Movie not found ID={id}");
}
dbMovie.Title = newMovie.Title;
dbMovie.ReleaseYear = newMovie.ReleaseYear;
_context.Movies.Update(dbMovie);
await _context.SaveChangesAsync();
return dbMovie;
}
// ------------ DELETE ------------
public async Task DeleteMovie(int id)
{
var dbMovie = await _context.Movies.FindAsync(id);
if (dbMovie == null)
{
throw new Exception($"Movie not found ID={id}");
}
_context.Remove(dbMovie);
await _context.SaveChangesAsync();
}
}
}
7.program.csへ Entity Framework 情報追加
using Blazor_EF_CRUD.Components;
using Blazor_EF_CRUD.Context;
using Blazor_EF_CRUD.Services;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// -------------- Entity Framework ---------------
var dbConnection = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<DataContext>(options =>
options.UseSqlServer(dbConnection));
builder.Services.AddScoped<IMovieService, MovieService>();
// -----------------------------------------------
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
8.マイグレーションの開始
8.1 パッケージマネージャーコンソールの表示
8.2 program.csのあるディレクトリへ移動
PM> cd D:\Dev\Blazor_EF_CRUD\Blazor_EF_CRUD
PM> dir
ディレクトリ: D:\Dev\Blazor_EF_CRUD\Blazor_EF_CRUD
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2024/12/03 5:07 bin
d----- 2024/12/03 5:07 Components
d----- 2024/12/03 5:32 Context
d----- 2024/12/03 5:30 Models
d----- 2024/12/03 5:13 obj
d----- 2024/12/03 5:07 Properties
d----- 2024/12/03 5:49 Services
d----- 2024/12/03 5:07 wwwroot
-a---- 2024/12/03 5:07 127 appsettings.Development.json
-a---- 2024/12/03 6:00 301 appsettings.json
-a---- 2024/12/03 6:09 686 Blazor_EF_CRUD.csproj
-a---- 2024/12/03 5:07 238 Blazor_EF_CRUD.csproj.user
-a---- 2024/12/03 6:03 1157 Program.cs
PM>
8.3 マイグレーションの作成
PM> dotnet ef migrations add Initial
8.4 マイグレーションをSQL Serverへ反映
PM> dotnet ef database update
8.5 SQL SERVERで作成されたDBとテーブルを確認
9.Blazor側を作成
9.1 一覧画面
\Components\Pages\Movies.razor
@page "/movies"
@using Blazor_EF_CRUD.Models
@using Blazor_EF_CRUD.Services
@inject IMovieService MovieService
@inject NavigationManager NavigationManager
@attribute [StreamRendering(true)]
@rendermode @(new InteractiveServerRenderMode(prerender: false))
<h3>MovieList</h3>
@if (movies == null)
{
<p>Loading...</p>
}
else
{
<div class="mb-3">
<table class="table">
<thead>
<tr>
<th>タイトル</th>
<th>発表</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var movie in movies)
{
<tr>
<td class="align-middle">@movie.Title</td>
<td class="align-middle">@movie.ReleaseYear</td>
<td class="align-middle"><button @onclick="() => EditMovie(movie.Id)" class="btn btn-link">編集</button></td>
<td class="align-middle"><button @onclick="() => DeleteMovie(movie.Id)" class="btn btn-danger">削除</button></td>
</tr>
}
</tbody>
</table>
</div>
}
<div class="mb-3">
<button @onclick="CreateMovie" class="btn btn-primary">新しい映画を追加</button>
</div>
@code {
List<Movie>? movies = null;
protected override async Task OnInitializedAsync()
{
movies = await MovieService.GetAllMovies();
}
void CreateMovie(){
NavigationManager.NavigateTo("/editmovie");
}
void EditMovie(int id)
{
NavigationManager.NavigateTo($"/editmovie/{id}");
}
async Task DeleteMovie(int id)
{
await MovieService.DeleteMovie(id);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
9.2 登録/修正画面
\Components\Pages\EditMovie.razor
@page "/editmovie"
@page "/editmovie/{Id:int}"
@using Blazor_EF_CRUD.Models
@using Blazor_EF_CRUD.Services
@inject IMovieService MovieService
@inject NavigationManager NavigationManager
@rendermode InteractiveServer
<h3>Edit Movie</h3>
<EditForm Model="movie" OnSubmit="HandleSubmit">
<div>
<div class="mb-3">
<label class="form-label">タイトル</label>
<InputText @bind-Value="movie.Title" class="form-control"></InputText>
</div>
<div class="mb-3">
<label class="form-label">発表(年)</label>
<InputNumber @bind-Value="movie.ReleaseYear" class="form-control"></InputNumber>
</div>
</div>
<button type="submit" class="btn btn-primary">登録</button>
</EditForm>
@code {
[Parameter]
public int? Id { get; set; }
Movie movie { get; set; } = new Movie{Title = string.Empty};
// Idを受け取った場合はデータ取得
protected override async Task OnParametersSetAsync()
{
if (Id == null) {
return;
}
var getMovie = await MovieService.GetMovieById((int)Id);
if (getMovie != null){
movie = getMovie;
}
}
async Task HandleSubmit()
{
if(Id == null) {
await MovieService.AddMovie(movie);
}else{
await MovieService.EditMovie((int)Id, movie);
}
NavigationManager.NavigateTo("/movies");
}
}
9.3 ナビゲーションメニューの修正
\Components\Layout\NavMenu.razor
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Blazor_EF_CRUD</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="movies">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-film" viewBox="0 0 16 16">
<path d="M0 1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1zm4 0v6h8V1zm8 8H4v6h8zM1 1v2h2V1zm2 3H1v2h2zM1 7v2h2V7zm2 3H1v2h2zm-2 3v2h2v-2zM15 1h-2v2h2zm-2 3v2h2V4zm2 3h-2v2h2zm-2 3v2h2v-2zm2 3h-2v2h2z" />
</svg> Movies
</NavLink>
</div>
</nav>
</div>
10. 動作確認
10.1 初期一覧画面(登録データ0件)
10.2 登録画面
10.3 数件登録後の一覧画面
10.4 修正画面
11.全ソース