-
EF Core Code First Entity Model을 작업하는 방법프로그래밍 2024. 9. 24. 14:46
EF Core를 사용하는 경우 개발자는 Entity에 대한 Model작업을 해야 한다.
여기서는 EF Core Code First를 수행하는 방법과 도움이 되는 요소를 설명하고자 한다.
Code First시에는 두 가지 방법이 존재한다.
첫째, Attribute를 사용하는 방법.
둘째, ModelBuilder를 사용하는 방법.
필자는 처음 EF를 접했을 때 첫째 방법으로만 개발을 진행하였고, Attribute로 설정할 경우 몇 가지 어려움이 있었다.
첫 번째 방법의 문제는 Attribute속성을 적용할 경우 에러에 대한 핸들링이다.
Attribute로 선언할 경우 SaveChange에서 오류가 발생하는 것이 아닌 Add, Update, Delete에서 문제가 발생한다.
주로 Add, Delete 에서 문제가 발생하겠다.
다만, Attribute로 작업할 경우 상속을 사용하여 작업할 수 있으므로 Table 클래스와 기본 클래스를 분리해서 관리할 수 있고 중첩된 코드를 작성하지 않아도 된다.
그럼에도 불구하고 현재는 첫 번째 방법을 권장하지 않는다.
두 번째 방법은 ModelBuilder를 사용하는 것이다.
ModelBuilder란 Entity-Table-에 대한 속성을 직접 코드로 정의하는 것이다.
상속을 할 수 없어 Attribute에 이점을 누리기 어렵고 Entity 정보로 Table 구조를 명확히 알 수 없어 명확성이 떨어진다.
그럼에도 ModelBuilder를 추천한다.
이유는 아래와 같다.
1. Add, Update, Delete 등의 함수를 실행할 경우 Key 등의 Attribute 등의 오류를 줄일 수 있다.
2. 1:1, 1-N 등의 설정시 Attribute나 EF에서 지원하는 자동 PK-FK 설정등을 인식하는 범위로 할 수 있다.
3. Attribute에서 지원하지 않는 일부 설정을 할 수 있다.
아래의 코드를 보자.
public class Profile : EntityBase { public string Id { get; set; } public string UserName { get; set; } public float Height { get; set; } public float Weight { get; set; } public short Age { get; set; } public string Gender { get; set; } public string ProfileImageUrl { get; set; } public string PhoneNumber { get; set; } public string Token { get; set; } public string RefreshToken { get; set; } public DateTime Expire { get; set; } public bool IsLogin { get; set; } public string IpAddr { get; set; } public string DeviceId { get; set; } public string DeviceType { get; set; } public bool IsMaster { get; set; } public string Language { get; set; } = "ko-KR"; public string TimeZone { get; set; } = "Korea Standard Time"; public Guid AccountId { get; set; } public virtual Account Accounts { get; set; } public virtual ProfileSetting ProfileSetting { get; set; } public virtual ICollection<ProfileGroup> Groups { get; set; } }
프로필 정보 Entity이다.
아래은 해당 Entity의 ModelBuilder 코드이다.
public class ProfileModelBuilder : ModelBuilderBase<Profile> { public override void Build(ModelBuilder builder) { builder.Entity<Profile>(e => { e.ToTable(nameof(Profile), SchemaConst.Dbo); e.HasKey(m => m.Id); e.Property(m => m.Id) .HasMaxLength(26); e.Property(m => m.UserName) .HasMaxLength(100) .IsRequired(); e.Property(m => m.Height) .IsRequired(); e.Property(m => m.Weight) .IsRequired(); e.Property(m => m.Age) .IsRequired(); e.Property(m => m.Gender) .HasMaxLength(6) ; e.Property(m => m.ProfileImageUrl) .HasMaxLength(1000) ; e.Property(m => m.PhoneNumber) .HasMaxLength(100) ; e.Property(m => m.Token) .HasMaxLength(4000) ; e.Property(m => m.RefreshToken) .HasMaxLength(450) ; e.Property(m => m.IpAddr) .HasMaxLength(39) ; e.Property(m => m.DeviceId) .HasMaxLength(200) ; e.Property(m => m.DeviceType) .HasMaxLength(20) ; e.Property(m => m.Language) .HasMaxLength(50) ; e.Property(m => m.TimeZone) .HasMaxLength(20) ; e.HasOne(m => m.Accounts) .WithMany(m => m.Profiles) .HasForeignKey(m => m.AccountId) .OnDelete(DeleteBehavior.Restrict) ; }); base.Build(builder); } }
위와 같이 정의할 수 있다.
Attribute로 설정할 수 있는 부분 및 PK-FK에 대한 지정도 코드로 하고 있다.
위에서 언급한 Attribute의 이점을 얻기 위해서 필자는 MobelBuilderBase라는 클래스를 작성했다.
코드는 아래와 같다.
public interface IModelBuilderBase { void Build(ModelBuilder builder); } public abstract class ModelBuilderBase<T> : IModelBuilderBase where T : EntityBase { public virtual void Build(ModelBuilder builder) { builder.Entity<T>(e => { e.Property(m => m.CreateBy) .HasMaxLength(36) .IsRequired() ; e.Property(m => m.ModifiedBy) .HasMaxLength(36) ; e.Property(m => m.FId) .HasMaxLength(52) ; e.Property(m => m.IsActive) .HasDefaultValue(true); }); } }
IModelBuilderBase를 상속받는 ModelBuilderBase는 제너릭 T를 기반으로 ModelBuilder 작업을 수행하도록 하고 있다.
또한 ModelBuilderBase는 기본적으로 EntityBase를 대상으로 함으로 Attribute를 사용하는 상속 방법의 이점을 얻도록 하였다.
그렇다면 실제 수행하는 부분의 코드는 아래와 같다.
protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); var types = Assembly.GetExecutingAssembly().GetTypes() .Where(t => typeof(IModelBuilderBase).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); foreach (var type in types) { var modelBuilderInstance = (IModelBuilderBase)Activator.CreateInstance(type); modelBuilderInstance!.Build(builder); } }
위와 같이 Reflection을 사용하여 타입을 동적으로 생성, 처리하고 있다.
따라서, 위와 같은 형태로 작업할 경우 Attribute를 사용하는 방법의 이점을 누리면서 제한적인 Attribute의 지원 사항을 떠나 보다 유연하게 작업할 수 있는 방법이 되겠다.
사실, DbContext를 도메인별로 모두 분리하여 만드는 것도 분리를 하는 방법이겠지만 하나의 DbContext를 유지한 상태로 각각의 Entity Modeling을 분리하고 모델링할 경우 첫 번째와 두 번째 방법 모두에 장점을 가져가려는 노력의 결과겠다.
모두 해피 코딩.
'프로그래밍' 카테고리의 다른 글
멀티스레드 환경에서의 데이터 전송에 대한 안정성 향상 방법 (3) 2024.10.10 테이블 캐시를 관리하는 방법 (0) 2024.10.04 python + .net core integration, use pythonnet (0) 2024.07.26 Node 앱을 위한 타입스크립트 설정 (0) 2024.03.26 Javascript 최적화 (0) 2024.03.26