콘솔로 프로젝트를 생성한 후 Microsoft.EntityFrameworkCore.Sqlite 및 Microsoft.EntityFrameworkCore.Tools 패키지를 설치한 후
테이블이 될 모델과
| TodoInfo.cs
[Table(nameof(TodoInfo))]
[Index(nameof(IsComplete))]
[Index(nameof(TargetDate))]
public class TodoInfo
{
/// <summary>
/// 아이디 (PK)
/// </summary>
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// 내용
/// </summary>
public string Detail { get; set; } = "";
/// <summary>
/// 완료유무
/// </summary>
public bool IsComplete { get; set; }
/// <summary>
/// 삭제유무
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 목표일
/// </summary>
public DateOnly? TargetDate { get; set; }
/// <summary>
/// 완료일
/// </summary>
public DateOnly? CompletedDate { get; set; }
}
DbContext 코드를 생성합니다.
| TodoContext.cs
public class TodoContext : DbContext
{
public DbSet<TodoInfo> Todos { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=data.db");
}
}
패키지 관리자 콘솔에서 Add-Migration first를 해서 first라는 마이그레이션을 만들고 Update-Database를 하면 data.db가 생깁니다.
Db Browser for SQLite를 이용해 data.db를 살펴보면 다음처럼 TodoInfo라는 테이블이 잘 생성되었음을 확인할 수 있으며,
간단하게 다음 코드를 통해
using var db = new TodoContext();
for (var i = 0; i < 10000; i++)
{
var newTodo = new TodoInfo
{
Detail = $"간단한 EFCore 샘플 만들기 : {i + 1}",
TargetDate = new DateOnly(2022, 5, 13)
};
db.Todos.Add(newTodo);
}
db.SaveChanges();
보호된 키를 사용해보기 위해 ID 형식을 만들고 사용해보았으나 변환기가 없다는 예외로 동작하지 않습니다. 관련된 학습은 좀 더 나중에 해야 할 것 같네요.
...
[Key]
public TodoInfoId Id { get; set; }
...
public class TodoInfoId
{
public int Id { get; }
public TodoInfoId(int id) => Id = id;
}
System.InvalidOperationException: The 'TodoInfoId' property 'TodoInfo.Id' could not be mapped because the database provider does not support this type.
프로젝트 설정에서 nullable을 활성화 하면 참조형식을 할당하지 않으면 널 경고가 발생합니다.
신기하게 EF Core 7 미리보기 4에서는 DbContext의 DbSet 속성에서 널 관련 경고가 발생하지 않는데, EF Core 6에서는 발생하는 것으로 보아 이번 EF Core 7에서는 기존처럼 DbContext의 DbSet을 { get; set; } 으로 할당 없이 사용해도 경고를 표시하지 않도록 조치가 되는 것 같습니다.
EF Core 6에서 관련 경고를 없애러면 다음 처럼
...
public DbSet<Entity> Entities => Set<Entity>();
...
마이그레이션 도구를 이용해 EF Core 모델을 데이터베이스 스키마와 동기화 할 수 있습니다. EF Core를 사용하면 반드시 해야 하는데요, 데이터베이스에 반영되지 않으면 동작하지 않기 때문입니다.
EF Core는 dotnet CLI 도구 또는 패키지 관리자 콘솔 도구를 이용해 마이그레이션을 할 수 있습니다. 최근엔 dotnet CLI 도구를 사용하므로 도구를 설치하는 방법을 살펴봅시다.
dotnet tool install 명령어를 이용해 전역으로 설치할 수 있습니다.
dotnet tool install --global dotnet-ef
업데이트는 dotnet tool update를 통해 할 수 있습니다.
dotnet tool update --global dotnet-ef
이제는 개발에 필요한 설정이 간단해져서 너무 좋군요.
이후에 dotnet ef migrations add 명령을 통해 마이그레이션을 추가할 수 있는데, 마이그레이션을 추가하는 시점은 엔터티 또는 DB 컨텍스트가 변경되었을 때 수행하며 오류없이 마이그레이션이 추가되면 관련 마이그레이션 코드가 생성되고 dotnet ef database update를 통해 데이터베이스의 스키마에 반영할 수 있습니다.
마이그레이션을 잘못했을 경우 롤백해야 할 수 도 있습니다. dotnet ef migrations remove를 통해 할 수 있습니다.
EF Core는 비동기 API를 제공합니다. 비동기 작업은 async/await 구조 덕분에 이제는 상당히 사용하기 쉬워졌습니다. 데이터베이스에 데이터를 저장하거나 질의하는 수행은 약간의 처리 시간이 필요하므로 비동기 API를 이용하면 좀 더 유리합니다.
EF Core는 .NET 표준에 따라 모든 동기 I/O에 대한 비동기 I/O를 제공합니다. SaveChanges() 메소드의 비동기 버젼은 SaveChangedAsync()로 ~Async가 붙는 식입니다.
LINQ 명령어는 서버에서 평가되거나 클라이언트에서 평가됩니다. 서버에서 평가된다는 의미는 즉 DBMS의 쿼리로 해석된다는 것이고 클라이언트는 .NET의 API로 평가된다는 것인데, EF Core가 계속해서 버젼업 되면서 되도록이면 서버에서 평가되는 쿼리를 생성하는 추세입니다.
서버 및 클라이언트에서 평가되는 명령을 비동기로 처리하려면 다음의 코드처럼 사용해야 합니다.
EF Core의 비동기 API는 DBMS 제공자에 따라 여러 제한이 있다고 합니다. 가령 여러 병렬 작업을 하나의 DB 컨텍스틍데서 처리할 수 없습니다. 또한 경험상 DBMS 제공자에 따라 상이하게 동작하기도 했습니다. 이 경험은 EF Core 6 이전의 경험이라 지금은 아닐 수도 있겠네요. 다양한 테스트를 해보고 그 결과도 공유해 보겠습니다.
이후 관련된 모든 로그가 콘솔로 출력되게 됩니다.
### Microsoft.Extensions.Logging
EF Core는 `Microsoft.Extensions.Logging`의 로깅과 통합되기 때문에 ASP.NET Core의 로깅과 함께 사용할 수 있습니다.
https://docs.microsoft.com/en-us/ef/core/logging-events-diagnostics/extensions-logging?tabs=v3
### 이벤트
EF Core 5부터 사용할 수 있으며 이벤트 방식으로 동작을 관찰 할 수 있습니다. 자세한 것은 아래 링크를 통해 확인할 수 있습니다.
https://docs.microsoft.com/en-us/ef/core/logging-events-diagnostics/events
### 차단
EF Core 5부터 사용할 수 있으며 작업을 가로채서 수정 또는 억제할 수 있는 방법을 제공합니다.
자세한 내용은 아래 링크를 참조하세요.
https://docs.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors
### 진단 수신기
.NET은 진단 수신기(DiagnosticListener)를 이용해 여러 동작을 청취할 수 있고 EF Core의 동작또한 청취할 수 있습니다.
아래처럼 유사하게 Observer를 구현한 후,
```csharp
public class DiagnosticObserver : IObserver<DiagnosticListener>
{
public void OnCompleted()
=> throw new NotImplementedException();
public void OnError(Exception error)
=> throw new NotImplementedException();
public void OnNext(DiagnosticListener value)
{
if (value.Name == DbLoggerCategory.Name) // "Microsoft.EntityFrameworkCore"
{
value.Subscribe(new KeyValueObserver());
}
}
}