다음을 통해 공유


EF Core 5.0의 새로운 기능

다음 목록에는 EF Core 5.0의 주요한 새 기능이 포함되어 있습니다. 릴리스의 전체 이슈 목록은 이슈 추적기를 참조하세요.

주요 릴리스인 EF Core 5.0에는 기존 애플리케이션에 부정적인 영향을 줄 수 있는 API 개선이나 동작 변경에 해당하는 호환성이 손상되는 변경도 다수 포함되어 있습니다.

다대다

EF Core 5.0은 조인 테이블을 명시적으로 매핑하지 않고 다 대 다 관계를 지원합니다.

예를 들어 다음과 같은 엔터티 형식을 고려합니다.

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }
    public ICollection<Post> Posts { get; set; }
}

EF Core 5.0은 이를 규칙에 따른 다 대 다 관계로 인식하며 데이터베이스에 PostTag 조인 테이블을 자동으로 만듭니다. 조인 테이블을 명시적으로 참조하지 않고도 데이터를 쿼리하고 업데이트할 수 있으므로 코드가 크게 간소화됩니다. 필요시 조인 테이블을 계속 명시적으로 사용자 지정하고 쿼리할 수 있습니다.

자세한 내용은 다 대 다에 관한 전체 설명서를 참조하세요.

분할 쿼리

EF Core 3.0부터 EF Core에서는 항상 각 LINQ 쿼리에 단일 SQL 쿼리를 생성합니다. 따라서 사용 중인 트랜잭션 모드의 제약 조건 내에서 반환된 데이터의 일관성이 보장됩니다. 그러나 쿼리에서 Include 또는 프로젝션을 사용하여 여러 관련 컬렉션을 다시 가져오는 경우 매우 느려질 수 있습니다.

이제 EF Core 5.0에서는 관련 컬렉션을 포함해 단일 LINQ 쿼리를 여러 SQL 쿼리로 분할할 수 있습니다. 이렇게 하면 성능을 크게 향상할 수 있지만 두 쿼리 사이에 데이터가 변경되는 경우 반환되는 결과가 일치하지 않을 수 있습니다. 직렬화 가능 트랜잭션 또는 스냅샷 트랜잭션을 사용하면 분할 쿼리에서 이 문제를 완화하고 일관성을 얻을 수 있지만 다른 성능 비용과 동작 차이를 초래할 수 있습니다.

예를 들어 Include를 사용하여 두 가지 수준의 관련 컬렉션을 가져오는 쿼리를 살펴보겠습니다.

var artists = context.Artists
    .Include(e => e.Albums)
    .ToList();

기본적으로 EF Core에서는 SQLite 공급자를 사용할 때 다음 SQL을 생성합니다.

SELECT a."Id", a."Name", a0."Id", a0."ArtistId", a0."Title"
FROM "Artists" AS a
LEFT JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id", a0."Id"

분할 쿼리를 사용하면 다음 SQL이 대신 생성됩니다.

SELECT a."Id", a."Name"
FROM "Artists" AS a
ORDER BY a."Id"

SELECT a0."Id", a0."ArtistId", a0."Title", a."Id"
FROM "Artists" AS a
INNER JOIN "Album" AS a0 ON a."Id" = a0."ArtistId"
ORDER BY a."Id"

분할 쿼리는 새 AsSplitQuery 연산자를 LINQ 쿼리의 아무 곳에나 배치하거나 모델의 OnConfiguring에 전역으로 배치하여 사용할 수 있습니다. 자세한 내용은 분할 쿼리에 관한 전체 설명서를 참조하세요.

간단한 로깅 및 향상된 진단

EF Core 5.0은 새 LogTo 메서드를 통해 로깅을 설정하는 간단한 방법을 도입합니다. 다음을 실행하면 EF Core에서 생성된 모든 SQL을 포함하여 로깅 메시지가 콘솔에 작성됩니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

또한 이제 모든 LINQ 쿼리에서 ToQueryString을 호출하여 쿼리가 실행할 SQL을 검색할 수 있습니다.

Console.WriteLine(
    ctx.Artists
    .Where(a => a.Name == "Pink Floyd")
    .ToQueryString());

