데이터 시드
데이터 시딩은 초기 데이터 세트로 데이터베이스를 채우는 프로세스입니다.
EF Core에서 이 작업을 수행할 수 있는 방법에는 여러 가지가 있습니다.
구성 옵션 UseSeeding
및 UseAsyncSeeding
메서드
초기 데이터로 데이터베이스를 시드하는 편리한 방법을 제공하는 EF 9가 도입되었습니다 UseSeeding
UseAsyncSeeding
. 이러한 메서드는 사용자 지정 초기화 논리를 사용하는 환경을 개선하는 것을 목표로 합니다(아래 설명). 모든 데이터 시드 코드를 배치할 수 있는 하나의 명확한 위치를 제공합니다. 또한 내부 코드 UseSeeding
와 UseAsyncSeeding
메서드는 동시성 문제를 방지하기 위해 마이그레이션 잠금 메커니즘에 의해 보호됩니다.
새 시드 메서드는 모델 변경 내용이 없고 마이그레이션이 적용되지 않은 경우에도 작업 Migrate
및 dotnet ef database update
명령의 EnsureCreated
일부로 호출됩니다.
팁
EF Core로 작업할 때 초기 데이터로 데이터베이스를 시드하는 데 권장되는 방법 및 UseAsyncSeeding
사용 UseSeeding
방법입니다.
이러한 메서드는 옵션 구성 단계에서 설정할 수 있습니다. 예를 들어 다음과 같습니다.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
.UseSeeding((context, _) =>
{
var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
context.SaveChanges();
}
})
.UseAsyncSeeding(async (context, _, cancellationToken) =>
{
var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
await context.SaveChangesAsync(cancellationToken);
}
});
참고 항목
UseSeeding
는 메서드에서 EnsureCreated
호출되고 UseAsyncSeeding
메서드에서 EnsureCreatedAsync
호출됩니다. 이 기능을 사용하는 경우 EF를 사용하는 코드가 비동기인 경우에도 유사한 논리를 사용하여 메서드와 UseAsyncSeeding
메서드를 모두 UseSeeding
구현하는 것이 좋습니다. EF Core 도구는 현재 메서드의 동기 버전을 사용하며 메서드가 구현되지 않은 경우 데이터베이스를 UseSeeding
올바르게 시드하지 않습니다.
사용자 지정 초기화 논리
데이터 시드를 수행하는 간단하고 강력한 방법은 기본 애플리케이션 논리가 실행을 시작하기 전에 DbContext.SaveChanges()
를 사용하는 것입니다. 사용 UseSeeding
하는 것이 좋습니다 하 고 UseAsyncSeeding
그 목적을 위해, 그러나 경우에 따라 이러한 메서드를 사용 하 여 좋은 솔루션. 예제 시나리오는 시드 시 한 트랜잭션에서 서로 다른 두 컨텍스트를 사용해야 하는 경우입니다. 다음은 애플리케이션에서 직접 사용자 지정 초기화를 수행하는 코드 샘플입니다.
using (var context = new DataSeedingContext())
{
context.Database.EnsureCreated();
var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
if (testBlog == null)
{
context.Blogs.Add(new Blog { Url = "http://test.com" });
context.SaveChanges();
}
}
Warning
여러 인스턴스가 실행 중일 때 동시성 문제가 발생할 수 있으며 앱에 데이터베이스 스키마를 수정할 수 있는 권한이 있어야 하므로 시드 코드는 정상적인 앱 실행의 일부가 되어서는 안 됩니다.
배포의 제약 조건에 따라 초기화 코드를 다음과 같은 다양한 방법으로 실행할 수 있습니다.
- 로컬로 초기화 앱 실행
- 기본 앱을 사용하여 초기화 앱을 배포하고, 초기화 루틴을 호출하며, 초기화 앱을 사용하지 않도록 설정하거나 제거합니다.
일반적으로 게시 프로필을 사용하여 자동화할 수 있습니다.
관리되는 데이터 모델
데이터도 모델 구성의 일부로 엔터티 형식과 연결할 수 있습니다. 그런 다음 EF Core 마이그레이션은 데이터베이스를 새 버전의 모델로 업그레이드할 때 적용해야 하는 삽입, 업데이트 또는 삭제 작업을 자동으로 계산할 수 있습니다.
Warning
마이그레이션은 관리되는 데이터를 원하는 상태로 가져오기 위해 수행해야 하는 작업을 결정할 때만 모델 변경을 고려합니다. 따라서 마이그레이션 외부에서 수행되는 데이터를 변경하면 손실되거나 오류가 발생할 수 있습니다.
예를 들어 다음에서 관리되는 데이터를 구성합니다.Country
OnModelCreating
modelBuilder.Entity<Country>(b =>
{
b.Property(x => x.Name).IsRequired();
b.HasData(
new Country { CountryId = 1, Name = "USA" },
new Country { CountryId = 2, Name = "Canada" },
new Country { CountryId = 3, Name = "Mexico" });
});
관계가 있는 엔터티를 추가하려면 외래 키 값을 지정해야 합니다.
modelBuilder.Entity<City>().HasData(
new City { Id = 1, Name = "Seattle", LocatedInId = 1 },
new City { Id = 2, Name = "Vancouver", LocatedInId = 2 },
new City { Id = 3, Name = "Mexico City", LocatedInId = 3 },
new City { Id = 4, Name = "Puebla", LocatedInId = 3 });
다대다 탐색에 대한 데이터를 관리하는 경우 조인 엔터티를 명시적으로 구성해야 합니다. 엔터티 형식에 섀도 상태의 속성(예: LanguageCountry
아래 조인 엔터티)이 있는 경우 익명 클래스를 사용하여 값을 제공할 수 있습니다.
modelBuilder.Entity<Language>(b =>
{
b.HasData(
new Language { Id = 1, Name = "English" },
new Language { Id = 2, Name = "French" },
new Language { Id = 3, Name = "Spanish" });
b.HasMany(x => x.UsedIn)
.WithMany(x => x.OfficialLanguages)
.UsingEntity(
"LanguageCountry",
r => r.HasOne(typeof(Country)).WithMany().HasForeignKey("CountryId").HasPrincipalKey(nameof(Country.CountryId)),
l => l.HasOne(typeof(Language)).WithMany().HasForeignKey("LanguageId").HasPrincipalKey(nameof(Language.Id)),
je =>
{
je.HasKey("LanguageId", "CountryId");
je.HasData(
new { LanguageId = 1, CountryId = 2 },
new { LanguageId = 2, CountryId = 2 },
new { LanguageId = 3, CountryId = 3 });
});
});
소유 엔터티 형식은 비슷한 방식으로 구성할 수 있습니다.
modelBuilder.Entity<Language>().OwnsOne(p => p.Details).HasData(
new { LanguageId = 1, Phonetic = false, Tonal = false, PhonemesCount = 44 },
new { LanguageId = 2, Phonetic = false, Tonal = false, PhonemesCount = 36 },
new { LanguageId = 3, Phonetic = true, Tonal = false, PhonemesCount = 24 });
자세한 컨텍스트는 전체 샘플 프로젝트를 참조하세요.
데이터가 모델에 추가되면 마이그레이션을 통해 변경 내용을 적용해야 합니다.
팁
자동화된 배포의 일부로 마이그레이션을 적용해야 하는 경우 실행 전에 미리 볼 수 있는 SQL 스크립트를 만들 수 있습니다.
또는 테스트 데이터베이스 또는 메모리 내 공급자 또는 비관계형 데이터베이스를 사용하는 경우와 같이 관리되는 데이터가 포함된 새 데이터베이스를 만드는 데 사용할 context.Database.EnsureCreated()
수 있습니다. 데이터베이스가 이미 있는 EnsureCreated()
경우 데이터베이스의 스키마나 관리되는 데이터를 업데이트하지 않습니다. 관계형 데이터베이스의 경우 마이그레이션을 사용하려면 EnsureCreated()
를 호출해서는 안 됩니다.
참고 항목
"데이터 시드"라고 하는 데 사용되는 메서드를 사용하여 HasData
데이터베이스를 채웁니다. 이 명명은 기능이 여러 제한 사항을 가지며 특정 유형의 데이터에만 적합하기 때문에 잘못된 기대치를 설정합니다. 그래서 이름을 "모델 관리 데이터"로 바꾸기로 결정했습니다. UseSeeding
및 UseAsyncSeeding
메서드는 범용 데이터 시드에 사용해야 합니다.
모델 관리 데이터의 제한 사항
이 유형의 데이터는 마이그레이션을 통해 관리되며 데이터베이스에 이미 있는 데이터를 업데이트하는 스크립트는 데이터베이스에 연결하지 않고 생성해야 합니다. 그러면 다음과 같은 몇 가지 제한 사항이 적용됩니다.
- 기본 키 값은 일반적으로 데이터베이스에서 생성되더라도 지정해야 합니다. 이는 마이그레이션 간의 데이터 변경 내용을 검색하는 데 사용됩니다.
- 기본 키가 어떤 방식으로든 변경되면 이전에 삽입된 데이터가 제거됩니다.
따라서 이 기능은 마이그레이션 외부에서 변경될 것으로 예상되지 않고 데이터베이스의 다른 항목(예: 우편 번호)에 의존하지 않는 정적 데이터에 가장 유용합니다.
시나리오에 다음이 포함된 경우 첫 번째 섹션에 설명된 방법 및 UseAsyncSeeding
사용하는 UseSeeding
것이 좋습니다.
- 테스트를 위한 임시 데이터
- 데이터베이스 상태에 따라 달라지는 데이터
- 대용량 데이터(시드 데이터는 마이그레이션 스냅샷에서 캡처되고 대용량 데이터는 빠르게 거대한 파일 및 성능 저하로 이어질 수 있음)
- 대체 키를 ID로 사용하는 엔터티를 포함하여 데이터베이스에서 생성해야 하는 키 값이 필요한 데이터
- 일부 암호 해시와 같이 사용자 지정 변환이 필요한 데이터(값 변환에 의해 처리되지 않음)
- ASP.NET Core ID 역할 및 사용자 만들기와 같은 외부 API에 대한 호출이 필요한 데이터
수동 마이그레이션 사용자 지정
마이그레이션이 추가되면 HasData
에 지정된 데이터에 대한 변경 내용이 InsertData()
, UpdateData()
, DeleteData()
에 대한 호출로 변환됩니다. HasData
의 몇 가지 제한 사항을 해결하는 한 가지 방법은 이러한 호출 또는 사용자 지정 작업을 마이그레이션에 수동으로 추가하는 것입니다.
migrationBuilder.InsertData(
table: "Countries",
columns: new[] { "CountryId", "Name" },
values: new object[,]
{
{ 1, "USA" },
{ 2, "Canada" },
{ 3, "Mexico" }
});
migrationBuilder.InsertData(
table: "Languages",
columns: new[] { "Id", "Name", "Details_PhonemesCount", "Details_Phonetic", "Details_Tonal" },
values: new object[,]
{
{ 1, "English", 44, false, false },
{ 2, "French", 36, false, false },
{ 3, "Spanish", 24, true, false }
});
migrationBuilder.InsertData(
table: "Cites",
columns: new[] { "Id", "LocatedInId", "Name" },
values: new object[,]
{
{ 1, 1, "Seattle" },
{ 2, 2, "Vancouver" },
{ 3, 3, "Mexico City" },
{ 4, 3, "Puebla" }
});
migrationBuilder.InsertData(
table: "LanguageCountry",
columns: new[] { "CountryId", "LanguageId" },
values: new object[,]
{
{ 2, 1 },
{ 2, 2 },
{ 3, 3 }
});
.NET