EF Core를 사용하면서 공유할 만한 내용을 계속 뎃글로 달겠습니다.
record를 이용한 일반적인 엔티티 형태
public record Slogger(string Id, string Name, string NickName, string Email, bool ConfirmEmail)
{
[Key]
public string Id { get; init; } = Id;
[MaxLength(32)]
public string Name { get; init; } = Name;
[MaxLength(32)]
public string NickName { get; init; } = NickName;
[MaxLength(64)]
public string Email { get; init; } = Email;
public bool ConfirmEmail { get; init; } = ConfirmEmail;
}
소스코드에서 데이터베이스 마이그레이션을 하려면
dbContext.Database.Migration()
컴럼이 추가/삭제 되거나, FK가 변경되거나 데이터형이 변경되는 등 이럴때 데이터도 자동으로 마이그레이션 해주면 좋겠지만 데이터는 수동으로 마이그레이션 해줘야 하는 상황이 종종 생깁니다. 이것은 비단 코드 방식 뿐만 아니라 커맨드 방식도 마찬가지입니다.
(Add-Migration 후 데이터를 수동으로 마이그레이션 하지 않으면 Update-Database 가 실패하는 경우가 종종 생김)
그래서 실제 현업에서는 저 Migration()를 쓸일이 그닥 없게 됩니다. Microsoft문서에서도 저 방법 보다는 SQL 스크립트를 생성해서 마이그레이션 하는것을 권장합니다.
- 프로덕션 환경에서이 방법을 사용 하기 전에 신중 하 게 고려해 야 합니다. 환경에서이 배포 전략의 단순성은 생성 되는 문제에 의해 얻게 됩니다. 대신 마이그레이션에서 SQL 스크립트를 생성 하는 것이 좋습니다.
정보를 저장하거나 변경할 때 상태에 따른 다양한 동작이 필요하는데요,
EF Core는 변경 추적 기능이 있어서 SaveChanged를 호출할 때 자연스럽게 변경내용이 적용됩니다.
하지만 대부분의 경우, API 서비스를 구성하게 되면서 질의 지점과 변경 지점의 계층이 분리되기 때문에 DbContext로 계속 변경지점을 추척할 수 없습니다.
그래서 데이터를 변경하였을 때 감지하여 적절하게 Add 또는 Update를 해주면 편리할 텐데요,
record와 결합하여 다음과 같이 사용할 수 있습니다.
public abstract record BaseRecord : ISettableBaseRecord
{
[NotMapped]
public bool IsStored { get; private set; }
[NotMapped]
public bool IsChanged { get; private set; }
public BaseRecord(BaseRecord record)
{
IsStored = record.IsStored;
IsChanged = true;
}
void ISettableBaseRecord.SetSaved()
{
IsStored = true;
IsChanged = false;
}
}
public interface ISettableBaseRecord
{
void SetSaved();
}
public record Slogger(string Id, string Name, string NickName, string Email, bool ConfirmEmail) : BaseRecord
{
[Key]
public string Id { get; init; } = Id;
[MaxLength(32)]
public string Name { get; init; } = Name;
[MaxLength(32)]
public string NickName { get; init; } = NickName;
[MaxLength(64)]
public string Email { get; init; } = Email;
public bool ConfirmEmail { get; init; } = ConfirmEmail;
}
이후 BaseRecord를 사용받아 엔티티들을 구현하면, 엔티티 인스턴스마다 상태관리가 가능해집니다.
var slogger = new Slogger("dimohy", "정세일", "디모이", "dimohy@email.com", false);
// 아직 DB에 저장하지 않은 상태
Console.WriteLine(slogger);
// 저장 후
(slogger as ISettableBaseRecord).SetSaved();
// 저장 후 상태
Console.WriteLine(slogger);
// 수정 발생
var slogger2 = slogger with { Name = "정세팔" };
Console.WriteLine(slogger2);
출력결과
Slogger { IsStored = False, IsChanged = False, Id = dimohy, Name = 정세일, NickName = 디모이, Email = dimohy@email.com, ConfirmEmail = False }
Slogger { IsStored = True, IsChanged = False, Id = dimohy, Name = 정세일, NickName = 디모이, Email = dimohy@email.com, ConfirmEmail = False }
Slogger { IsStored = True, IsChanged = True, Id = dimohy, Name = 정세팔, NickName = 디모이, Email = dimohy@email.com, ConfirmEmail = False }
EF Core에 C# 9 record를 사용하려는 사람이 아직은 많지는 않을 텐데요
현재는 인자에 KeyAttribute등의 attribute를 부여할 수 없기 때문에 record recordName([Key] string Id, ...) 형태를 쓸 수 가 없습니다. 인자에 [Key]등의 Attribute를 부여할 수 있게 EF Core가 변화가 필요할 것 같습니다.
거기다가 <Nullable>enable</Nullable>을 할 경우 기존 방식처럼 EF Core를 써왔을 경우 엔티티 속성에 수많은 경고가 뜨는걸 볼 수 있습니다.
디자인 타임에서 DbContext 생성
Add-Migration, Update-Database 명령을 사용하려면, 디자인타임에 ConnectionString을 지정해서 DB에 접속 가능해야 합니다.
다음과 같은 DesignTimeDbContextFactory를 구현하면 디자인 타임에서 DbContext 인스턴스를 생성하여 마이그레이션 등의 처리를 할 수 있습니다.
public class SlogContextFactory : IDesignTimeDbContextFactory<SlogContext>
{
public SlogContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<SlogContext>();
var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var connectionString = config["ConnectionStrings:postgresql"];
optionsBuilder.UseNpgsql(connectionString);
return new(optionsBuilder.Options);
}
}