마지막으로, 내부 요소를 자세히 보여 주는 향상된 DebugView 속성에 맞게 다양한 EF Core 형식이 조정되었습니다. 예를 들어 ChangeTracker.DebugView를 참조하여 특정 시점에 추적되는 엔터티를 정확하게 확인할 수 있습니다.

자세한 내용은 로깅 및 인터셉션에 관한 설명서를 참조하세요.

필터링 적용 포함

이제 Include 메서드는 포함되는 엔터티의 필터링을 지원합니다.

var blogs = context.Blogs
    .Include(e => e.Posts.Where(p => p.Title.Contains("Cheese")))
    .ToList();

이 쿼리는 게시물 제목에 “Cheese”가 포함된 경우에만 각 관련 게시물과 함께 블로그를 반환합니다.

자세한 내용은 필터링된 전체 설명서를 참조하세요.

TPT(형식당 테이블) 매핑

기본적으로 EF Core는 .NET 형식의 상속 계층 구조를 단일 데이터베이스 테이블에 매핑합니다. 이를 TPH(계층 구조당 하나의 테이블) 매핑이라고 합니다. EF Core 5.0에서는 상속 계층 구조의 각 .NET 형식을 다른 데이터베이스 테이블에 매핑할 수 있으며, 이를 TPT(형식당 하나의 테이블) 매핑이라고 합니다.

예를 들어 매핑된 계층 구조가 포함된 다음 모델을 살펴보겠습니다.

public class Animal
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Cat : Animal
{
    public string EducationLevel { get; set; }
}

public class Dog : Animal
{
    public string FavoriteToy { get; set; }
}

TPT를 사용하면 계층 구조에서 각 형식에 해당하는 데이터베이스 테이블이 생성됩니다.

CREATE TABLE [Animals] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NULL,
    CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);

CREATE TABLE [Cats] (
    [Id] int NOT NULL,
    [EducationLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Cats_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);

CREATE TABLE [Dogs] (
    [Id] int NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Dogs_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE NO ACTION,
);

자세한 내용은 TPT에 관한 전체 설명서를 참조하세요.

유연한 엔터티 매핑

엔터티 형식은 일반적으로 테이블이나 뷰에 매핑되어 EF Core가 해당 형식을 쿼리할 때 테이블이나 뷰의 콘텐츠를 다시 끌어오도록 합니다. EF Core 5.0은 엔터티를 SQL 쿼리(“정의 쿼리”라고 함) 또는 TVF(테이블 반환 함수)에 매핑할 수 있는 추가 매핑 옵션을 추가합니다.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>().ToSqlQuery(
        @"SELECT Id, Name, Category, BlogId FROM posts
          UNION ALL
          SELECT Id, Name, ""Legacy"", BlogId from legacy_posts");

    modelBuilder.Entity<Blog>().ToFunction("BlogsReturningFunction");
}

테이블 반환 함수를 DbSet 대신 .NET 메서드에 매핑하여 매개 변수를 전달할 수도 있으며 HasDbFunction을 사용하여 매핑을 설정할 수 있습니다.

마지막으로, 엔터티는 쿼리할 경우 뷰에(또는 함수나 정의 쿼리에) 매핑할 수 있지만 업데이트할 경우 테이블에 매핑할 수 있습니다.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Blog>()
        .ToTable("Blogs")
        .ToView("BlogsView");
}

공유 형식 엔터티 형식 및 속성 모음

EF Core 5.0에서는 동일한 CLR 형식을 여러 가지 다른 엔터티 형식에 매핑할 수 있으며 해당 형식을 공유 형식 엔터티 형식이라고 합니다. 이 기능에서는 모든 CLR 형식을 사용할 수 있지만 .NET Dictionary는 “속성 모음”이라는 특별히 주목할 만한 사용 사례를 제공합니다.

public class ProductsContext : DbContext
{
    public DbSet<Dictionary<string, object>> Products => Set<Dictionary<string, object>>("Product");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Product", b =>
        {
            b.IndexerProperty<int>("Id");
            b.IndexerProperty<string>("Name").IsRequired();
            b.IndexerProperty<decimal>("Price");
        });
    }
}

해당 엔터티는 고유한 전용 CLR 형식을 사용하여 일반 엔터티 형식처럼 쿼리하고 업데이트할 수 있습니다. 자세한 내용은 속성 모음에 관한 설명서를 참조하세요.

