다음을 통해 공유


EF9(EF Core 9)의 호환성이 손상되는 변경

이 페이지는 EF Core 8에서 EF Core 9로 업데이트되는 기존 애플리케이션의 호환성이 손상될 수 있는 API 및 동작 변경을 기록합니다. 이전 버전의 EF Core에서 업데이트하는 경우 이전의 호환성이 손상되는 변경을 검토합니다.

대상 프레임워크

EF Core 9는 .NET 8을 대상으로 합니다. 즉, .NET 8을 대상으로 하는 기존 애플리케이션은 계속 그렇게 할 수 있습니다. 이전 .NET, .NET Core 및 .NET Framework 버전을 대상으로 하는 애플리케이션은 EF Core 9를 사용하려면 .NET 8 또는 .NET 9를 대상으로 해야 합니다.

요약

참고 항목

Azure Cosmos DB를 사용하는 경우 Azure Cosmos DB 호환성이 손상되는 변경에 대한 아래의 별도 섹션을 참조하세요.

주요 변경 내용 영향
EF.Functions.Unhex()가 이제 byte[]?를 반환합니다. 낮음
SqlFunctionExpression의 Null 허용 여부 인수의 arity 유효성이 검사됨 낮음
ToString() 메서드는 이제 인스턴스에 대해 빈 문자열을 null 반환합니다. 낮음
공유 프레임워크 종속성이 9.0.x로 업데이트되었습니다. 낮음

낮은 영향을 주는 변경 내용

EF.Functions.Unhex()가 이제 byte[]?를 반환합니다.

추적 이슈 #33864

이전 동작

EF.Functions.Unhex() 함수는 이전에 byte[]를 반환하도록 주석이 추가되었습니다.

새 동작

EF Core 9.0부터 Unhex()는 이제 byte[]?을(를) 반환하도록 주석이 추가됩니다

이유

Unhex()는 잘못된 입력에 대해 NULL을 반환하는 SQLite unhex 함수로 변환됩니다. 결과적으로 Unhex()는 주석을 위반하여 이러한 경우에 대해 null을 반환했습니다.

해결 방법

Unhex()에 전달된 텍스트 콘텐츠가 유효한 16진수 문자열을 나타내는 것이 확실하다면, 호출이 절대로 null을 반환하지 않는다는 어설션으로 null 허용 연산자를 추가하면 됩니다

var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();

그렇지 않으면 Unhex()의 반환 값에 null에 대한 런타임 검사를 추가합니다.

SqlFunctionExpression의 Null 허용 여부 인수의 arity 유효성이 검사됨

추적 이슈 #33852

이전 동작

이전에는 다른 수의 인수와 null 허용 여부 전파 인수를 사용하여 SqlFunctionExpression을(를) 만들 수 있었습니다.

새 동작

EF Core 9.0부터 EF는 이제 인수 수와 null 허용 여부 전파 인수가 일치하지 않으면 throw됩니다.

이유

일치하는 인수 수와 Null 허용 여부 전파 인수가 없으면 예기치 않은 동작이 발생할 수 있습니다.

완화 방법

argumentsPropagateNullabilityarguments와(과) 같은 수의 요소가 있는지 확인합니다. 의심스러운 경우 null 허용 여부 인수에 false을(를) 사용합니다.

ToString() 메서드는 이제 인스턴스에 대해 빈 문자열을 null 반환합니다.

추적 문제 #33941

이전 동작

이전에 EF는 인수 값이 있을 때 메서드에 ToString() 대해 일관되지 않은 결과를 반환했습니다 null. 예를 들어 값이 null 반환된 속성에서 값이 반환nullTruenull 속성 bool? 이 아닌 식의 경우입니다. ToString() bool? 다른 데이터 형식(예: 값 열거형이 빈 문자열을 null 반환하는 경우ToString())에도 동작이 일치하지 않았습니다.

새 동작

EF Core 9.0부터 메서드는 ToString() 이제 인수 값이 null있을 때 모든 경우에 빈 문자열을 일관되게 반환합니다.

이유

이전 동작은 서로 다른 데이터 형식 및 상황에서 일관성이 없을 뿐만 아니라 C# 동작일치하지 않았습니다.

해결 방법

이전 동작으로 되돌리려면 그에 따라 쿼리를 다시 작성합니다.

var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());

공유 프레임워크 종속성이 9.0.x로 업데이트되었습니다.

이전 동작

SDK를 사용하고 Microsoft.NET.Sdk.Web net8.0을 대상으로 하는 앱은 공유 프레임워크와 Microsoft.Extensions.DependencyModel 같은 Microsoft.Extensions.Configuration.AbstractionsSystem.Text.JsonMicrosoft.Extensions.Caching.MemoryMicrosoft.Extensions.Logging 패키지를 확인하므로 이러한 어셈블리는 일반적으로 앱과 함께 배포되지 않습니다.

