Blazor 서버 앱에서 EF Core로 데이터베이스 연동하기

EF Core 데이터 연동
📚
꼭 알아둘 점

EF Core 데이터 연동

Entity Framework Core를 Blazor 서버 앱에 연결해 데이터베이스를 조회·저장합니다.

Entity Framework Core를 Blazor Server 앱에 연결해 데이터를 조회하고 저장하는 방법을 단계별로 학습합니다.

📌 학습 목표

  • Entity Framework Core(EF Core)의 역할과 Blazor Server에서의 동작 방식을 설명할 수 있습니다.
  • DbContext와 엔티티 모델을 정의하고 마이그레이션을 실행할 수 있습니다.
  • IDbContextFactory 패턴을 활용해 Blazor 컴포넌트에서 안전하게 DB에 접근할 수 있습니다.
  • Blazor 컴포넌트에서 데이터 조회·생성·수정·삭제(CRUD)를 구현할 수 있습니다.
  • EF Core 사용 시 자주 발생하는 오류와 해결 방법을 이해합니다.

📝 개념 설명

Entity Framework Core(EF Core)란?

EF Core는 .NET의 공식 ORM(Object-Relational Mapper)입니다. ORM이란 C# 클래스와 데이터베이스 테이블을 자동으로 매핑해주는 기술로, SQL을 직접 작성하지 않고도 C# 코드만으로 데이터베이스를 조작할 수 있게 해줍니다.

  • 엔티티(Entity): 데이터베이스 테이블에 대응하는 C# 클래스
  • DbContext: 데이터베이스 연결과 쿼리를 관리하는 핵심 클래스
  • 마이그레이션(Migration): C# 모델 변경을 실제 DB 스키마에 반영하는 버전 관리 메커니즘
  • LINQ 쿼리: C# 코드로 SQL을 대체하는 쿼리 방식

 

 

🔑 EF Core 핵심 구성 요소

 

🗄️
엔티티(Entity)
DB 테이블과 1:1 대응하는 C# 클래스. 속성이 컬럼으로 매핑됨
🔌
DbContext
DB 연결·트랜잭션·쿼리 실행을 담당하는 EF Core의 핵심 관문
🔄
마이그레이션
C# 모델 변경을 DB 스키마에 안전하게 반영하는 버전 관리 시스템
🔍
LINQ 쿼리
Where·Select·OrderBy 등 C# 코드로 데이터를 조회·조작하는 방식

Blazor Server에서 EF Core를 사용하는 방식

일반 ASP.NET Core 앱과 달리 Blazor Server는 클라이언트와 상시 연결된 SignalR 커넥션을 유지합니다. 이 구조 때문에 일반적인 방식으로 DbContext를 주입하면 심각한 문제가 발생합니다.

⚠️ 왜 Blazor에서 일반 DbContext 주입이 위험한가?
DbContext는 기본적으로 Scoped(요청 단위) 수명을 가집니다. 그런데 Blazor Server는 HTTP 요청이 아니라 지속적인 SignalR 연결 기반이므로, Scoped DbContext가 컴포넌트 전체 생명주기 동안 살아있게 됩니다. 장시간 열려있는 DbContext는 변경 추적(Change Tracking) 누적, 동시성 충돌, 메모리 누수 등의 문제를 유발합니다.

IDbContextFactory 패턴 — Blazor 공식 권장 방식

Microsoft가 공식 권장하는 Blazor + EF Core 패턴은 IDbContextFactory<T>를 사용하는 것입니다. 이 패턴에서는 DB 작업이 필요할 때마다 새 DbContext를 생성하고, 작업이 끝나면 즉시 해제합니다.

  • DbContext 수명을 코드로 명시적으로 제어
  • 동시성 문제 방지 (각 작업마다 독립적인 DbContext 사용)
  • using 키워드로 블록 종료 시 DbContext 자동 해제

 

 

🔄 Blazor + EF Core 데이터 흐름

 

Blazor 컴포넌트
IDbContextFactory
DbContext 생성
LINQ 쿼리 실행
SQLite / SQL Server

💡 예제 & 실습 — 할 일 목록 앱 만들기

SQLite 데이터베이스와 연결되는 할 일(Todo) 앱을 단계별로 구축합니다.

Step 1. NuGet 패키지 설치

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
  • Sqlite: 파일 기반 SQLite 프로바이더 (개발·학습용)
  • Tools: dotnet ef CLI 명령 도구
  • Design: 마이그레이션 생성에 필요한 디자인 타임 컴포넌트
