EF Core 6.0의 호환성이 손상되는 변경

EF Core 6.0으로 업데이트되는 기존 애플리케이션의 호환성이 손상될 수 있는 API 및 동작 변경은 다음과 같습니다.

대상 프레임워크

EF Core 6.0은 .NET 6을 대상으로 합니다. 이전 .NET, .NET Core 및 .NET Framework 버전을 대상으로 하는 애플리케이션은 EF Core 6.0을 사용하려면 .NET 6을 대상으로 해야 합니다.

요약

주요 변경 내용 영향
필수 속성 없이 테이블을 공유하는 중첩된 선택적 종속 항목을 저장할 수 없음 높음
이제 소유된 엔터티의 소유자를 변경하면 예외가 throw됨 중간
Azure Cosmos DB: 관련 엔터티 형식이 소유된 것으로 검색됩니다. 중간
SQLite: 연결이 풀링된 경우 중간
매핑된 조인 엔터티가 없는 다대다 관계가 이제 스캐폴드됨 중간
DeleteBehavior 및 ON DELETE 값 간의 매핑이 정리됨 낮음
메모리 내 데이터베이스의 유효성 검사에 필요한 속성이 null을 포함하지 않음 낮음
컬렉션에 조인할 때 마지막 ORDER BY가 제거됨 낮음
DbSet은 IAsyncEnumerable을 더 이상 구현하지 않음 낮음
TVF 반환 엔터티 형식도 기본적으로 테이블에 매핑됨 낮음
CHECK 제약 조건 이름 고유성이 이제 유효성 검사됨 낮음
IReadOnly 메타데이터 인터페이스가 추가되고 확장 메서드가 제거됨 낮음
IExecutionStrategy는 이제 singleton 서비스입니다. 낮음
SQL Server: 더 많은 오류가 일시적으로 간주됨 낮음
Azure Cosmos DB: 더 많은 문자가 'id' 값으로 이스케이프됩니다. 낮음
일부 싱글톤 서비스가 이제 범위가 지정됨 낮음*
서비스를 추가하거나 바꾸는 확장의 새 캐싱 API 낮음*
새 스냅샷 및 디자인 타임 모델 초기화 프로시저 낮음
OwnedNavigationBuilder.HasIndex가 이제 다른 형식을 반환함 낮음
DbFunctionBuilder.HasSchema(null)[DbFunction(Schema = "schema")]을 재정의함 낮음
미리 초기화된 탐색이 데이터베이스 쿼리의 값으로 재정의됨 낮음
쿼리할 때 데이터베이스의 알 수 없는 열거형 문자열 값이 열거형 기본값으로 변환되지 않음 낮음
DbFunctionBuilder.HasTranslation이 이제 IReadOnlyCollection이 아니라 IReadOnlyList로서 함수 인수를 제공함 낮음
엔터티가 테이블 반환 함수에 매핑될 때 기본 테이블 매핑이 제거되지 않음 낮음
dotnet-ef는 .NET 6을 대상으로 함 낮음
디자인 타임 캐싱을 처리하려면 IModelCacheKeyFactory 구현을 업데이트해야 할 수 있습니다. 낮음
NavigationBaseIncludeIgnored는 이제 기본적으로 오류입니다. 낮음

* 데이터베이스 공급자 및 확장 작성자가 이러한 변경 내용에 특히 관심이 있습니다.

높은 영향을 주는 변경 내용

필수 속성 없이 테이블을 공유하는 중첩된 선택적 종속 항목이 허용되지 않음

추적 이슈 #24558

이전 동작

필수 속성 없이 테이블을 공유하는 중첩된 선택적 종속 항목이 있는 모델은 허용되었지만, 데이터를 쿼리하고 다시 저장하면 데이터가 손실될 수 있었습니다. 예를 들어 다음 모델을 살펴봅니다.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

ContactInfo 또는 Address에 속성이 필요하지 않으며, 이러한 모든 엔터티 형식은 동일한 테이블에 매핑됩니다. 선택적 종속 항목(필수 종속 항목의 반대)에 대한 규칙은 ContactInfo에 대한 모든 열이 null이면 소유자 Customer를 쿼리할 때 ContactInfo의 인스턴스가 생성되지 않습니다. 그러나 Address 열이 null이 아닌 경우에도 Address의 인스턴스가 생성되지 않음도 의미합니다.

새 동작

이제 이 모델을 사용하려고 하면 다음 예외가 throw됩니다.