1:1 종속 항목 필요

EF Core 3.1에서 일 대 일 관계의 종속 끝은 항상 선택 사항으로 간주되었습니다. 이는 소유한 엔터티를 사용하는 경우 가장 분명하게 드러났습니다. 소유한 엔터티의 열이 모델에서 필수로 구성된 경우에도 해당 열은 모두 데이터베이스에서 null 허용으로 생성되었기 때문입니다.

EF Core 5.0에서는 소유한 엔터티 탐색을 필수 종속 항목으로 구성할 수 있습니다. 예시:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });
        b.Navigation(e => e.HomeAddress).IsRequired();
    });
}

DbContextFactory

EF Core 5.0은 애플리케이션의 DI(종속성 주입) 컨테이너에서 DbContext 인스턴스를 만들기 위해 팩터리를 등록하는 AddDbContextFactoryAddPooledDbContextFactory를 도입하며, 이는 애플리케이션 코드가 컨텍스트 인스턴스를 수동으로 만들고 삭제해야 하는 경우 유용할 수 있습니다.

services.AddDbContextFactory<SomeDbContext>(b =>
    b.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));

이때 ASP.NET Core 컨트롤러와 같은 애플리케이션 서비스에 IDbContextFactory<TContext>를 주입할 수 있으며, 이를 사용하여 컨텍스트 인스턴스를 인스턴스화할 수 있습니다.

public class MyController : Controller
{
    private readonly IDbContextFactory<SomeDbContext> _contextFactory;

    public MyController(IDbContextFactory<SomeDbContext> contextFactory)
        => _contextFactory = contextFactory;

    public void DoSomeThing()
    {
        using (var context = _contextFactory.CreateDbContext())
        {
            // ...
        }
    }
}

자세한 내용은 DbContextFactory에 관한 전체 설명서를 참조하세요.

SQLite 테이블 다시 빌드

다른 데이터베이스와 비교할 때 SQLite는 해당 스키마 조작 기능에서 비교적 제한적입니다. 예를 들어 기존 테이블에서 열을 삭제하는 작업이 지원되지 않습니다. EF Core 5.0은 자동으로 새 테이블을 만들고, 이전 테이블에서 데이터를 복사하고, 이전 테이블을 삭제하며, 새 테이블의 이름을 바꿔서 해당 제한 사항을 해결합니다. 이렇게 하면 테이블이 “다시 빌드”되고 이전에 지원되지 않던 마이그레이션 작업을 안전하게 적용할 수 있습니다.

현재 테이블 다시 빌드를 통해 지원되는 마이그레이션 작업에 관한 자세한 내용은 관련 설명서 페이지를 참조하세요.

데이터베이스 데이터 정렬

EF Core 5.0은 데이터베이스, 열 또는 쿼리 수준에서 텍스트 데이터 정렬을 지정할 수 있도록 지원합니다. 이를 통해 유연하면서도 쿼리 성능을 낮추지 않는 방식으로 대/소문자 구분 및 기타 텍스트 측면을 구성할 수 있습니다.

예를 들어 다음은 SQL Server에서 대/소문자를 구분하도록 Name 열을 구성하며 열에 생성된 모든 인덱스는 해당 구성에 따라 작동합니다.

modelBuilder
    .Entity<User>()
    .Property(e => e.Name)
    .UseCollation("SQL_Latin1_General_CP1_CS_AS");

자세한 내용은 데이터 정렬 및 대/소문자 구분에 관한 전체 설명서를 참조하세요.

이벤트 카운터

EF Core 5.0은 애플리케이션 성능을 추적하고 다양한 변칙을 파악하는 데 사용할 수 있는 이벤트 카운터를 공개합니다. dotnet-counters 도구를 사용하여 EF를 실행하는 프로세스에 연결하기만 하면 됩니다.

> dotnet counters monitor Microsoft.EntityFrameworkCore -p 49496

[Microsoft.EntityFrameworkCore]
    Active DbContexts                                               1
    Execution Strategy Operation Failures (Count / 1 sec)           0
    Execution Strategy Operation Failures (Total)                   0
    Optimistic Concurrency Failures (Count / 1 sec)                 0
    Optimistic Concurrency Failures (Total)                         0
    Queries (Count / 1 sec)                                     1,755
    Queries (Total)                                            98,402
    Query Cache Hit Rate (%)                                      100
    SaveChanges (Count / 1 sec)                                     0
    SaveChanges (Total)                                             1