💡 운영 환경에서는 Microsoft.EntityFrameworkCore.SqlServer 또는 Npgsql.EntityFrameworkCore.PostgreSQL로 교체합니다. 연결 문자열만 변경하면 나머지 코드는 동일하게 유지됩니다.

Step 2. 엔티티 모델 정의

Models/TodoItem.cs 파일을 생성합니다.

using System.ComponentModel.DataAnnotations;

public class TodoItem
{
    public int Id { get; set; }

    [Required]
    [MaxLength(200)]
    public string Title { get; set; } = string.Empty;

    public bool IsCompleted { get; set; }

    public DateTime CreatedAt { get; set; } = DateTime.Now;
}
  • Id: EF Core가 자동으로 기본 키(Primary Key)로 인식
  • [Required]: NOT NULL 제약 조건 적용
  • [MaxLength(200)]: 문자열 최대 길이 제한
  • CreatedAt: 레코드 생성 시각 자동 기록

Step 3. DbContext 작성

Data/AppDbContext.cs 파일을 생성합니다.

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<TodoItem> TodoItems { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TodoItem>()
            .Property(t => t.Title)
            .IsRequired();
    }
}
  • DbContextOptions<AppDbContext>: 연결 문자열 등 설정 정보를 생성자로 수신
  • DbSet<TodoItem> TodoItems: TodoItems 테이블에 대응하는 컬렉션 속성
  • OnModelCreating: Fluent API를 통한 추가 스키마 세부 설정 (어노테이션으로 설정한 내용과 중복 가능)

Step 4. Program.cs에 서비스 등록

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Blazor Server에서는 AddDbContext 대신 AddDbContextFactory 사용
builder.Services.AddDbContextFactory<AppDbContext>(options =>
    options.UseSqlite("Data Source=todos.db"));

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();
// ...이하 생략
⚠️ 핵심 주의사항: Blazor Server에서는 반드시 AddDbContextFactory를 사용해야 합니다. AddDbContext를 사용하면 DbContext가 Scoped로 등록되어 Blazor의 긴 연결 수명과 충돌하고, 예기치 않은 오류나 메모리 문제를 유발합니다.

Step 5. 마이그레이션 생성 및 데이터베이스 적용

# 1단계: 마이그레이션 파일 생성
dotnet ef migrations add InitialCreate

# 2단계: 데이터베이스에 스키마 반영
dotnet ef database update

명령 실행 후 프로젝트에 Migrations/ 폴더가 생성되고, 프로젝트 루트에 todos.db SQLite 파일이 만들어집니다.

Step 6. Blazor 컴포넌트에서 CRUD 구현

Components/Pages/TodoList.razor 파일을 작성합니다.

@page "/todos"
@using Microsoft.EntityFrameworkCore
@inject IDbContextFactory<AppDbContext> DbFactory

<h1>할 일 목록</h1>

@if (todos == null)
{
    <p>데이터를 불러오는 중...</p>
}
else if (!todos.Any())
{
    <p>등록된 할 일이 없습니다.</p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>
                @todo.Title
                <button @onclick='() => DeleteTodo(todo.Id)'>삭제</button>
            </li>
        }
    </ul>
}

<hr />
<input @bind='newTitle' placeholder='새 할 일 입력' />
<button @onclick='AddTodo'>추가</button>

@code {
    private List<TodoItem>? todos;
    private string newTitle = string.Empty;

    protected override async Task OnInitializedAsync()
    {
        await LoadTodosAsync();
    }

    private async Task LoadTodosAsync()
    {
        using var context = await DbFactory.CreateDbContextAsync();
        todos = await context.TodoItems
            .OrderByDescending(t => t.CreatedAt)
            .ToListAsync();
    }

    private async Task AddTodo()
    {
        if (string.IsNullOrWhiteSpace(newTitle)) return;

        using var context = await DbFactory.CreateDbContextAsync();
        context.TodoItems.Add(new TodoItem { Title = newTitle });
        await context.SaveChangesAsync();
        newTitle = string.Empty;
        await LoadTodosAsync();
    }

    private async Task DeleteTodo(int id)
    {
        using var context = await DbFactory.CreateDbContextAsync();
        var item = await context.TodoItems.FindAsync(id);
        if (item != null)
        {
            context.TodoItems.Remove(item);
            await context.SaveChangesAsync();
        }
        await LoadTodosAsync();
    }
}
코드 패턴 핵심 해설:
  • @inject IDbContextFactory<AppDbContext> DbFactory: 의존성 주입(DI) 컨테이너에서 팩토리를 주입받음
  • using var context = await DbFactory.CreateDbContextAsync(): 단기 수명 DbContext를 생성하고 using으로 자동 해제 보장
  • 조회·추가·삭제 각 작업마다 독립적인 DbContext 인스턴스를 사용
  • 데이터 변경 후 LoadTodosAsync()를 호출해 UI를 최신 상태로 갱신

 

 

