Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

이 글 에서는 Blazor 서버앱에서 Entity Framework Core(EF Core)를 사용하는 방법을 설명합니다. DB를 연결하고 사용자 Add, Edit, Delete 하는 화면까지 Step by Step 으로 진행합니다.

소개

Blazor는 .NET 웹 시스템으로, C# 및 Razor에 의해 제어됩니다. Entity Framework Core와 함께 Blazor 서버 앱에서 CRUD(Create, Read, Update, Delete) 작업을 수행하는 방법을 살펴보겠습니다.
이글에서 사용될 DB 는 MSSQL 이고 예제 DB 는 다음 에서 발췌하여 테스트를 진행 했습니다.

https://en.wikiversity.org/wiki/Database_Examples/Northwind/SQL_Server

Database First 로 DB 먼저 작성후 모델을 생성하도록 하겠습니다.

프로젝트 생성

먼저 가장 간단한 프로젝트를 생성해 보도록 하겠습니다.
이글에서는 Visual Studio 2022, .Net 7.0 으로 프로젝트를 생성했습니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

첫번째로 프로젝트 새로만들기를 클릭하여 블레이저 서버 앱을 선택하고 다음을 누릅니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

프로젝트 이름을 BlazorCRUD 로 정하고 다음을 누릅니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

화면 그대로 .Net7.0 으로 프로젝트를 생성합니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

위의 구조와 같은 프로젝트가 생성되었습니다.

데이터베이스 연결

EntityFramework 를 사용하고 모델을 자동 생성하기 위하여 다음 두가지 패키지를 설치합니다.

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

MSSQL 서버에 접속하여 모델을 생성하도록 하겠습니다.

DB 접속

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

DB 접속 정보를 확인하기 위하여 SQL Server 개체 탐색기를 엽니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

개체탐색기에서 접속 정보가 없다면 SQL Server 추가 버튼을 누릅니다.
이미 접속 정보가 있다면 이번 그림과 다음 그림은 건너띄기 하시면 됩니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

서버 접속 정보를 입력 하시고 연결을 누릅니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

미리 구성해 두었던 Northwind DB 가 보이며 선택하면 속성창에 연결 문자열이 보입니다. 그 연결 문자열을 복사합니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

appsetting.json 파일에 접속 정보를 붙여넣기 합니다.

"ConnectionStrings": {
"DefaultConnection": "Data Source=127.0.0.1,1433;Initial Catalog=Northwind;User ID=XXXXXXX;Password=XXXXX;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
}

위의 구문을 추가 하면 됩니다.

모델 자동 생성

우리는 Northwind 데이터베이스에서 Employees 테이블만 사용하도록 하겠습니다.

Scaffold-DbContext 명령어는 기존 데이터베이스를 기반으로 모델을 만드는 데 사용합니다. 패키지 관리자 콘솔에서 Scaffold-DbContext를 사용하여 지정할 수 있는 매개 변수는 다음과 같습니다.

Scaffold-DbContext [-Connection] [-Provider] [-OutputDir]
            [-Context] [-Schemas>] [-Tables>] 
            [-DataAnnotations] [-Force] [-Project]
            [-StartupProject] [<CommonParameters>]

위의 규칙을 읽어보시고 제 DB 에서 모델을 생성하도록 하겠습니다. 패키지 관리자 콘솔을 열어줍니다.
다음과 같이 타이핑 합니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기
Scaffold-DbContext "Name=ConnectionStrings:DefaultConnection" Microsoft.EntityFrameWorkCore.SqlServer -outputdir Data -context NorthwindContext -contextdir Data -DataAnnotations -Force -Tables Employees

그러면 솔루션 탐색기의 Data 폴더에 Employee.cs, NorthwindContxt.cs 파일이 자동 생깁니다.
Emplyee 모델과 DbContext 클래스 파일입니다.

appsetting.json 의 연결 문자열을 적용하기 위해서 NorthwindContxt.cs 에서 OnConfiguring 함수를 삭제합니다.

/*
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer("Name=ConnectionStrings:DefaultConnection");
*/

program.cs 에 다음을 추가합니다.

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? 
    throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContextFactory<NorthwindContext>(item => item.UseSqlServer(connectionString));

이제 Navimenu.razor 파일에 다음을 추가합니다. 메뉴를 추가 하였습니다.

    <div class="nav-item px-3">
        <NavLink class="nav-link" href="Employees">
            <span class="oi oi-people" aria-hidden="true"></span> Employees
        </NavLink>
    </div>