새 동작

EF Core 9.0은 여전히 net8.0을 지원하지만 이제는 9.0.x 버전의 System.Text.Json, Microsoft.Extensions.Logging Microsoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.Abstractions및 .Microsoft.Extensions.DependencyModel net8.0을 대상으로 하는 앱은 공유 프레임워크를 활용하여 이러한 어셈블리를 배포하지 않도록 할 수 없습니다.

이유

일치하는 종속성 버전에는 최신 보안 수정 사항이 포함되어 있으며 이를 사용하면 EF Core에 대한 서비스 모델이 간소화됩니다.

해결 방법

이전 동작을 가져오기 위해 앱을 대상 net9.0으로 변경합니다.

Azure Cosmos DB 호환성이 손상되는 변경

9.0에서 Azure Cosmos DB 공급자를 개선하기 위한 광범위한 작업이 진행되었습니다. 변경 내용에는 많은 영향을 미치는 호환성이 손상되는 변경이 포함됩니다. 기존 애플리케이션을 업그레이드하는 경우 다음을 주의 깊게 읽어 보세요.

주요 변경 내용 영향
이제 판별자 속성의 이름은 Discriminator 대신 $type으로 지정됨 높음
id 속성은 기본적으로 판별자를 더 이상 포함하지 않음 높음
Azure Cosmos DB 공급자를 통한 동기화 I/O는 더 이상 지원되지 않음 중간
SQL 쿼리는 이제 JSON 값을 직접 프로젝트해야 함 중간
이제 쿼리 결과에서 정의되지 않은 결과가 자동으로 필터링됨 중간
잘못 변환된 쿼리는 더 이상 변환되지 않음 중간
HasIndex가 이제 무시되는 대신 throw됨 낮음
IncludeRootDiscriminatorInJsonId가 9.0.0-rc.2 이후 HasRootDiscriminatorInJsonId로 이름이 변경됨 낮음

높은 영향을 주는 변경 내용

이제 판별자 속성의 이름은 Discriminator 대신 $type으로 지정됨

추적 문제 #34269

이전 동작

EF는 JSON 문서에 판별자 속성을 자동으로 추가하여 문서가 나타내는 엔터티 형식을 식별합니다. 이전 버전의 EF에서 이 JSON 속성은 기본적으로 이름이 Discriminator로 지정되었습니다.

새 동작

EF Core 9.0부터 판별자 속성은 이제 기본적으로 $type이라고 합니다. 이전 버전의 EF에서 Azure Cosmos DB에 기존 문서가 있는 경우 이전 Discriminator 명명을 사용하고 EF 9.0으로 업그레이드한 후 해당 문서에 대한 쿼리가 실패합니다.

이유

새로운 JSON 사례는 문서의 형식을 식별해야 하는 시나리오에서 $type 속성을 사용합니다. 예를 들어. NET의 System.Text.Json은 $type을 기본 판별자 속성 이름(docs)으로 사용하여 다형성도 지원합니다. 나머지 에코시스템에 맞게 외부 도구와 쉽게 상호 운용할 수 있도록 기본값이 변경되었습니다.

해결 방법

가장 쉬운 완화 방법은 이전과 마찬가지로 판별자 속성의 이름을 Discriminator로 구성하기만 하면 됩니다.

modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");

모든 최상위 엔터티 형식에 대해 이 작업을 수행하면 EF가 이전처럼 동작하게 됩니다.

이 시점에서 원하는 경우 새 $type 이름을 사용하도록 모든 문서를 업데이트할 수도 있습니다.

이제 id 속성에는 기본적으로 EF 키 속성만 포함됨

추적 문제 #34179

이전 동작

이전에 EF는 엔터티 형식의 판별자 값을 문서의 id 속성에 삽입했습니다. 예를 들어 8을 포함하는 Id 속성이 있는 Blog 엔터티 형식을 저장한 경우 JSON id 속성에는 Blog|8이 포함됩니다.

새 동작

EF Core 9.0부터 JSON id 속성은 더 이상 판별자 값을 포함하지 않으며 키 속성의 값만 포함합니다. 위의 예제에서 JSON id 속성은 단순히 8입니다. 이전 버전의 EF에서 Azure Cosmos DB에 기존 문서가 있는 경우 JSON id 속성에 판별자 값이 있으며 EF 9.0으로 업그레이드한 후 해당 문서에 대한 쿼리가 실패합니다.

이유

JSON id 속성은 고유해야 하므로 이전에 동일한 키 값을 가진 다른 엔터티가 존재할 수 있도록 판별자가 추가되었습니다. 예를 들어 동일한 컨테이너 및 Post 파티션 내에 값 8이 포함된 속성과 Id a를 둘 다 Blog 사용할 수 있습니다. 이는 각 엔터티 형식이 자체 테이블에 매핑되므로 고유한 키 공간이 있는 관계형 데이터베이스 데이터 모델링 패턴에 더 잘 맞습니다.