System.InvalidOperationException: ‘ContactInfo’ 엔터티 형식은 엔터티가 존재하는지 식별하기 위해 필요한 비공유 속성이 없는 기타 종속 항목을 포함하고 테이블 공유를 사용하는 선택적 종속 항목입니다. 모든 null 허용 속성이 데이터베이스의 null 값을 포함한다면 개체 인스턴스가 쿼리에 생성되지 않으며 중첩된 종속 항목의 값은 손실됩니다. 필수 속성을 추가하여 다른 속성에 대해 null 값이 포함된 인스턴스를 만들거나 들어오는 탐색을 필수로 표시하여 항상 인스턴스를 만듭니다.

이렇게 하면 데이터를 쿼리하고 저장할 때 데이터가 손실되지 않습니다.

이유

필수 속성 없이 테이블을 공유하는 중첩된 선택적 종속 항목이 있는 모델을 사용하면 종종 자동 데이터 손실이 발생합니다.

해결 방법

필수 속성 없이 테이블을 공유하는 선택적 종속 항목을 사용하지 마세요. 다음과 같은 세 가지 간단한 방법으로 이를 수행할 수 있습니다.

  1. 종속 항목을 필수로 표시합니다. 즉, 모든 속성이 null인 경우에도 쿼리된 후 종속 엔터티는 항상 값을 갖게 됩니다. 예시:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    또는

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. 종속 항목에 하나 이상의 필수 속성이 포함되도록 합니다.

  3. 주 엔터티와 테이블을 공유하는 대신 고유한 테이블에 선택적 종속 항목을 매핑합니다. 예시:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

선택적 종속 항목 관련 문제와 이러한 완화 관련 예제는 EF Core 6.0의 새로운 기능 설명서에 포함되어 있습니다.

중간 영향을 주는 변경 내용

이제 소유된 엔터티의 소유자를 변경하면 예외가 throw됨

추적 이슈 #4073

이전 동작

소유된 엔터티를 다른 소유자 엔터티에 다시 할당할 수 있었습니다.

새 동작

이 작업은 이제 예외를 throw합니다.

‘{entityType}.{property}’ 속성은 키의 일부이므로 수정하거나 수정됨으로 표시할 수 없습니다. 식별 외래 키를 사용하여 기존 엔터티의 보안 주체를 변경하려면 먼저 종속 항목을 삭제하고 ‘SaveChanges’를 호출한 다음, 종속 항목을 새 보안 주체와 연결합니다.

이유

소유된 형식에 키 속성이 필요하지 않더라도 EF는 기본 키로 사용할 섀도 속성과 소유자를 가리키는 외래 키를 만듭니다. 소유자 엔터티가 변경되면 소유된 엔터티의 외래 키 값이 변경되고 해당 값이 기본 키로도 사용되므로 엔터티 ID가 변경됩니다. 이는 아직 EF Core에서 완전히 지원되지 않으며, 소유된 엔터티에 대해서만 조건부로 허용되어 내부 상태가 일관되지 않은 경우가 있었습니다.

해결 방법

동일한 소유된 인스턴스를 새 소유자에게 할당하는 대신 복사본을 할당하고 이전 인스턴스를 삭제할 수 있습니다.

추적 이슈 #24803새로운 기능: 기본적으로 암시적 소유권으로 설정됨

이전 동작

다른 공급자와 마찬가지로 관련 엔터티 형식은 일반(소유되지 않음) 형식으로 검색되었습니다.

새 동작

이제 관련 엔터티 형식은 검색된 엔터티 형식이 소유합니다. DbSet<TEntity> 속성에 해당하는 엔터티 형식만 소유되지 않음으로 검색됩니다.

이유

이 동작은 관련 데이터를 단일 문서에 포함하는 Azure Cosmos DB의 데이터를 모델링하는 일반적인 패턴을 따릅니다. Cosmos DB는 기본적으로 다른 문서 조인을 지원하지 않으므로 관련 엔터티를 소유되지 않음으로 모델링하면 유용성이 제한됩니다.

해결 방법

엔터티 형식을 소유되지 않음으로 구성하려면 modelBuilder.Entity<MyEntity>();를 호출합니다.

SQLite: 연결이 풀링된 경우

추적 이슈 #13837새로운 기능: 기본적으로 암시적 소유권으로 설정됨

이전 동작

이전에는 Microsoft.Data.Sqlite의 연결이 풀링되지 않았습니다.

새 동작

6.0부터 이제 연결이 기본적으로 풀링됩니다. 이로 인해 ADO.NET 연결 개체가 닫힌 후에도 프로세스에 의해 데이터베이스 파일이 계속 열려 있습니다.