⚖️ AddDbContext vs AddDbContextFactory 비교

 

항목AddDbContextAddDbContextFactory
DbContext 수명Scoped (요청 단위)수동 제어 (using 블록)
Blazor 적합성❌ 부적합✅ 공식 권장
동시성 안전위험 (공유 컨텍스트)안전 (독립 인스턴스)
메모리 관리누수 위험즉시 해제
컴포넌트 주입@inject AppDbContext@inject IDbContextFactory

⚠️ 자주 틀리는 것 / 주의사항

1. AddDbContext를 그대로 사용하는 실수

문제: builder.Services.AddDbContext<AppDbContext>(...)를 사용하면 DbContext가 컴포넌트 수명 동안 살아있어 메모리 누수와 동시성 문제가 발생합니다.

해결: AddDbContextFactory로 변경하고, 컴포넌트에서 IDbContextFactory<AppDbContext>를 주입받아 각 작업마다 새 컨텍스트를 생성합니다.

2. DbContext를 Dispose하지 않는 실수

// ❌ 잘못된 예 — DbContext가 메모리에 계속 남아있음
var context = await DbFactory.CreateDbContextAsync();
var items = await context.TodoItems.ToListAsync();

// ✅ 올바른 예 — using으로 블록 종료 시 자동 해제
using var context = await DbFactory.CreateDbContextAsync();
var items = await context.TodoItems.ToListAsync();

3. 동일한 DbContext로 병렬 작업 시도

EF Core의 DbContext는 스레드 안전하지 않습니다. 동일한 인스턴스로 동시에 여러 비동기 작업을 실행하면 InvalidOperationException이 발생합니다. IDbContextFactory 패턴을 쓰면 작업마다 독립 인스턴스를 사용하므로 이 문제를 원천적으로 방지합니다.

4. 모델 변경 후 마이그레이션 누락

엔티티에 속성을 추가하거나 변경한 후 마이그레이션을 실행하지 않으면 런타임에 데이터베이스 예외가 발생합니다. 모델을 변경할 때마다 반드시 순서대로 실행하세요:

dotnet ef migrations add [변경_내용_설명]
dotnet ef database update

5. 크로스 컨텍스트 엔티티 추적 오류

컨텍스트 A에서 조회한 엔티티를 컨텍스트 B로 업데이트하려 하면 변경 추적(Change Tracking) 충돌이 발생합니다. 수정 작업은 반드시 동일한 DbContext 인스턴스 내에서 조회→변경→저장을 완결해야 합니다.

// ✅ 올바른 수정 패턴
private async Task UpdateTodo(int id, string newTitle)
{
    using var context = await DbFactory.CreateDbContextAsync();
    var item = await context.TodoItems.FindAsync(id);  // 같은 컨텍스트로 조회
    if (item != null)
    {
        item.Title = newTitle;
        await context.SaveChangesAsync();              // 같은 컨텍스트로 저장
    }
}

🎯 마무리

EF Core와 IDbContextFactory 패턴은 Blazor Server 앱에서 데이터베이스를 다루는 표준적이고 안전한 방법입니다. SQLite로 시작해 동일한 코드 구조를 유지하면서 SQL Server나 PostgreSQL로 쉽게 전환할 수 있다는 점도 EF Core의 강점입니다. 마이그레이션 시스템 덕분에 스키마 변경도 안전하게 버전 관리할 수 있습니다.

✅ 핵심 정리
  • EF Core는 .NET 공식 ORM으로, C# 클래스(엔티티)와 DB 테이블을 자동 매핑합니다.
  • Blazor Server에서는 반드시 AddDbContextFactory를 사용해야 합니다 (AddDbContext 사용 금지).
  • 각 DB 작업마다 using var context = await DbFactory.CreateDbContextAsync()로 독립 컨텍스트를 생성하고 자동 해제합니다.
  • 마이그레이션(dotnet ef migrations adddotnet ef database update)으로 모델 변경을 DB에 반영합니다.
  • 수정 작업은 반드시 동일한 DbContext 인스턴스 내에서 조회→변경→저장을 완결합니다.
  • EF Core는 SQLite·SQL Server·PostgreSQL 등 다양한 DB를 동일한 코드 구조로 지원합니다.

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.