0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blazor + Entity Framework + SQL Server で ゼロからCRUD構築手順 .Net 8

Last updated at Posted at 2024-12-02

1.新しいプロジェクトの作成

image.png
image.png
image.png

2.NuGetパッケージの追加

追加導入するパッケージは3つ

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.SqlServer

image.png

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 パッケージマネージャーコンソールの表示

image.png

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

image.png

8.4 マイグレーションをSQL Serverへ反映

PM> dotnet ef database update

image.png

8.5 SQL SERVERで作成されたDBとテーブルを確認

image.png

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件)

image.png

10.2 登録画面

image.png

10.3 数件登録後の一覧画面

image.png

10.4 修正画面

image.png

11.全ソース

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?