이유

기본 연결을 풀링하면 ADO.NET 연결 개체의 열기 및 닫기 성능이 크게 향상됩니다. 이는 암호화의 경우와 같이 기본 연결을 여는 데 비용이 많이 드는 시나리오 또는 데이터베이스에 대한 단기 연결이 많은 시나리오에서 특히 그렇습니다.

해결 방법

연결 문자열에 Pooling=False를 추가하여 연결 풀링을 사용하지 않도록 설정할 수 있습니다.

데이터베이스 파일 삭제처럼 일부 시나리오에서는 이제 파일이 계속 사용되고 있음을 나타내는 오류가 발생할 수 있습니다. SqliteConnection.ClearPool()을 사용하여 파일 작업을 수행하기 전에 연결 풀을 수동으로 지울 수 있습니다.

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

매핑된 조인 엔터티가 없는 다대다 관계가 이제 스캐폴드됨

추적 이슈 #22475

이전 동작

기존 데이터베이스의 엔터티 형식과 DbContext를 스캐폴딩(리버스 엔지니어링)하면 항상 명시적으로 매핑된 조인 테이블이 다대다 관계의 엔터티 형식을 조인합니다.

새 동작

다른 테이블에 대한 외래 키 속성이 두 개만 포함된 단순 조인 테이블은 더 이상 명시적 엔터티 형식에 매핑되지 않지만 대신 조인된 두 테이블 간의 다대다 관계로 매핑됩니다.

이유

명시적 조인 형식이 없는 다대다 관계는 EF Core 5.0에서 도입되었으며 단순 조인 테이블을 표현하는 더 깔끔하고 자연스러운 방법입니다.

해결 방법

두 가지 완화 방법이 있습니다. 선호되는 방법은 다대다 관계를 직접 사용하도록 코드를 업데이트하는 것입니다. 다대다 관계에 대해 외래 키가 두 개만 포함되는 경우 조인 엔터티 형식을 직접 사용해야 하는 경우는 거의 없습니다.

또는 명시적 조인 엔터티를 EF 모델에 다시 추가할 수 있습니다. 예를 들어 PostTag 간 다대다 관계를 가정하면 부분 클래스를 사용하여 조인 형식과 탐색을 다시 추가합니다.

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

그런 다음, DbContext에 대한 부분 클래스에 조인 형식과 탐색의 구성을 추가합니다.

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

마지막으로, 스캐폴드된 컨텍스트에서 다대다 관계에 대해 생성된 구성을 제거합니다. 이는 명시적 형식을 사용하려면 모델에서 스캐폴드된 조인 엔터티 형식을 제거해야 하기 때문에 필요합니다. 이 코드는 컨텍스트가 스캐폴드될 때마다 제거되어야 하지만, 위의 코드는 부분 클래스에 있으므로 유지됩니다.

이 구성을 사용하면 이전 버전의 EF Core와 마찬가지로 조인 엔터티를 명시적으로 사용할 수 있습니다. 그러나 관계는 다대다 관계로도 사용될 수 있습니다. 즉, 이와 같은 코드를 업데이트하는 것은 나머지 코드가 업데이트되는 동안 관계를 자연스러운 방식으로 다대다로 사용하기 위한 임시 방법입니다.

낮은 영향을 주는 변경 내용

DeleteBehavior 및 ON DELETE 값 간의 매핑이 정리됨

추적 이슈 #21252

이전 동작

마이그레이션과 스캐폴딩 모두에서 데이터베이스 내 관계의 OnDelete() 동작과 외래 키의 ON DELETE 동작 간의 매핑 중 일부가 일치하지 않았습니다.

새 동작

다음 표에서는 마이그레이션의 변경 내용을 보여 줍니다.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
제한 RESTRICT
Cascasde CASCADE
ClientCascade RESTRICTNO ACTION
SetNull SET NULL
ClientSetNull RESTRICTNO ACTION

스캐폴딩의 변경 내용은 다음과 같습니다.

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNullRestrict
CASCADE Cascade
SET NULL SetNull

이유

새 매핑은 더 일관됩니다. NO ACTION의 기본 데이터베이스 동작은 이제 더 제한적이고 성능이 떨어지는 RESTRICT 동작보다 선호됩니다.

해결 방법

선택적 관계의 기본 OnDelete() 동작은 ClientSetNull입니다. 매핑이 RESTRICT에서 NO ACTION으로 변경되었습니다. 이로 인해 EF Core 6.0으로 업그레이드한 후 추가된 첫 번째 마이그레이션에서 많은 작업이 생성될 수 있습니다.