자세한 내용은 이벤트 카운터에 관한 전체 설명서를 참조하세요.

기타 기능

모델 빌드

  • 모델 빌드 API는 값 비교자를 더 쉽게 구성하기 위해 도입되었습니다.
  • 이제 계산 열을 ‘저장’ 또는 ‘가상’으로 구성할 수 있습니다.
  • 이제 흐름 API를 통해 전체 자릿수 및 소수 자릿수를 구성할 수 있습니다.
  • 탐색 속성에 관한 새로운 모델 빌드 API가 도입되었습니다.
  • 속성과 유사하게 필드에 관한 새로운 모델 빌드 API가 도입되었습니다.
  • 이제 .NET PhysicalAddressIPAddress 형식을 데이터베이스 문자열 열에 매핑할 수 있습니다.
  • 이제 새로운 [BackingField] 특성을 통해 지원 필드를 구성할 수 있습니다.
  • 이제 null 허용 지원 필드가 허용되므로 CLR 기본값이 적절한 센티널 값(주목할 만한 bool)이 아닌 경우 저장소 생성 기본값 지원이 개선됩니다.
  • 흐름 API를 사용하는 대신 엔터티 형식에서 새 [Index] 특성을 사용하여 인덱스를 지정할 수 있습니다.
  • [Keyless] 특성을 사용하여 키가 없는 엔터티 형식을 구성할 수 있습니다.
  • 기본적으로 EF Core는 이제 판별자를 ‘완료’로 간주합니다. 즉, EF Core는 판별자 값이 모델에서 애플리케이션에 의해 구성되지 않는 경우가 없을 것이라고 예상합니다. 이를 통해 일부 성능을 개선할 수 있으며 판별자 열에 알 수 없는 값이 포함된 경우 해당 구성을 사용하지 않도록 설정할 수 있습니다.

쿼리

  • 이제 쿼리 변환 실패 예외에는 문제를 파악하기 위해 실패 원인에 관한 더욱 명시적인 원인이 포함됩니다.
  • 이제 비 추적 쿼리는 ID 확인을 수행하여 동일한 데이터베이스 개체에 대해 여러 개의 엔터티 인스턴스가 반환되지 않도록 합니다.
  • 조건부 집계를 사용하여 GroupBy 지원이 추가되었습니다(예: GroupBy(o => o.OrderDate).Select(g => g.Count(i => i.OrderDate != null))).
  • 집계 전에 그룹 요소를 통해 Distinct 연산자를 변환하는 지원이 추가되었습니다.
  • Reverse를 변환합니다.
  • SQL Server의 DateTime 관련 변환이 개선되었습니다(예: DateDiffWeek, DateFromParts).
  • 바이트 배열에서 새 메서드를 변환합니다(예: Contains, Length, SequenceEqual).
  • 2의 보수와 같은 일부 추가 비트 연산자를 변환합니다.
  • 문자열을 통해 FirstOrDefault를 변환합니다.
  • null 의미 체계 관련 쿼리 변환이 개선되어 더 긴밀하고 효율적인 쿼리가 생성됩니다.
  • 이제 사용자 매핑 함수에 주석을 달아 null 전파를 제어할 수 있으므로 다시 더 긴밀하고 효율적인 쿼리가 생성됩니다.
  • CASE 블록을 포함하는 SQL은 이제 훨씬 더 간결합니다.
  • 이제 새 EF.Functions.DataLength 메서드를 통해 쿼리에서 SQL Server DATALENGTH 함수를 호출할 수 있습니다.
  • EnableDetailedErrors예외에 추가 세부 정보를 추가합니다.

저장

  • SaveChanges 인터셉션이벤트
  • 트랜잭션 저장점을 제어하기 위한 API가 도입되었습니다. 또한 EF Core는 SaveChanges가 호출되고 트랜잭션이 이미 진행 중인 경우 자동으로 저장점을 만들고 오류 발생 시 저장점을 롤백합니다.
  • 애플리케이션에서 트랜잭션 ID를 명시적으로 설정할 수 있어 로깅 및 기타 위치에서 트랜잭션 이벤트의 상관 관계를 더 쉽게 형성할 수 있습니다.
  • SQL Server의 기본 최대 일괄 처리 크기가 일괄 처리 성능 분석에 따라 42로 변경되었습니다.

