이 페이지에서는 EF Core 9에서 EF Core 10으로 업데이트되는 기존 애플리케이션을 중단시킬 가능성이 있는 API 및 동작 변경 내용을 설명합니다. 이전 버전의 EF Core에서 업데이트하는 경우 과거의 주요 변경 사항을 반드시 검토하십시오.
요약
비고
Microsoft.Data.Sqlite를 사용하는 경우 아래의 Microsoft.Data.Sqlite 호환성이 손상되는 변경에 대한 별도의 섹션을 참조하세요.
중간 영향을 주는 변경 내용
이제 EF 도구를 사용하려면 다중 대상 프로젝트에 프레임워크를 지정해야 합니다.
기존 동작
이전에는 사용할 프레임워크를 지정하지 않고 여러 프레임워크를 대상으로 하는 프로젝트에서 EF 도구(dotnet-ef)를 사용할 수 있습니다.
새 동작
EF Core 10.0부터 여러 프레임워크를 대상으로 하는 프로젝트에서 EF 도구를 실행할 때, <TargetFrameworks>를 대신하여 <TargetFramework>를 사용해야 할 경우, --framework 옵션으로 사용할 프레임워크를 명시적으로 지정해야 합니다. 이 옵션이 없으면 다음 오류가 발생합니다.
프로젝트는 여러 프레임워크를 대상으로 합니다. --framework 옵션을 사용하여 사용할 대상 프레임워크를 지정합니다.
왜
EF Core 10에서 도구는 프로젝트 종속성에 대한 보다 정확한 정보를 얻기 위해 MSBuild 작업에 의존 ResolvePackageAssets 하기 시작했습니다. 그러나 프로젝트가 여러 TFM(대상 프레임워크)을 대상으로 하는 경우에는 이 작업을 사용할 수 없습니다. 솔루션을 사용하려면 사용자가 사용해야 하는 프레임워크를 선택해야 합니다.
완화
여러 프레임워크를 대상으로 하는 프로젝트에서 EF 도구 명령을 실행하는 경우 이 옵션을 사용하여 대상 프레임워크를 --framework 지정합니다. 다음은 그 예입니다.
dotnet ef migrations add MyMigration --framework net9.0
dotnet ef database update --framework net9.0
dotnet ef migrations script --framework net9.0
프로젝트 파일이 다음과 같은 경우:
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
EF 도구를 실행할 때 프레임워크 중 하나(예: net9.0)를 선택해야 합니다.
영향이 적은 변경 내용
이제 애플리케이션 이름이 연결 문자열에 삽입됩니다.
새 동작
연결 문자열이 Application Name 없이 EF에 전달되면, EF는 사용 중인 EF 및 SqlClient 버전에 대한 익명 정보를 포함하는 Application Name을 삽입합니다. 대부분의 경우 이는 애플리케이션에 어떤 식으로든 영향을 주지 않지만 일부 에지의 동작에 영향을 줄 수 있습니다. 예를 들어 EF 및 다른 비 EF 데이터 액세스 기술(예: Dapper, ADO.NET)을 사용하여 동일한 데이터베이스에 연결하는 경우 EF가 이제 다른 업데이트된 연결 문자열( Application Name 삽입된 연결 문자열)을 사용하므로 SqlClient는 다른 내부 연결 풀을 사용합니다. 이러한 종류의 혼합 액세스가 한 TransactionScope내에서 수행되는 경우 SqlClient가 두 개의 고유 데이터베이스로 식별하는 두 개의 연결 문자열을 사용했기 때문에 이전에는 필요하지 않았던 분산 트랜잭션으로 에스컬레이션될 수 있습니다.
완화
완화 방법은 연결 문자열에서 Application Name 정의하기만 하면 됩니다. 정의되면 EF는 덮어쓰지 않으며 원래 연결 문자열은 그대로 유지됩니다.
Azure SQL 및 호환성 수준 170에서 기본적으로 사용되는 SQL Server json 데이터 형식
기존 동작
이전에는 데이터베이스의 JSON에 기본 컬렉션 또는 소유 형식을 매핑할 때 SQL Server 공급자가 열에 nvarchar(max) JSON 데이터를 저장했습니다.
public class Blog
{
// ...
// Primitive collection, mapped to nvarchar(max) JSON column
public string[] Tags { get; set; }
// Owned entity type mapped to nvarchar(max) JSON column
public List<Post> Posts { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson());
}
위의 경우 EF는 이전에 다음 테이블을 생성했습니다.
CREATE TABLE [Blogs] (
...
[Tags] nvarchar(max),
[Posts] nvarchar(max)
);
새 동작
EF 10을 사용하여 EF UseAzureSql 를 구성하거나(설명서 참조) 호환성 수준이 170 이상인 EF를 구성하는 경우(설명서 참조) EF는 대신 새 JSON 데이터 형식에 매핑됩니다.
CREATE TABLE [Blogs] (
...
[Tags] json
[Posts] json
);
새 JSON 데이터 형식은 앞으로 SQL Server에 JSON 데이터를 저장하는 권장 방법이지만 전환 nvarchar(max)할 때 몇 가지 동작 차이가 있을 수 있으며 일부 특정 쿼리 양식은 지원되지 않을 수 있습니다. 예를 들어 SQL Server는 JSON 배열을 통해 DISTINCT 연산자를 지원하지 않으며, 그렇게 하려는 쿼리는 실패합니다.
기존 테이블이 있고 사용 UseAzureSql중인 경우 EF 10으로 업그레이드하면 마이그레이션이 생성되어 모든 기존 nvarchar(max) JSON 열 json이 변경됩니다. 이 변경 작업은 지원되며 문제 없이 원활하게 적용되어야 하지만 데이터베이스에 대한 사소한 변경입니다.
왜
SQL Server에서 도입된 새로운 JSON 데이터 형식은 데이터베이스에서 JSON 데이터를 저장하고 상호 작용하는 뛰어난 1급 방법입니다. 특히 성능이 크게 향상되었습니다(설명서 참조). Azure SQL Database 또는 SQL Server 2025를 사용하는 모든 애플리케이션은 새 JSON 데이터 형식으로 마이그레이션하는 것이 좋습니다.
완화
Azure SQL Database를 대상으로 하고 새 JSON 데이터 형식으로 바로 전환하지 않으려는 경우 호환성 수준이 170보다 낮은 EF를 구성할 수 있습니다.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseAzureSql("<connection string>", o => o.UseCompatibilityLevel(160));
}
온-프레미스 SQL Server를 대상으로 하는 경우 기본 호환성 수준은 UseSqlServer 현재 150(SQL Server 2019)이므로 JSON 데이터 형식은 사용되지 않습니다.
또는 특정 속성의 열 형식을 다음으로 명시적으로 설정할 수 있습니다 nvarchar(max).
public class Blog
{
public string[] Tags { get; set; }
public List<Post> Posts { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().PrimitiveCollection(b => b.Tags).HasColumnType("nvarchar(max)");
modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson().HasColumnType("nvarchar(max)"));
modelBuilder.Entity<Blog>().ComplexProperty(e => e.Posts, b => b.ToJson());
}
매개 변수가 있는 컬렉션은 이제 기본적으로 여러 매개 변수를 사용합니다.
기존 동작
EF Core 9 이하에서는 LINQ 쿼리의 매개 변수가 있는 컬렉션(예: 사용된 .Contains()컬렉션)이 기본적으로 JSON 배열 매개 변수를 사용하여 SQL로 변환되었습니다. 다음과 같은 쿼리를 고려해 보세요.
int[] ids = [1, 2, 3];
var blogs = await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();
SQL Server에서 다음과 같은 SQL이 생성되었습니다.
@__ids_0='[1,2,3]'
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (
SELECT [i].[value]
FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)
새 동작
EF Core 10.0부터 매개 변수가 있는 컬렉션은 기본적으로 여러 스칼라 매개 변수를 사용하여 변환됩니다.
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3)
왜
새 기본 변환은 컬렉션에 대한 카디널리티 정보를 쿼리 플래너에 제공하므로 많은 시나리오에서 더 나은 쿼리 계획을 만들 수 있습니다. 여러 매개 변수 접근 방식은 계획 캐시 효율성(매개 변수화)과 쿼리 최적화(카디널리티 제공)간에 균형을 이깁니다.
그러나 워크로드가 다르면 컬렉션 크기, 쿼리 패턴 및 데이터베이스 특성에 따라 다른 변환 전략을 활용할 수 있습니다.
비고
새 기본 변환은 대부분의 경우 동작 변경 또는 성능 회귀를 일으키지는 않지만 쿼리가 SQL로 변환되는 방식의 변경은 일부 시나리오에서 부정적인 결과를 초래할 수 있습니다.
EF Core 8 또는 9로 빌드되고 JSON 배열 매개 변수 변환(사용 OPENJSON 또는 유사한 데이터베이스 관련 함수)의 성능 특성을 사용하는 애플리케이션은 EF Core 10으로 업그레이드할 때 성능 차이가 발생할 수 있습니다. 이는 특히 이전 번역 전략의 이점을 활용한 큰 컬렉션 또는 특정 쿼리 패턴이 있는 쿼리와 관련이 있습니다.
업그레이드 후 성능 회귀가 발생하는 경우 아래의 완화 전략을 사용하여 전역적으로 또는 특정 쿼리에 대해 이전 동작으로 되돌리는 것이 좋습니다.
완화
새 기본 동작(예: 성능 회귀)에 문제가 발생하는 경우 변환 모드를 전역적으로 구성할 수 있습니다.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("<CONNECTION STRING>",
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
사용 가능한 모드는 다음과 같습니다.
-
ParameterTranslationMode.MultipleParameters- 새 기본값(여러 스칼라 매개 변수) -
ParameterTranslationMode.Constant- 값을 상수로 인라인하는 기본 동작(EF8 이전) -
ParameterTranslationMode.Parameter- JSON 배열 매개 변수 사용(EF8-9 기본값)
쿼리별로 변환을 제어할 수도 있습니다.
// Use constants instead of parameters for this specific query
var blogs = await context.Blogs
.Where(b => EF.Constant(ids).Contains(b.Id))
.ToListAsync();
// Use a single parameter (e.g. JSON parameter with OPENJSON) instead of parameters for this specific query
var blogs = await context.Blogs
.Where(b => EF.Parameter(ids).Contains(b.Id))
.ToListAsync();
// Use multiple scalar parameters for this specific query. This is the default in EF 10, but is useful if the default was changed globally:
var blogs = await context.Blogs
.Where(b => EF.MultipleParameters(ids).Contains(b.Id))
.ToListAsync();
매개 변수가 있는 컬렉션 번역에 대한 자세한 내용은 설명서를 참조하세요.
ExecuteUpdateAsync는 이제 정규식이 아닌 람다를 허용합니다.
기존 동작
이전에는 ExecuteUpdate이(가) 열 설정을 위한 표현 트리 인수(Expression<Func<...>>)를 수락했습니다.
새 동작
EF Core 10.0부터 ExecuteUpdate는 열 setter의 비식 인수(Func<...>)를 허용합니다. 열 setter 인수를 동적으로 만들기 위해 식 트리를 빌드하는 경우 코드는 더 이상 컴파일되지 않지만 훨씬 더 간단한 대안으로 바꿀 수 있습니다(아래 참조).
왜
열 setters 매개 변수가 식 트리라는 사실 때문에 열 setter의 동적 생성을 수행하는 것이 매우 어려웠습니다. 여기서 일부 setter는 일부 조건에 따라 존재합니다(예제는 아래 완화 참조).
완화
열 설정자 인수를 동적으로 만들기 위해 식 트리를 구축한 코드는 다시 작성해야 하지만, 결과는 훨씬 더 간단해질 것입니다. 예를 들어 블로그의 보기를 업데이트하고 조건부로 해당 이름을 업데이트한다고 가정해 보겠습니다. setters 인수는 식 트리이므로 다음과 같은 코드를 작성해야 했습니다.
// Base setters - update the Views only
Expression<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>> setters =
s => s.SetProperty(b => b.Views, 8);
// Conditionally add SetProperty(b => b.Name, "foo") to setters, based on the value of nameChanged
if (nameChanged)
{
var blogParameter = Expression.Parameter(typeof(Blog), "b");
setters = Expression.Lambda<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>>(
Expression.Call(
instance: setters.Body,
methodName: nameof(SetPropertyCalls<Blog>.SetProperty),
typeArguments: [typeof(string)],
arguments:
[
Expression.Lambda<Func<Blog, string>>(Expression.Property(blogParameter, nameof(Blog.Name)), blogParameter),
Expression.Constant("foo")
]),
setters.Parameters);
}
await context.Blogs.ExecuteUpdateAsync(setters);
식 트리를 수동으로 만드는 것은 복잡하고 오류가 발생하기 쉬워서 이 일반적인 시나리오를 예상보다 훨씬 어렵게 만듭니다. 이제 EF 10부터 다음을 대신 작성할 수 있습니다.
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged)
{
s.SetProperty(b => b.Name, "foo");
}
});
복합 타입 열 이름은 이제 고유하게 처리됩니다.
기존 동작
이전에는 복합 형식을 테이블 열에 매핑할 때 여러 복합 형식의 여러 속성에 동일한 열 이름이 있는 경우 자동으로 동일한 열을 공유합니다.
새 동작
EF Core 10.0부터는 동일한 이름의 열이 테이블에 있는 경우 복잡한 형식의 열 이름에 숫자를 추가하여 고유해집니다.
왜
이렇게 하면 여러 속성이 의도치 않게 동일한 열에 매핑될 때 발생할 수 있는 데이터 손상이 방지됩니다.
완화
동일한 열을 공유하기 위해 여러 속성이 필요한 경우 다음을 사용하여 Property 명시적으로 구성합니다 HasColumnName.
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
});
중첩된 복합 형식 속성은 열 이름에 전체 경로를 사용합니다.
기존 동작
이전에는 중첩된 복합 형식의 속성이 선언 형식 이름만 사용하여 열에 매핑되었습니다. 예를 들어, EntityType.Complex.NestedComplex.Property는 NestedComplex_Property 열에 매핑되었습니다.
새 동작
EF Core 10.0부터 중첩된 복합 형식의 속성은 열 이름의 일부로 속성의 전체 경로를 사용합니다. 예를 들어 EntityType.Complex.NestedComplex.Property 이제 열 Complex_NestedComplex_Property에 매핑됩니다.
왜
이렇게 하면 열 이름 고유성이 향상되고 어떤 속성이 어떤 열에 매핑되는지 명확하게 파악할 수 있습니다.
완화
이전 열 이름을 유지 관리해야 하는 경우 다음을 사용하여 Property 명시적으로 구성합니다 HasColumnName.
modelBuilder.Entity<EntityType>()
.ComplexProperty(e => e.Complex)
.ComplexProperty(o => o.NestedComplex)
.Property(c => c.Property)
.HasColumnName("NestedComplex_Property");
IDiscriminatorPropertySetConvention 서명이 변경됨
기존 동작
IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet 이전에는 IConventionEntityTypeBuilder을 매개 변수로 사용했습니다.
새 동작
EF Core 10.0부터 메서드 서명이 IConventionTypeBaseBuilder 대신 IConventionEntityTypeBuilder을 사용하도록 변경되었습니다.
왜
이렇게 변경하면 규칙이 엔터티 형식과 복합 형식 모두에서 작동할 수 있습니다.
완화
새 서명을 사용하도록 사용자 지정 규칙 구현을 업데이트합니다.
public virtual void ProcessDiscriminatorPropertySet(
IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
string name,
Type type,
MemberInfo memberInfo,
IConventionContext<IConventionProperty> context)
IRelationalCommandDiagnosticsLogger 메서드는 logCommandText 매개 변수를 추가합니다.
기존 동작
이전에는 IRelationalCommandDiagnosticsLogger, CommandReaderExecuting, CommandReaderExecuted, CommandScalarExecuting 및 다른 메서드에서 실행 중인 데이터베이스 명령을 나타내는 command 매개 변수를 수락했습니다.
새 동작
EF Core 10.0부터 이러한 메서드에는 이제 추가 logCommandText 매개 변수가 필요합니다. 이 매개 변수에는 기록될 SQL 명령 텍스트가 포함되며, 활성화되지 않은 경우 EnableSensitiveDataLogging 중요한 데이터가 수정될 수 있습니다.
왜
이 변경은 기본적으로 인라인 상수의 로깅을 수정하는 새로운 기능을 지원합니다. EF가 매개 변수 값을 SQL로 인라인하는 경우(예: 사용하는 EF.Constant()경우) 중요한 데이터 로깅을 명시적으로 사용하도록 설정하지 않는 한 해당 값은 이제 로그에서 수정됩니다.
logCommandText 매개 변수는 로깅 목적의 수정된 SQL을 제공하며, command 매개 변수는 실행되는 실제 SQL을 포함합니다.
완화
사용자 지정 구현 IRelationalCommandDiagnosticsLogger이 있는 경우 새 logCommandText 매개 변수를 포함하도록 메서드 서명을 업데이트해야 합니다. 다음은 그 예입니다.
public InterceptionResult<DbDataReader> CommandReaderExecuting(
IRelationalConnection connection,
DbCommand command,
DbContext context,
Guid commandId,
Guid connectionId,
DateTimeOffset startTime,
string logCommandText) // New parameter
{
// Use logCommandText for logging purposes
// Use command for execution-related logic
}
매개 변수에는 logCommandText 기록할 SQL(인라인 상수가 잠재적으로 수정됨)이 포함되고 command.CommandText 데이터베이스에 대해 실행될 실제 SQL이 포함됩니다.
Microsoft.Data.Sqlite 호환성에 영향을 미치는 변경 사항
요약
| 중대한 변경 | 영향 |
|---|---|
| 이제 오프셋 없이 GetDateTimeOffset을 사용하여 UTC를 가정합니다. | 높음 |
| 이제 DATETimeOffset을 REAL 열에 쓰면 UTC로 작성됩니다. | 높음 |
| 이제 오프셋과 함께 GetDateTime을 사용하면 값이 UTC로 반환됩니다. | 높음 |
높은 영향을 주는 변경 내용
이제 오프셋 없이 GetDateTimeOffset을 사용하여 UTC를 가정합니다.
기존 동작
이전에는 오프셋이 없는 텍스트 타임스탬프(예: GetDateTimeOffsetMicrosoft.Data.Sqlite)에서 사용할 2014-04-15 10:47:16 때 값이 현지 표준 시간대에 있다고 가정했습니다. 즉, 값이 2014-04-15 10:47:16+02:00로 구문 분석되었습니다 (현지 표준 시간대가 UTC+2일 경우).
새 동작
Microsoft.Data.Sqlite 10.0부터 오프셋이 없는 텍스트 타임스탬프에서 사용하는 GetDateTimeOffset 경우 Microsoft.Data.Sqlite는 값이 UTC로 있다고 가정합니다.
왜
오프셋이 없는 타임스탬프가 UTC로 처리되는 SQLite의 동작에 맞춰야 합니다.
완화
코드는 그에 따라 조정되어야 합니다.
마지막/임시 수단으로 AppContext 스위치Microsoft.Data.Sqlite.Pre10TimeZoneHandling를 설정 true 하여 이전 동작으로 되돌릴 수 있습니다. 자세한 내용은 라이브러리 소비자에 대한 AppContext를 참조하세요.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);
이제 DATETimeOffset을 REAL 열에 쓰면 UTC로 작성됩니다.
기존 동작
이전에는 Microsoft.Data.Sqlite에서 REAL 열에 값을 DateTimeOffset 기록할 때 오프셋을 고려하지 않고 값을 기록했습니다.
새 동작
Microsoft.Data.Sqlite 10.0부터, DateTimeOffset 값을 REAL 열에 쓸 때, Microsoft.Data.Sqlite는 값을 UTC로 변환한 후에 변환 작업을 수행하고 기록합니다.
왜
작성된 값이 잘못되었습니다. REAL 타임스탬프가 UTC로 계산되는 SQLite의 동작과 일치하지 않습니다.
완화
코드는 그에 따라 조정되어야 합니다.
마지막/임시 수단으로 AppContext 스위치Microsoft.Data.Sqlite.Pre10TimeZoneHandling를 설정 true 하여 이전 동작으로 되돌릴 수 있습니다. 자세한 내용은 라이브러리 소비자에 대한 AppContext를 참조하세요.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);
이제 오프셋과 함께 GetDateTime을 사용하면 값이 UTC로 반환됩니다.
기존 동작
이전에 Microsoft.Data.Sqlite에서 오프셋이 있는 텍스트 타임스탬프(예: GetDateTime)에 2014-04-15 10:47:16+02:00를 사용할 때, 오프셋이 로컬이 아니어도 DateTimeKind.Local의 값을 반환했습니다. 오프셋을 고려하여 시간을 올바르게 분석했습니다.
새 동작
Microsoft.Data.Sqlite 10.0부터 오프셋이 있는 텍스트 타임스탬프에서 사용하는 GetDateTime 경우 Microsoft.Data.Sqlite는 값을 UTC로 변환하고 함께 반환 DateTimeKind.Utc합니다.
왜
시간이 올바르게 구문 분석되었지만 컴퓨터가 구성한 현지 표준 시간대에 따라 달라졌으므로 예기치 않은 결과가 발생할 수 있습니다.
완화
코드는 그에 따라 조정되어야 합니다.
마지막/임시 수단으로 AppContext 스위치Microsoft.Data.Sqlite.Pre10TimeZoneHandling를 설정 true 하여 이전 동작으로 되돌릴 수 있습니다. 자세한 내용은 라이브러리 소비자에 대한 AppContext를 참조하세요.
AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);
.NET