이러한 작업은 EF Core에 기능적으로 영향을 미치지 않기 때문에 마이그레이션에서 수동으로 제거하거나 적용하도록 선택할 수 있습니다.

SQL Server에서 RESTRICT를 지원하지 않으므로 이러한 외래 키는 NO ACTION을 사용하여 이미 만들어졌습니다. 마이그레이션 작업은 SQL Server에 영향을 미치지 않으며 제거해도 안전합니다.

메모리 내 데이터베이스의 유효성 검사에 필요한 속성이 null을 포함하지 않음

추적 이슈 #10613

이전 동작

속성이 필수로 구성된 경우에도 메모리 내 데이터베이스에서 null 값을 저장할 수 있습니다.

새 동작

SaveChanges 또는 SaveChangesAsync가 호출되고 필수 속성이 null로 설정되면 메모리 내 데이터베이스가 Microsoft.EntityFrameworkCore.DbUpdateException을 throw합니다.

이유

이제 메모리 내 데이터베이스 동작이 다른 데이터베이스의 동작과 일치합니다.

해결 방법

메모리 내 공급자를 구성하는 경우 이전 동작(즉, null 값을 확인하지 않음)을 복원할 수 있습니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

컬렉션에 조인할 때 마지막 ORDER BY가 제거됨

추적 이슈 #19828

이전 동작

컬렉션(일 대 다 관계)에서 SQL JOIN을 수행할 때 EF Core는 조인된 테이블의 각 키 열에 대해 ORDER BY를 추가하는 데 사용되었습니다. 예를 들어 관련 게시물과 함께 모든 블로그를 로드하는 작업은 다음 SQL을 통해 수행되었습니다.

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

이러한 순서는 엔터티를 적절히 구체화하는 데 필요합니다.

새 동작

이제 컬렉션 조인에 대한 마지막 ORDER BY가 생략되었습니다.

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Post의 ID 열에 대한 ORDER BY가 더 이상 생성되지 않습니다.

이유

모든 ORDER BY는 데이터베이스 쪽에서 추가 작업을 적용하며, EF Core의 구체화 요구 사항에는 마지막 순서가 필요하지 않습니다. 데이터에 따르면 이 마지막 순서를 제거하면 일부 시나리오에서 성능이 크게 향상될 수 있습니다.

해결 방법

애플리케이션에서 조인된 엔터티가 특정 순서로 반환되어야 하는 경우 쿼리에 LINQ OrderBy 연산자를 추가하여 명시적으로 만듭니다.

DbSet은 IAsyncEnumerable을 더 이상 구현하지 않음

추적 이슈 #24041

이전 동작

DbContext에서 쿼리를 실행하는 데 사용되는 DbSet<TEntity>을 사용하여 IAsyncEnumerable<T>을 구현했습니다.

새 동작

DbSet<TEntity>IAsyncEnumerable<T>을 더 이상 직접 구현하지 않습니다.

이유

DbSet<TEntity>은 원래 IAsyncEnumerable<T>을 구현하여 foreach 구문을 통해 직접 열거형을 허용하도록 만들어졌습니다. 안타깝게도 프로젝트가 비동기 LINQ 연산자 클라이언트 쪽을 구성하기 위해 System.Linq.Async를 참조하면 IQueryable<T>에 대해 정의된 연산자와 IAsyncEnumerable<T>에 대해 정의된 연산자 간에 모호한 호출 오류가 발생했습니다. C# 9에서는 foreach 루프에 대한 확장 GetEnumerator 지원을 추가하여 IAsyncEnumerable을 참조하는 원래의 주요 이유를 제거했습니다.

DbSet에 대해 LINQ 연산자를 작성하거나 열거하는 등 대부분의 DbSet 사용은 그대로 계속 작동합니다. 그러나 DbSetIAsyncEnumerable에 직접 캐스팅하려는 경우에만 사용이 중단됩니다.

해결 방법

DbSet<TEntity>IAsyncEnumerable<T>로 참조해야 하는 경우에는 DbSet<TEntity>.AsAsyncEnumerable을 호출하여 명시적으로 캐스팅하세요.

TVF 반환 엔터티 형식도 기본적으로 테이블에 매핑됨

추적 이슈 #23408

이전 동작

HasDbFunction로 구성된 TVF의 반환 형식으로 사용되는 경우 기본적으로 엔터티 형식이 테이블에 매핑되지 않았습니다.

새 동작