마이그레이션 및 스캐폴딩

  • 이제 테이블을 마이그레이션에서 제외할 수 있습니다.
  • 이제 새 dotnet ef migrations list 명령이 아직 데이터베이스에 적용되지 않은 마이그레이션을 보여 줍니다(Get-Migration은 패키지 관리 콘솔에서 동일한 작업을 수행함).
  • 이제 마이그레이션 스크립트에는 마이그레이션 애플리케이션이 실패하는 처리 사례를 향상하기 위해 필요시 트랜잭션 문이 포함됩니다.
  • 이제 매핑되지 않은 기본 클래스에 대한 열은 매핑된 엔터티 형식에 대한 다른 열 다음으로 순서가 지정됩니다. 이 기능은 새로 만든 테이블에만 영향을 주며 기존 테이블의 열 순서는 변경되지 않습니다.
  • 이제 마이그레이션 생성에서는 생성되는 마이그레이션이 idempotent인지와 출력을 즉시 실행할지 아니면 스크립트로 생성할지를 인식할 수 있습니다.
  • 마이그레이션스캐폴딩에서 네임스페이스를 지정하기 위한 새 명령줄 매개 변수가 추가되었습니다.
  • dotnet ef database update 명령은 이제 연결 문자열을 지정하기 위한 새 --connection 매개 변수를 허용합니다.
  • 이제 기존 데이터베이스를 스캐폴딩하면 테이블 이름이 단수화되므로 PeopleAddresses라는 테이블이 PersonAddress라는 엔터티 형식으로 스캐폴딩됩니다. 원래 데이터베이스 이름은 계속 유지될 수 있습니다.
  • --no-onconfiguring 옵션은 모델을 스캐폴딩할 때 OnConfiguring을 제외하도록 EF Core에 지시할 수 있습니다.

Azure Cosmos DB

Sqlite

  • 이제 계산 열이 지원됩니다.
  • 이제 SqliteBlob 및 스트림을 사용하여 GetBytes, GetChars 및 GetTextReader를 사용한 이진 및 문자열 데이터 검색을 보다 효율적으로 진행할 수 있습니다.
  • 이제 SqliteConnection의 초기화가 지연됩니다.

기타

  • INotifyPropertyChangingINotifyPropertyChanged를 자동으로 구현하는 변경 내용 추적 프록시를 생성할 수 있습니다. 이 방법은 SaveChanges를 호출할 때 변경 내용을 검색하지 않는 변경 내용 추적 대신 사용할 수 있습니다.
  • 이제 이미 초기화된 DbContext에서 DbConnection 또는 연결 문자열을 변경할 수 있습니다.
  • 새로운 ChangeTracker.Clear 메서드가 추적되는 모든 엔터티의 DbContext를 삭제합니다. 각 작업 단위에 대해 새로운 단기 컨텍스트 인스턴스를 만드는 모범 사례를 사용할 때는 일반적으로 이 작업은 필요하지 않습니다. 그러나 DbContext 인스턴스의 상태를 다시 설정해야 하는 경우에는 새 Clear() 메서드를 사용하는 것이 모든 엔터티를 대량 분리하는 것보다 더 효율적이고 강력합니다.
  • 이제 EF Core 명령줄 도구에서 ASPNETCORE_ENVIRONMENTDOTNET_ENVIRONMENT 환경 변수를 “Development”로 자동으로 구성합니다. 따라서 개발 중 ASP.NET Core 환경에서 제네릭 호스트를 사용하면 환경이 제공됩니다.
  • 사용자 지정 명령줄 인수를 IDesignTimeDbContextFactory<TContext>로 이동할 수 있어 애플리케이션이 컨텍스트를 만들고 초기화하는 방법을 제어할 수 있습니다.
  • 이제 인덱스 채우기 비율을 SQL Server에서 구성할 수 있습니다.
  • IsRelational 속성을 사용하여 관계형 공급자와 비 관계형 공급자(예: in-Memory)를 사용하는 경우를 구분할 수 있습니다.