이제 CRUD 를 구성할 수 있는 파일 Razor 구성요소들을 추가하도록 하겠습니다.

  1. CRUD 의 Create를 담당하게될 AddEmployees.razor
  2. CRUD 의 Read를 담당하게될 Employees.razor
  3. CRUD 의 Update 를 담당하게될 EditEmployee.razor
  4. CRUD 의 Delete 를 담당헤게될 Delete.razor

AddEmployees.razor

@page "/AddEmployee"
@using BlazorCRUD.Data
@using Microsoft.EntityFrameworkCore;

@inject IDbContextFactory<NorthwindContext> DbFactory
@inject NavigationManager NavigationManager

<h2>Add Employee</h2>
<hr />

<form>
    <div class="row">
        <div class="col-md-8">
            <div class="form-group">
                <label for="Name" class="control-label">LastName</label>
                <input form="Name" class="form-control" @bind="@obj.LastName" />
            </div>
            <div class="form-group">
                <label for="Name" class="control-label">FirstName</label>
                <input form="Name" class="form-control" @bind="@obj.FirstName" />
            </div>
            <div class="form-group">
                <label for="BirthDay" class="control-label">BirthDay</label>
                <input form="BirthDay" class="form-control" @bind="@obj.BirthDate" />
            </div>
            <div class="form-group">
                <label for="Photo" class="control-label">Photo</label>
                <input form="Photo" class="form-control" @bind="@obj.Photo" />
            </div>
            <div class="form-group">
                <label for="Notes" class="control-label">Notes</label>
                <input form="Notes" class="form-control" @bind="@obj.Notes" />
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-4">
            <div class="form-group">
                <input type="button" class="btn btn-primary" @onclick="@CreateEmployee" value="Save" />
                <input type="button" class="btn btn-primary" @onclick="@Cancel" value="Cancel" />
            </div>
        </div>
    </div>
</form>

@code {
    Employee obj = new Employee();
    protected async void CreateEmployee()
    {
        using var context = DbFactory.CreateDbContext();

        if (obj is not null)
        {
            context.Employees?.Add(obj);
        }

        try
        {
            await context.SaveChangesAsync();
        }
        catch (Exception ex)
        {
        }



        NavigationManager.NavigateTo("Employees");
    }
    void Cancel()
    {
        NavigationManager.NavigateTo("Employees");
    }
}

Employees.razor

@page "/Employees"
@using BlazorCRUD.Data
@using Microsoft.EntityFrameworkCore;

@inject IDbContextFactory<NorthwindContext> DbFactory

<NavLink class="nav-link" href="AddEmployee">
    <span class="oi oi-plus" aria-hidden="true">Add New</span>

</NavLink>

<h1>Employee Info</h1>
@if (EmpObj is null)
{
    <p><em>Loading... !</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>EmployeeId</th>
                <th>LastName</th>
                <th>FirstName</th>
                <th>BirthDate</th>
                <th>Photo</th>
                <th>Notes</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var emp in EmpObj)
            {
                <tr>
                    <td>@emp.EmployeeId</td>
                    <td>@emp.LastName</td>
                    <td>@emp.FirstName</td>
                    <td>@emp.BirthDate</td>
                    <td>@emp.Photo</td>
                    <td>@emp.Notes</td>
                    <td>
                        <a class="nav-link" href="EditEmployee/@emp.EmployeeId">
                            <span class="oi oi-pencil" aria-hidden="true">Edit</span>
                        </a>
                        <a class="nav-link" href="Delete/@emp.EmployeeId">
                            <span class="oi oi-trash" aria-hidden="true">Delete</span>
                        </a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}


@code {
    List<Employee>? EmpObj;
    protected override async Task OnInitializedAsync()
    {
        using var context = DbFactory.CreateDbContext();

        EmpObj = await Task.Run(() => context.Employees.ToList<Employee>());
    }
}

EditEmployee.razor

@page "/EditEmployee/{Id}"
@using BlazorCRUD.Data
@using Microsoft.EntityFrameworkCore;

@implements IDisposable

@inject IDbContextFactory<NorthwindContext> DbFactory
@inject NavigationManager NavigationManager

<h2>Edit Employee</h2>
<hr />