TVF의 반환 형식으로 사용되는 엔터티 형식은 기본 테이블 매핑을 유지합니다.

이유

TVF를 구성하면 반환 엔터티 형식의 기본 테이블 매핑이 제거되는 것은 직관적이지 않습니다.

해결 방법

기본 테이블 매핑을 제거하려면 ToTable(EntityTypeBuilder, String)을 호출합니다.

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

CHECK 제약 조건 이름 고유성이 이제 유효성 검사됨

추적 이슈 #25061

이전 동작

이름이 동일한 CHECK 제약 조건을 동일한 테이블에서 선언하고 사용할 수 있었습니다.

새 동작

이제 동일한 테이블에서 이름이 동일한 두 개의 CHECK 제약 조건을 명시적으로 구성하면 예외가 발생합니다. 규칙에 따라 생성되는 CHECK 제약 조건에는 고유한 이름이 할당됩니다.

이유

대부분 데이터베이스에서는 동일한 테이블에서 동일한 이름으로 두 개의 CHECK 제약 조건을 만들 수 없으며 일부 데이터베이스의 경우 CHECK 제약 조건 이름은 테이블 간에 고유해야 합니다. 이로 인해 마이그레이션을 적용할 때 예외가 throw됩니다.

해결 방법

이 변경으로 인해 유효한 CHECK 제약 조건 이름이 다를 수도 있습니다. 원하는 이름을 명시적으로 지정하려면 HasName을 호출합니다.

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

IReadOnly 메타데이터 인터페이스가 추가되고 확장 메서드가 제거됨

추적 이슈 #19213

이전 동작

IModel, IMutableModel, IConventionModel라는 세 개의 메타데이터 인터페이스 세트와 확장 메서드가 있었습니다.

새 동작

새로운 IReadOnly 인터페이스 세트가 추가되었습니다(예: IReadOnlyModel). 이전에 메타데이터 인터페이스용으로 정의된 확장 메서드는 기본 인터페이스 메서드로 변환되었습니다.

이유

기본 인터페이스 메서드를 사용하면 구현을 재정의할 수 있으며 이는 새 런타임 모델 구현에서 더 나은 성능을 제공하는 데 사용됩니다.

해결 방법

이 변경 내용은 대부분 코드에 영향을 주지 않습니다. 그러나 정적 호출 구문을 통해 확장 메서드를 사용하는 경우 인스턴스 호출 구문으로 변환해야 합니다.

IExecutionStrategy는 이제 singleton 서비스입니다.

추적 이슈 #21350

새 동작

IExecutionStrategy는 이제 singleton 서비스입니다. 즉, 사용자 지정 구현에서 추가된 상태는 실행 간에 유지되고 ExecutionStrategy에 전달된 대리자는 한 번만 실행됩니다.

이유

이렇게 하여 EF의 두 실행 부하 과다 경로에 대한 할당이 감소했습니다.

해결 방법

ExecutionStrategy에서 파생되는 구현은 OnFirstExecution()의 모든 상태를 지워야 합니다.

ExecutionStrategy에 전달된 대리자의 조건부 논리를 IExecutionStrategy의 사용자 지정 구현으로 이동해야 합니다.

SQL Server: 더 많은 오류가 일시적으로 간주됨

추적 이슈 #25050

새 동작

위 이슈에 나열된 오류는 이제 일시적으로 간주됩니다. 기본(다시 시도되지 않음) 실행 전략을 사용하는 경우 이 오류는 이제 추가 예외 인스턴스에서 래핑됩니다.

이유

사용자와 SQL Server 팀에서 일시적으로 간주해야 하는 오류에 관한 피드백을 계속 수집하고 있습니다.

해결 방법

일시적으로 간주되는 오류 세트를 변경하려면 SqlServerRetryingExecutionStrategy - Connection Resiliency - EF Core에서 파생될 수 있는 사용자 지정 실행 전략을 사용합니다.

Azure Cosmos DB: 더 많은 문자가 'id' 값으로 이스케이프됩니다.

추적 이슈 #25100

이전 동작

EF Core 5에서는 '|'id 값에서 이스케이프되었습니다.

새 동작

EF Core 6에서는 '/', '\', '?', '#'id 값에서 이스케이프됩니다.

이유

Resource.Id에 설명된 대로 이러한 문자는 잘못되었습니다. id에서 해당 문자를 사용하면 쿼리가 실패합니다.

해결 방법

엔터티가 Added로 표시되기 전에 생성된 값을 설정하여 재정의할 수 있습니다.

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