EF 9.0은 일반적으로 관계형 데이터베이스에서 들어오는 사용자의 기대에 부합하는 대신 일반적인 Azure Cosmos DB NoSQL 사례 및 기대에 맞게 매핑을 변경했습니다. 또한 id 속성에 판별자 값이 있으면 외부 도구 및 시스템이 EF 생성 JSON 문서와 상호 작용하는 것이 더 어려워졌습니다. 이러한 외부 시스템은 일반적으로 .NET 형식에서 파생된 EF 판별자 값을 인식하지 못합니다.

해결 방법

가장 쉬운 완화 방법은 이전과 같이 JSON id 속성에 판별자를 포함하도록 EF를 구성하는 것입니다. 이 목적을 위해 새 구성 옵션이 도입되었습니다.

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

모든 최상위 엔터티 형식에 대해 이 작업을 수행하면 EF가 이전처럼 동작하게 됩니다.

이 시점에서 원하는 경우 모든 문서를 업데이트하여 JSON id 속성을 다시 작성할 수도 있습니다. 이는 다른 형식의 엔터티가 동일한 컨테이너 내에서 동일한 ID 값을 공유하지 않는 경우에만 가능합니다.

중간 영향을 주는 변경 내용

Azure Cosmos DB 공급자를 통한 I/O 동기화는 더 이상 지원되지 않음

추적 이슈 #32563

이전 동작

이전에는 Azure Cosmos DB SDK에 대해 비동기 호출을 실행할 때 ToList 또는 SaveChanges와 같은 동기 메서드를 호출하면 EF Core가 .GetAwaiter().GetResult()를 사용하여 동기적으로 차단되었습니다. 이로 인해 교착 상태가 발생할 수 있습니다.

새 동작

EF Core 9.0부터 EF는 이제 동기 I/O를 사용하려고 시도할 때 기본적으로 오류를 throw합니다. 예외 메시지는 "Azure Cosmos DB는 동기 I/O를 지원하지 않습니다. Entity Framework Core를 사용하여 Azure Cosmos DB에 액세스하는 경우 비동기 메서드만 사용하고 올바르게 대기해야 합니다. 자세한 내용은 https://aka.ms/ef-cosmos-nosync를 참조하세요."

이유

비동기 메서드에 대한 동기 차단으로 인해 교착 상태가 발생할 수 있으며 Azure Cosmos DB SDK는 비동기 메서드만 지원합니다.

완화 방법

EF Core 9.0에서는 다음을 사용하여 오류를 표시하지 않을 수 있습니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}

즉, Azure Cosmos DB SDK에서는 지원되지 않으므로 애플리케이션은 Azure Cosmos DB와의 동기화 API 사용을 중지해야 합니다. 예외를 표시하지 않는 기능은 EF Core의 향후 릴리스에서 제거될 예정이며, 이후에는 유일한 옵션은 비동기 API를 사용하는 것입니다.

SQL 쿼리는 이제 JSON 값을 직접 프로젝트해야 함

추적 문제 #25527

이전 동작

이전에는 EF에서 다음과 같은 쿼리를 생성했습니다.

SELECT c["City"] FROM root c

이러한 쿼리로 인해 Azure Cosmos DB는 다음과 같이 각 결과를 JSON 개체로 래핑합니다.

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]
새 동작

EF Core 9.0부터 EF는 이제 다음과 같이 쿼리에 VALUE 한정자를 추가합니다.

SELECT VALUE c["City"] FROM root c

이러한 쿼리로 인해 Azure Cosmos DB는 래핑되지 않고 값을 직접 반환합니다.

[
    "Berlin",
    "México D.F."
]

애플리케이션에서 SQL 쿼리를 사용하는 경우 이러한 쿼리는 VALUE 한정자를 포함하지 않으므로 EF 9.0으로 업그레이드한 후 해당 쿼리가 손상될 가능성이 높습니다.

이유

각 결과를 추가 JSON 개체로 래핑하면 일부 시나리오에서 성능이 저하되고 JSON 결과 페이로드가 부풀어 오르며 Azure Cosmos DB를 사용하는 자연스러운 방법이 아닙니다.

해결 방법

완화하려면 위와 같이 SQL 쿼리의 프로젝션에 VALUE 한정자를 추가하기만 하면 됩니다.

이제 쿼리 결과에서 정의되지 않은 결과가 자동으로 필터링됨

추적 문제 #25527

이전 동작

이전에는 EF에서 다음과 같은 쿼리를 생성했습니다.

SELECT c["City"] FROM root c

이러한 쿼리로 인해 Azure Cosmos DB는 다음과 같이 각 결과를 JSON 개체로 래핑합니다.

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]

