EF Core 6 배우기 - 3. DB 컨텍스트

지금까지 EF Core의 개발 환경 구성 및 엔터티에 대해 간략히 살펴봤습니다. 좀 더 자세한 내용은 마이크로소프트 EF Core 문서를 통해 살펴볼 수 있습니다.

오늘은 DB 컨텍스트를 구성하여 간단한 할 일 기능을 구현하려고 합니다.

할 일 개체관계 모델링(ERD)

ORM에서도 엔터티간의 관계가 중요하므로 여전히 ERD를 작성하는 것은 중요합니다. 그래서 먼저 할 일에 대한 ERD를 작성하는 것으로 시작합시다. ERD는 요구사항을 수집하여 논리 모델을 먼저 완성한 후 이것을 이용해 실제 데이터베이스에 적용할 물리 모델을 완성 하는 것으로 전개하는데요, ERD를 이용해 만든 할 일 논리 모델은 다음과 같습니다.

이렇게 사용자정보할일정보의 비식별관계를 이용해 1:N의 관계를 표현할 수 있습니다. 이것을 식별관계로도 표현할 수 있는데요,

엔터티와 엔터티간 관계가 밀접할 경우 이렇게 식별 관계로 표현할 수 있습니다. 위의 예시의 경우 할일정보는 복합키를 가지게 됩니다. 저는 비식별관계를 이용해 계속 전개하도록 할께요.

논리 모델이 끝나면 물리 모델을 전개합니다. 대략 다음 모습이 되는데요,

이것을 그대로 엔터티 클래스로 전환해봅시다.

여기서 문자 길이는 엔터티 속성에 MaxLength 특성을 적용해 지정할 수 있지만 본 글에서는 지정하지 않고 진행 합니다.

엔터티

| UserInfo.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TodoApp.Entities;

[Table(nameof(UserInfo))]
public record UserInfo
{
    [Key]
    public string UserId { get; set; } = default!;
    public string UserName { get; set; } = default!;

    public virtual ICollection<TodoInfo> Todos { get; } = default!;
}

| TodoInfo.cs


using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TodoApp.Entities;

[Table(nameof(TodoInfo))]
public record TodoInfo
{
    [Key]
    public int Seq { get; set; }

    public DateOnly? TodoDate { get; set; }
    public DateOnly? CompleteDate { get; set; }
    public bool IsComplete { get; set; }
    public string? Memo { get; set; }
    public bool IsDel { get; set; }

    [ForeignKey(nameof(User))]
    public string UserId { get; set; } = default!;
    
    public virtual UserInfo User { get; set; } = default!;
}

여기서 UserInfoTodosTodoInfoUser는 탐색 속성이 되며 나중에 질의 시 Include()를 통해 조인할 수 있게 됩니다.

DB 컨텍스트

EF Core에서는 엔터티DB 컨텍스트를 이용해 모델링을 합니다. DB 컨텍스트는 다음의 형태로 만들 수 있습니다.

| TodoContext.cs

using Microsoft.EntityFrameworkCore;

using TodoApp.Entities;

namespace TodoApp.DbContexts;

public class TodoContext : DbContext
{
    public DbSet<UserInfo> Users => Set<UserInfo>();
    public DbSet<TodoInfo> Todos => Set<TodoInfo>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // "database.db" 파일로 SQLite 사용
        optionsBuilder.UseSqlite("Data Source=database.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Todo의 키인 `Seq`는 자동 증가로 설정
        modelBuilder.Entity<TodoInfo>()
            .Property(x => x.Seq)
            .ValueGeneratedOnAdd();
    }
}