일부 싱글톤 서비스가 이제 범위가 지정됨

추적 이슈 #25084

새 동작

Singleton로 등록된 많은 쿼리 서비스와 일부 디자인 타임 서비스가 이제 Scoped로 등록됩니다.

이유

새 기능 DefaultTypeMapping이 쿼리에 영향을 줄 수 있으려면 수명을 변경해야 했습니다.

두 서비스를 모두 사용하는 경우 오류를 방지하기 위해 런타임 서비스 수명과 일치하도록 디자인 타임 서비스 수명이 조정되었습니다.

해결 방법

TryAdd를 통해 기본 수명을 사용하여 EF Core 서비스를 등록합니다. EF에서 추가하지 않는 서비스에만 TryAddProviderSpecificServices를 사용합니다.

서비스를 추가하거나 바꾸는 확장의 새 캐싱 API

추적 이슈 #19152

이전 동작

EF Core 5에서는 GetServiceProviderHashCodelong을 반환하고 서비스 공급자에 대한 캐시 키의 일부로 직접 사용되었습니다.

새 동작

GetServiceProviderHashCode는 이제 int를 반환하고 서비스 공급자에 대한 캐시 키의 해시 코드를 계산하는 데만 사용됩니다.

또한 현재 개체가 동일한 서비스 구성을 나타내므로 동일한 서비스 공급자를 사용할 수 있는지 여부를 나타내려면 ShouldUseSameServiceProvider를 구현해야 합니다.

이유

해시 코드를 캐시 키의 일부로 사용하면 가끔 진단과 수정이 어려운 충돌이 발생했습니다. 추가 메서드는 동일한 서비스 공급자가 적절한 경우에만 사용되도록 합니다.

해결 방법

대부분 확장은 등록된 서비스에 영향을 주는 옵션을 노출하지 않으며 ShouldUseSameServiceProvider의 다음 구현을 사용할 수 있습니다.

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

이외의 경우에는 모든 관련 옵션을 비교하기 위해 추가적인 조건자를 추가해야 합니다.

새 스냅샷 및 디자인 타임 모델 초기화 프로시저

추적 이슈 #22031

이전 동작

EF Core 5에서는 스냅샷 모델을 사용할 준비가 되기 전에 특정 규칙을 호출해야 했습니다.

새 동작

IModelRuntimeInitializer가 일부 필요한 단계를 숨기기 위해 도입되었으며, 일부 마이그레이션 메타데이터가 없는 런타임 모델이 도입되었으므로 모델 diff에 디자인 타임 모델을 사용해야 합니다.

이유

IModelRuntimeInitializer는 모델 종료 단계를 추상화하므로 이제 사용자에 대한 호환성이 손상되는 변경 없이 관련 단계를 변경할 수 있습니다.

런타임 성능을 개선하기 위해 최적화된 런타임 모델이 도입되었습니다. 여러 가지 최적화가 있으며, 그중 하나는 런타임에 사용되지 않는 메타데이터를 제거하는 것입니다.

해결 방법

다음 코드 조각은 현재 모델이 스냅샷 모델과 다른지 여부를 확인하는 방법을 보여 줍니다.

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

이 코드 조각은 외부에서 모델을 만들고 UseModel을 호출하여 IDesignTimeDbContextFactory<TContext>를 구현하는 방법을 보여줍니다.

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex는 이제 다른 형식을 반환합니다.

추적 이슈 #24005

이전 동작

EF Core 5에서는 HasIndexIndexBuilder<TEntity>를 반환했습니다. 여기서 TEntity는 소유자 형식입니다.

새 동작

HasIndex는 이제 IndexBuilder<TDependentEntity>를 반환합니다. 여기서 TDependentEntity는 소유된 형식입니다.

이유

반환된 작성기 개체가 올바르게 입력되지 않았습니다.

해결 방법

최신 버전의 EF Core에 대해 어셈블리를 다시 컴파일하면 이 변경으로 인한 문제를 충분히 해결할 수 있습니다.

DbFunctionBuilder.HasSchema(null)[DbFunction(Schema = "schema")]을 재정의함

추적 이슈 #24228

이전 동작

EF Core 5에서 값이 nullHasSchema을 호출하면 구성 소스가 저장되지 않으므로 DbFunctionAttribute가 이를 재정의할 수 있었습니다.

새 동작

값이 nullHasSchema을 호출하면 이제 구성 소스가 저장되고 특성이 재정의되지 않습니다.

이유

ModelBuilder API로 지정된 구성은 데이터 주석으로 재정의할 수 없습니다.