결과가 정의되지 않은 경우(예: City 속성이 문서에 없는 경우) 빈 문서가 반환되고 EF가 해당 결과에 대해 null을 반환합니다.

새 동작

EF Core 9.0부터 EF는 이제 다음과 같이 쿼리에 VALUE 한정자를 추가합니다.

SELECT VALUE c["City"] FROM root c

이러한 쿼리로 인해 Azure Cosmos DB는 래핑되지 않고 값을 직접 반환합니다.

[
    "Berlin",
    "México D.F."
]

Azure Cosmos DB 동작은 결과에서 값을 자동으로 필터링 undefined 하는 것입니다. 즉, 속성 중 City 하나가 문서에 없는 경우 쿼리는 두 개의 결과가 아닌 하나의 결과만 반환합니다 null.

이유

각 결과를 추가 JSON 개체로 래핑하면 일부 시나리오에서 성능이 저하되고 JSON 결과 페이로드가 부풀어 오르며 Azure Cosmos DB를 사용하는 자연스러운 방법이 아닙니다.

해결 방법

애플리케이션에서 정의되지 않은 결과에 대해 null 값을 가져오는 것이 중요한 경우, 새 EF.Functions.Coalesce 연산자를 사용하여 undefined 값을 null로 병합합니다.

var users = await context.Customer
    .Select(c => EF.Functions.CoalesceUndefined(c.City, null))
    .ToListAsync();

잘못 변환된 쿼리는 더 이상 변환되지 않음

추적 문제 #34123

이전 동작

이전에는 EF에서 다음과 같은 쿼리를 변환했습니다.

var sessions = await context.Sessions
    .Take(5)
    .Where(s => s.Name.StartsWith("f"))
    .ToListAsync();

그러나 이 쿼리에 대한 SQL 변환이 잘못되었습니다.

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0

SQL에서 WHERE 절은 OFFSETLIMIT 절보다 먼저 평가되지만, 위의 LINQ 쿼리에서는 Where 연산자 앞에 Take 연산자가 나타납니다. 따라서 이러한 쿼리는 잘못된 결과를 반환할 수 있습니다.

새 동작

EF Core 9.0부터 이러한 쿼리는 더 이상 변환되지 않으며 예외가 throw됩니다.

이유

잘못된 변환으로 인해 자동 데이터 손상이 발생할 수 있으므로 애플리케이션에서 발견하기 어려운 버그가 발생할 수 있습니다. EF는 항상 데이터 손상을 유발하는 대신 선행을 throw하여 페일 패스트(Fail-fast)를 선호합니다.

해결 방법

이전 동작에 만족하고 동일한 SQL을 실행하려는 경우 LINQ 연산자의 순서를 바꾸면 됩니다.

var sessions = await context.Sessions
    .Where(s => s.Name.StartsWith("f"))
    .Take(5)
    .ToListAsync();

아쉽게도 Azure Cosmos DB는 현재 원래 LINQ 쿼리의 적절한 변환에 필요한 SQL 하위 쿼리의 절 및 LIMIT 절을 지원하지 OFFSET 않습니다.

낮은 영향을 주는 변경 내용

HasIndex가 이제 무시되는 대신 throw됨

추적 문제 #34023

이전 동작

이전에는 EF Cosmos DB 공급자가 HasIndex에 대한 호출을 무시했습니다.

새 동작

HasIndex가 지정된 경우 이제 공급자가 throw됩니다.

이유

Azure Cosmos DB에서 모든 속성은 기본적으로 인덱싱되며 인덱싱을 지정할 필요가 없습니다. 사용자 지정 인덱싱 정책을 정의할 수 있지만 현재 EF에서 지원되지 않으며, EF 지원 없이 Azure Portal을 통해 수행할 수 있습니다. HasIndex 호출은 아무 작업도 수행하지 않았기 때문에 더 이상 허용되지 않습니다.

해결 방법

HasIndex에 대한 모든 호출을 제거합니다.

IncludeRootDiscriminatorInJsonId가 9.0.0-rc.2 이후 HasRootDiscriminatorInJsonId로 이름이 변경됨

추적 문제 #34717

이전 동작

API는 IncludeRootDiscriminatorInJsonId 9.0.0 rc.1에서 도입되었습니다.

새 동작

EF Core 9.0의 최종 릴리스에서 API 이름이 HasRootDiscriminatorInJsonId로 변경됨

이유

다른 관련 API의 이름이 Include 대신 Has로 시작되도록 바뀌었기 때문에 일관성을 위해 이름이 변경되었습니다.

해결 방법

코드가 IncludeRootDiscriminatorInJsonId API를 사용하는 경우 대신 HasRootDiscriminatorInJsonId를 참조하도록 변경하기만 하면 됩니다.