OnConfiguring()를 통해 SQLite 데이터베이스 파일명을 설정하고 OnModelCreating()을 통해 TodoInfo의 키인 Seq 속성의 값을 자동 증가하도록 설정하고 있습니다. 특성을 이용해 엔터티 및 속성의 특성을 설정 할 수 있지만 생성시 다양한 설정은 OnModelCreating()에서 제공하는 Fluent API` 로 상세히 설정할 수 있습니다.

DBSet<TEntity>는 LINQ를 이용해 엔터티의 인스턴스를 쿼리하고 저장할 수 있게 해줍니다. 프로젝트에 <Nullable> 설정이 되어 있다면 DbSet<UserInfo> { get; set; } 대신 DbSet<UserInfo> Users => Set<UserInfo>()로 널 관련 경고를 없앨 수 있습니다.

마이그레이션 및 데이터베이스 업데이트

dotnet CLI 도구가 잘 설치되어 있다면 다음의 명령을 통해 마이그레이션 할 수 있습니다.

dotnet ef migrations add first

first의 이름으로 마이그레이션을 수행 하며 엔터티DB 컨텍스트를 수정하여 데이터베이스에 반영해야 할 때 적절한 이름으로 마이그레이션을 수행할 수 있습니다.

정상적으로 마이그레이션이 마이그레이션 내용을 데이터베이스에 업데이트 할 수 있습니다.

dotnet ef database update

데이터베이스에 업데이트가 정상 수행되면 다음 처럼 데이터베이스에 반영이 됩니다.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655024283825/zzL7hjwzH.png align=“left”)

쿼리

이제 다양한 쿼리 테스트를 해봅시다. 쿼리는 DB 컨텍스트를 통해 이뤄지므로 먼저 DB 컨텍스트 인스턴스를 생성해야 합니다.

using Microsoft.EntityFrameworkCore;

using TodoApp.DbContexts;

using var c = new TodoContext();

사용자 추가

그런 후 dimohytest 계정을 생성해봅시다.

c.Users.Add(new()
{
    UserId = "dimohy",
    UserName = "디모이"
});

c.Users.Add(new()
{
    UserId = "test",
    UserName = "테스트"
});

c.SaveChanges();

데이터베이스에 다음과 같이 저장되었습니다.

실제 쿼리를 해봅시다. Users의 모든 사용자를 조회할 것이므로 코드는 다음과 같습니다.

foreach (var user in c.Users)
{
    Console.WriteLine(user);
}

| 결과

UserInfo { UserId = dimohy, UserName = 디모이, Todos =  }
UserInfo { UserId = test, UserName = 테스트, Todos =  }

할 일 추가

이제 할 일을 추가해 봅시다.

c.Todos.Add(new()
{
    UserId = "dimohy",

    TodoDate = new DateOnly(2022, 6, 12),
    Memo = "`EF Core 6 배우기 - 3. DB 컨텍스트` 완료",
});

c.Todos.Add(new()
{
    UserId = "test",

    TodoDate = new DateOnly(2022, 6, 12),
    Memo = "삽입 테스트",
});

c.SaveChanges();

데이터베이스에 다음과 같이 저장되었습니다.

사용자로 할 일 조회

EF Core는 탐색 속성을 통해 엔터티간 관계 조인을 통해 쿼리를 할 수 있습니다. 사용자로 할 일을 조회해봅시다.

var dimohy = c.Users.Include(x => x.Todos).First(x => x.UserId == "dimohy");
Console.WriteLine(dimohy);

foreach (var todo in dimohy.Todos)
{
    Console.WriteLine(todo);
}

| 결과

UserInfo { UserId = dimohy, UserName = 디모이, Todos = System.Collections.Generic.HashSet`1[TodoApp.Entities.TodoInfo] }
TodoInfo { Seq = 1, TodoDate = 2022-06-12, CompleteDate = , IsComplete = False, Memo = `EF Core 6 배우기 - 3. DB 컨텍스 트` 완료, IsDel = False, UserId = dimohy, User = UserInfo { UserId = dimohy, UserName = 디모이, Todos = System.Collections.Generic.HashSet`1[TodoApp.Entities.TodoInfo] } }

할 일로 사용자 조회

반대로 할 일에서 사용자를 확인해봅시다.

ar todos = c.Todos.Where(x => x.UserId == "dimohy").Include(x => x.User);
foreach (var todo in todos)
{
    Console.WriteLine(todo);
}

| 결과

TodoInfo { Seq = 1, TodoDate = 2022-06-12, CompleteDate = , IsComplete = False, Memo = `EF Core 6 배우기 - 3. DB 컨텍스 트` 완료, IsDel = False, UserId = dimohy, User = UserInfo { UserId = dimohy, UserName = 디모이, Todos = System.Collections.Generic.HashSet`1[TodoApp.Entities.TodoInfo] } }

할 일 속성 변경 및 데이터베이스 반영

var testTodo = c.Todos.First(x => x.UserId == "test");
testTodo.IsComplete = true;
testTodo.CompleteDate = DateOnly.FromDateTime(DateTime.Now);
c.SaveChanges();

데이터베이스에 다음과 같이 반영되었습니다.

정리

EF Core에서 DB 컨텍스트 및 엔터티 클래스를 이용해 데이터베이스 생성 및 쿼리를 살펴보았습니다. 다음 시간에는 마이그레이션에 대해 좀 더 알아보도록 합시다.

소스코드

좋아요 8