해결 방법

HasSchema 호출을 제거하여 특성이 스키마를 구성할 수 있도록 합니다.

미리 초기화된 탐색이 데이터베이스 쿼리의 값으로 재정의됨

추적 이슈 #23851

이전 동작

추적 쿼리의 경우 빈 개체로 설정된 탐색 속성을 변경하지 않고 유지했지만 비추적 쿼리의 경우 덮어썼습니다. 예를 들어 다음 엔터티 형식을 살펴봅니다.

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

데이터베이스에서 쿼리된 엔터티에 대해 Foo.Bar로 설정된 Bar를 포함하는 Foo의 비추적 쿼리. 예를 들어 이 코드는 다음과 같습니다.

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Foo.Bar.Id = 1이 출력되었습니다.

그러나 추적을 위해 동일한 쿼리를 실행해도 데이터베이스에서 쿼리된 엔터티로 Foo.Bar를 덮어쓰지 않았습니다. 예를 들어 이 코드는 다음과 같습니다.

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Foo.Bar.Id = 0이 출력되었습니다.

새 동작

EF Core 6.0에서는 추적 쿼리의 동작이 이제 비추적 쿼리의 동작과 일치합니다. 즉, 이 코드는 다음을 수행합니다.

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

다음 코드를 사용합니다.

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Foo.Bar.Id = 1을 출력합니다.

이유

이러한 변경에는 두 가지 이유가 있습니다.

  1. 추적 쿼리와 비추적 쿼리가 일관된 동작을 갖도록 합니다.
  2. 데이터베이스를 쿼리할 때 애플리케이션 코드가 데이터베이스에 저장된 값을 다시 얻고자 하는 것으로 가정하는 것이 합리적입니다.

해결 방법

다음과 같은 두 가지 완화 방법이 있습니다.

  1. 결과에 포함되지 않아야 하는 데이터베이스의 개체를 쿼리하지 않습니다. 예를 들어 위의 코드 조각에서, Bar 인스턴스를 데이터베이스에서 반환하여 결과에 포함하지 않아야 하는 경우 IncludeFoo.Bar를 수행하지 않습니다.
  2. 데이터베이스에서 쿼리한 후 탐색 값을 설정합니다. 예를 들어 위의 코드 조각에서, 쿼리를 실행한 후 foo.Bar = new()를 호출합니다.

또한 관련 엔터티 인스턴스를 기본 개체로 초기화하지 않습니다. 이는 관련 인스턴스가 키 값이 설정되지 않은 데이터베이스에 저장되지 않은 새 엔터티임을 의미합니다. 대신 관련 엔터티가 데이터베이스에 있는 경우 코드의 데이터는 기본적으로 데이터베이스에 저장된 데이터와 상충합니다.

쿼리할 때 데이터베이스의 알 수 없는 열거형 문자열 값이 열거형 기본값으로 변환되지 않음

추적 이슈 #24084

이전 동작

열거형 속성은 HasConversion<string>() 또는 EnumToStringConverter를 사용하여 데이터베이스의 문자열 열에 매핑할 수 있습니다. 이로 인해 EF Core에서 열의 문자열 값을 .NET 열거형 형식의 일치하는 멤버로 변환합니다. 그러나 문자열 값이 열거형 멤버와 일치하지 않으면 속성은 열거형의 기본값으로 설정되었습니다.

새 동작

이제 EF Core 6.0은 “데이터베이스의 ‘{value}’ 값을 매핑된 ‘{enumType}’ 열거형의 값으로 변환할 수 없습니다.” 메시지와 함께 InvalidOperationException을 throw합니다.

이유

엔터티가 나중에 데이터베이스에 다시 저장되는 경우 기본값으로 변환하면 데이터베이스 손상이 발생할 수 있습니다.

해결 방법

이상적으로 데이터베이스 열에 유효한 값만 포함되도록 합니다. 또는 이전 동작을 사용하여 ValueConverter를 구현합니다.

DbFunctionBuilder.HasTranslation이 이제 IReadOnlyCollection이 아니라 IReadOnlyList로서 함수 인수를 제공함

추적 이슈 #23565

이전 동작

HasTranslation 메서드를 사용하여 사용자 정의 함수의 변환을 구성할 때 해당 함수에 대한 인수가 IReadOnlyCollection<SqlExpression>으로 제공되었습니다.

새 동작

EF Core 6.0에서 인수는 이제 IReadOnlyList<SqlExpression>으로 제공됩니다.

이유