<form>
    <div class="row">
        <div class="col-md-8">
            <div class="form-group">
                <input form="Name" class="form-control" @bind="@obj.EmployeeId" />
            </div>
            <div class="form-group">
                <label for="Name" class="control-label">LastName</label>
                <input form="Name" class="form-control" @bind="@obj.LastName" />
            </div>
            <div class="form-group">
                <label for="Name" class="control-label">FirstName</label>
                <input form="Name" class="form-control" @bind="@obj.FirstName" />
            </div>
            <div class="form-group">
                <label for="BirthDay" class="control-label">BirthDay</label>
                <input form="BirthDay" class="form-control" @bind="@obj.BirthDate" />
            </div>
            <div class="form-group">
                <label for="Photo" class="control-label">Photo</label>
                <input form="Photo" class="form-control" @bind="@obj.Photo" />
            </div>
            <div class="form-group">
                <label for="Notes" class="control-label">Notes</label>
                <input form="Notes" class="form-control" @bind="@obj.Notes" />
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-4">
            <div class="form-group">
                <input type="button" class="btn btn-primary" @onclick="@UpdateEmployee" value="Update" />
                <input type="button" class="btn btn-primary" @onclick="@Cancel" value="Cancel" />
            </div>
        </div>
    </div>
</form>

@code {
    [Parameter]
    public string Id { get; set; }
    Employee obj = new Employee();

    private NorthwindContext? Context { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Context = DbFactory.CreateDbContext();

        var employee = await Context.Employees.SingleOrDefaultAsync(c => c.EmployeeId == Convert.ToInt32(Id));

        if (employee is not null)
        {
            obj = employee;
        }
    }
    protected async void UpdateEmployee()
    {
        if (Context is not null)
        {
            await Context.SaveChangesAsync();
        }

        NavigationManager.NavigateTo("Employees");
    }
    void Cancel()
    {
        NavigationManager.NavigateTo("Employees");
    }
    public void Dispose()
    {
        Context?.Dispose();
    }
}

Delete.razor

@page "/Delete/{Id}"
@using BlazorCRUD.Data
@using Microsoft.EntityFrameworkCore;

@implements IDisposable

@inject IDbContextFactory<NorthwindContext> DbFactory
@inject NavigationManager NavigationManager

<h2>Delete Employee</h2>
<hr />
<h3>Are you sure want to delete this?</h3>
<form>
    <div class="row">
        <div class=" col-md-8">
            <div class="form-group">
                <label>Employee Id:</label>
                <label>@obj.EmployeeId</label>
            </div>
            <div class="form-group">
                <label>LastName:</label>
                <label>@obj.LastName</label>
            </div>
            <div class="form-group">
                <label>FirstName:</label>
                <label>@obj.FirstName</label>
            </div>
            <div class="form-group">
                <label>BirthDate:</label>
                <label>@obj.BirthDate</label>
            </div>
            <div class="form-group">
                <label>Photo:</label>
                <label>@obj.Photo</label>
            </div>
            <div class="form-group">
                <label>Notes:</label>
                <label>@obj.Notes</label>
            </div>
        </div>

    </div>
    <div class="row">
        <div class="col-md-4">
            <div class="form-group">
                <input type="button" class="btn btn-danger" @onclick="@DeleteEmployee" value="Delete" />
                <input type="button" class="btn btn-primary" @onclick="@Cancel" value="Cancel" />
            </div>
        </div>
    </div>
</form>

@code {
    [Parameter]
    public string Id { get; set; }
    Employee obj = new Employee();

    private NorthwindContext? Context { get; set; }
    protected override async Task OnInitializedAsync()
    {
        Context = DbFactory.CreateDbContext();

        var employee = await Context.Employees.SingleOrDefaultAsync(c => c.EmployeeId == Convert.ToInt32(Id));

        if (employee is not null)
        {
            obj = employee;
        }
    }
    protected async void DeleteEmployee()
    {
        if (Context is not null)
        {
            Context.Employees?.Remove(obj);
            await Context.SaveChangesAsync();
        }

        NavigationManager.NavigateTo("Employees");
    }
    void Cancel()
    {
        NavigationManager.NavigateTo("Employees");
    }
    public void Dispose()
    {
        Context?.Dispose();
    }
}

응용 프로그램 실행 및 테스트

Employee 탭 클릭하면 그리드에 모든 기존 세부 정보가 표시됩니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

DB Northwind의 Employee 정보가 자세히 표시됩니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

Add 버튼을 클릭하여 추가하는 화면이 구성되었습니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

Employee 정보를 수정할 수 있습니다.

Blazor 서버앱에서 Entity Framework Core로 CRUD 작업하기

Employee 삭제도 가능합니다.

결론

이 글에서는 Blazor 앱에서 EntityFramework를 통한 DB 접근 CRUD 를 알아보았습니다.

Leave a Comment