IReadOnlyList는 인덱서 사용을 허용하므로 이제 인수에 쉽게 액세스할 수 있습니다.

해결 방법

없음 IReadOnlyListIReadOnlyCollection 인터페이스를 구현하므로 변환이 간단해야 합니다.

엔터티가 테이블 반환 함수에 매핑될 때 기본 테이블 매핑이 제거되지 않음

추적 이슈 #23408

이전 동작

엔터티가 테이블 반환 함수에 매핑될 때 테이블에 대한 기본 매핑이 제거되었습니다.

새 동작

EF Core 6.0에서는 엔터티가 테이블 반환 함수에 매핑되더라도 기본 매핑을 사용하여 테이블에 계속 매핑됩니다.

이유

엔터티를 반환하는 테이블 반환 함수는 전체 테이블을 엄격하게 대체하는 대신 엔터티 컬렉션을 반환하는 작업을 캡슐화하거나 도우미로 사용하는 경우가 많습니다. 이 변경은 가능성이 높은 사용자 의도에 더 부합하기 위한 것입니다.

해결 방법

테이블에 매핑이 모델 구성에서 명시적으로 사용하지 않도록 설정될 수 있습니다.

modelBuilder.Entity<MyEntity>().ToTable((string)null);

dotnet-ef는 .NET 6을 대상으로 함

추적 이슈 #27787

이전 동작

dotnet-ef 명령은 한동안 .NET Core 3.1을 대상으로 했습니다. 이렇게 하면 최신 버전의 .NET 런타임을 설치하지 않고도 최신 버전의 도구를 사용할 수 있습니다.

새 동작

EF Core 6.0.6에서 dotnet-ef 도구는 이제 .NET 6을 대상으로 합니다. 이전 버전의 .NET 및 .NET Core를 대상으로 하는 프로젝트에서 이 도구를 계속 사용할 수 있지만 이 도구를 실행하려면 .NET 6 런타임을 설치해야 합니다.

이유

.NET 6.0.200 SDK는 osx-arm64의 dotnet tool install 동작을 업데이트하여 .NET Core 3.1을 대상으로 하는 도구용 osx-x64 shim을 만들었습니다. dotnet-ef의 작업 기본 환경을 유지하려면 .NET 6을 대상으로 업데이트해야 했습니다.

해결 방법

.NET 6 런타임을 설치하지 않고 dotnet-ef를 실행하려면 이전 버전의 도구를 설치할 수 있습니다.

dotnet tool install dotnet-ef --version 3.1.*

디자인 타임 캐싱을 처리하려면 IModelCacheKeyFactory 구현을 업데이트해야 할 수 있습니다.

추적 문제 #25154

이전 동작

IModelCacheKeyFactory에는 런타임 모델과 별도로 디자인 타임 모델을 캐시할 수 있는 옵션이 없었습니다.

새 동작

IModelCacheKeyFactory에는 디자인 타임 모델을 런타임 모델과 별도로 캐시할 수 있는 새 오버로드가 있습니다. 이 메서드를 구현하지 않을 경우 다음 유사한 예외가 발생할 수 있습니다.

System.InvalidOperationException: '요청된 구성이 읽기 최적화 모델에 저장되지 않습니다. 'DbContext.GetService<IDesignTimeModel>().Model'을 사용하세요.

이유

컴파일된 모델을 구현하려면 디자인 타임(모델을 빌드할 때 사용됨) 및 런타임(쿼리를 실행할 때 사용) 모델을 분리해야 했습니다. 런타임 코드가 디자인 타임 정보에 액세스해야 하는 경우 디자인 타임 모델을 캐시해야 합니다.

해결 방법

새 오버로드를 구현합니다. 예시:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

수정 프로그램에서 자동으로 채워지므로 '{navigation}' 탐색이 쿼리의 '포함'에서 무시되었습니다. 나중에 '포함'에 추가 탐색이 지정되면 무시됩니다. 포함 트리에서 돌아갈 수 없습니다.

추적 이슈 #4315

이전 동작

이벤트 CoreEventId.NavigationBaseIncludeIgnored는 기본적으로 경고로 기록되었습니다.

새 동작

이벤트 CoreEventId.NavigationBaseIncludeIgnored는 기본적으로 오류로 기록되어 예외가 throw되도록 합니다.

이유

이러한 쿼리 패턴은 허용되지 않으므로 이제 EF Core는 쿼리를 업데이트해야 함을 나타냅니다.

완화 방법

이벤트를 경고로 구성하면 이전 동작을 복원할 수 있습니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));