EF Core 6 の新機能まとめ(その2)

EF Core 6 の新機能まとめ(その2)

前回に続き、今回はEF Core 6の新機能10個をお届けします。値コンバーター、スキャフォールディング、DbContextの改善など。

最終更新 2022/06/02 21:51
liamwang 精致码农
読了目安 9 分
カテゴリ
EF Core
タグ
.NET C# EF Core ORM

前の 記事 に引き続き、この記事では EF Core 6 の新機能を10個紹介します。値コンバーター、スキャフォールディング、DbContext の改善などが含まれます。

1 HasConversion で値コンバーターをサポート

EF Core 6.0 では、HasConversion メソッドのジェネリックオーバーロードを使用して、組み込みまたはカスタムの値コンバーターを指定できます。

public class ExampleContext : DbContext
{
    public DbSet<Person> People { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Person>()
            .Property(p => p.Address)
            .HasConversion<AddressConverter>();
    }
}
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address Address { get; set; }
}
public class Address
{
    public string Country { get; set; }
    public string Street { get; set; }
    public string ZipCode { get; set; }
}
public class AddressConverter : ValueConverter<Address, string>
{
    public AddressConverter()
        : base(
            v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
            v => JsonSerializer.Deserialize<Address>(v, (JsonSerializerOptions)null))
    {
    }
}

2 多対多リレーションの設定を簡素化

EF Core 6.0 から、多対多リレーションで結合エンティティを構成する際に、追加の設定が不要になりました。また、左右のリレーションを明示的に指定せずに結合エンティティを構成できるようになりました。

public class BloggingContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }
    public DbSet<PostTag> PostTags { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(t => t.Posts)
            .UsingEntity<PostTag>();
    }
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EFCore6Many2Many;Trusted_Connection=True;");
}
public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Tag> Tags { get; set; } = new List<Tag>();
}
public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }
    public List<Post> Posts { get; set; } = new List<Post>();
}
public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
    public DateTime AddedDate { get; set; }
}

3 スキャフォールディングの多対多リレーションの改善

EF Core 6.0 では、既存のデータベースのスキャフォールディングが改善されました。結合テーブルを検出し、多対多のマッピングを生成します。

以下のようなデータベース例:

CLI を使用:

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=EFCore6Many2Many" Microsoft.EntityFrameworkCore.SqlServer --context ExampleContext --output-dir Models

生成された DbContextOnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>(entity =>
    {
        entity.HasMany(d => d.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<Dictionary<string, object>>(
                "PostTag",
                l => l.HasOne<Tag>().WithMany().HasForeignKey("TagId"),
                r => r.HasOne<Post>().WithMany().HasForeignKey("PostId"),
                j =>
                {
                    j.HasKey("PostId", "TagId")
                    j.ToTable("PostTags")
                    j.HasIndex(new[] { "TagId" }, "IX_PostTags_TagId");
                });
    });

    OnModelCreatingPartial(modelBuilder);
}

4 スキャフォールディングで null 許容参照型を生成

EF Core 6.0 では、既存のデータベースのスキャフォールディングが改善されました。プロジェクトで null 許容参照型(NRT)が有効になっている場合、EF Core は自動的に NRT を使用して DbContext とエンティティ型を構築します。

例のテーブル:

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [Description] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id])
)

生成されるモデル:

public partial class Post
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Desciption { get; set; }
}

5 スキャフォールディングでデータベースコメントを生成

EF Core 6.0 では、データベースコメントとコードコメントが関連付けられます。

データベース例:

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [Description] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));
EXEC sp_addextendedproperty
    @name = N'MS_Description', @value = 'The post table',
    @level0type = N'Schema', @level0name = dbo,
    @level1type = N'Table',  @level1name = Posts
EXEC sp_addextendedproperty
    @name = N'MS_Description', @value = 'The post identifier',
    @level0type = N'Schema', @level0name = dbo,
    @level1type = N'Table',  @level1name = Posts,
    @level2type = N'Column', @level2name = [Id];
EXEC sp_addextendedproperty
    @name = N'MS_Description', @value = 'The post name',
    @level0type = N'Schema', @level0name = dbo,
    @level1type = N'Table',  @level1name = Posts,
    @level2type = N'Column', @level2name = [Name];
EXEC sp_addextendedproperty
    @name = N'MS_Description', @value = 'The description name',
    @level0type = N'Schema', @level0name = dbo,
    @level1type = N'Table',  @level1name = Posts,
    @level2type = N'Column', @level2name = [Description];

生成されるモデル:

/// <summary>
/// The post table
/// </summary>
public partial class Post
{
    /// <summary>
    /// The post identifier
    /// </summary>
    public int Id { get; set; }
    /// <summary>
    /// The post name
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// The description name
    /// </summary>
    public string Description { get; set; }
}

6 AddDbContextFactory で DbContext を登録

EF Core 5.0 では、DbContext インスタンスを手動で作成するためのファクトリを登録できました。EF Core 6.0 からは、AddDbContextFactory を使用して DbContext を登録できます。これにより、必要に応じてファクトリと DbContext の両方を同時に注入できます。

var serviceProvider = new ServiceCollection()
    .AddDbContextFactory<ExampleContext>(builder =>
        builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database = EFCore6Playground"))
    .BuildServiceProvider();

var factory = serviceProvider.GetService<IDbContextFactory<ExampleContext>>();
using (var context = factory.CreateDbContext())
{
    // ファクトリから取得したコンテキストは明示的に破棄する必要があります
}

using (var scope = serviceProvider.CreateScope())
{
    var context = scope.ServiceProvider.GetService<ExampleContext>();
    // スコープが破棄されるとコンテキストも破棄されます
}
class ExampleContext : DbContext
{ }

7 依存性注入なしの DbContext プール

EF Core 6.0 では、依存性注入を使用せずに DbContext プールを利用できます。PooledDbContextFactory 型が public として定義されました。プールは DbContextOptions で作成され、DbContext インスタンスの作成に使用されます。

var options = new DbContextOptionsBuilder<ExampleContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCore6Playground")
    .Options;

var factory = new PooledDbContextFactory<ExampleContext>(options);

using var context1 = factory.CreateDbContext();
Console.WriteLine($"Created DbContext with ID {context1.ContextId}");
// 出力: Created DbContext with ID e49db9b7-a0b0-4b54-8d0d-2cbd6c4cece7:1

using var context2 = factory.CreateDbContext();
Console.WriteLine($"Created DbContext with ID {context2.ContextId}");
// 出力: Created DbContext with ID b5a35bcb-270d-40f1-b668-5f76da1f35ad:1

class ExampleContext : DbContext
{
    public ExampleContext(DbContextOptions<ExampleContext> options)
        : base(options)
    {
    }
}

8 CommandSource 列挙型

EF Core 6.0 では、新しい列挙型 CommandSourceCommandEventData 型に追加され、診断ソースとインターセプターに提供されます。この列挙値は、EF のどの部分がこのコマンドを作成したかを示します。

Db コマンドインターセプターでの CommandSource の使用:

class ExampleInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command,
        CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        if (eventData.CommandSource == CommandSource.SaveChanges)
        {
            Console.WriteLine($"Saving changes for {eventData.Context.GetType().Name}:");
            Console.WriteLine();
            Console.WriteLine(command.CommandText);
        }

        if (eventData.CommandSource == CommandSource.FromSqlQuery)
        {
            Console.WriteLine($"From Sql query for {eventData.Context.GetType().Name}:");
            Console.WriteLine();
            Console.WriteLine(command.CommandText);
        }

        return result;
    }
}

DbContext:

class ExampleContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCore6CommandSource")
        .AddInterceptors(new ExampleInterceptor());
}
class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Program:

using var context = new ExampleContext();

context.Products.Add(new Product { Name = "Laptop", Price = 1000 });
context.SaveChanges();

var product = context.Products
    .FromSqlRaw("SELECT * FROM dbo.Products")
    .ToList();

/* 出力:
Saving changes for ExampleContext:

SET NOCOUNT ON;
INSERT INTO[Products] ([Name], [Price])
VALUES(@p0, @p1);
SELECT[Id]
FROM[Products]
WHERE @@ROWCOUNT = 1 AND[Id] = scope_identity();


From Sql query for ExampleContext:

SELECT* FROM dbo.Products
*/

9 値コンバーターで null を変換可能

EF Core 6.0 では、値コンバーターで null を変換できるようになりました。これは、未知の値を持つ列挙型があり、テーブル内で null 許容の文字列列として表現される場合に便利です。

public class ExampleContext : DbContext
{
    public DbSet<Dog> Dogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Dog>()
            .Property(c => c.Breed)
            .HasConversion<BreedConverter>();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .LogTo(Console.WriteLine)
            .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EFCore6ValueConverterAllowsNulls;");
    }
}
public enum Breed
{
    Unknown,
    Beagle,
    Bulldog
}
public class Dog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Breed? Breed { get; set; }
}
public class BreedConverter : ValueConverter<Breed, string>
{
#pragma warning disable EF1001
    public BreedConverter()
        : base(
            v => v == Breed.Unknown ? null : v.ToString(),
            v => v == null ? Breed.Unknown : Enum.Parse<Breed>(v),
            convertsNulls: true)
    {
    }
#pragma warning restore EF1001
}

ただし、これには落とし穴があります。詳細はリンク先を参照してください:https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/whatsnew#allow-value-converters-to-convert-nulls

10 一時値を明示的に設定

EF Core 6.0 では、エンティティが追跡される前に一時値を明示的に設定できます。値が一時値としてマークされている場合、EF は以前のようにそれをリセットしません。

using var context = new ExampleContext();

Blog blog = new Blog { Id = -5 };
context.Add(blog).Property(p => p.Id).IsTemporary = true;

var post1 = new Post { Id = -1 };
var post1IdEntry = context.Add(post1).Property(e => e.Id).IsTemporary = true;
post1.BlogId = blog.Id;

var post2 = new Post();
var post2IdEntry = context.Add(post2).Property(e => e.Id).IsTemporary = true;
post2.BlogId = blog.Id;

Console.WriteLine($"Blog explicitly set temporary ID = {blog.Id}");
Console.WriteLine($"Post 1 explicitly set temporary ID = {post1.Id} and FK to Blog = {post1.BlogId}");
Console.WriteLine($"Post 2 generated temporary ID = {post2.Id} and FK to Blog = {post2.BlogId}");

// 出力:
// Blog explicitly set temporary ID = -5
// Post 1 explicitly set temporary ID = -1 and FK to Blog = -5
// Post 2 generated temporary ID = -2147482647 and FK to Blog = -5

class Blog
{
    public int Id { get; set; }
}
class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; }
}
class ExampleContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCore6TempValues");
}

11 まとめ

この記事のすべてのコードサンプルは、私の GitHub で入手できます: https://github.com/okyrylchuk/dotnet6_features/tree/main/EF%20Core%206#miscellaneous-enhancements

原文:https://blog.okyrylchuk.dev/entity-framework-core-6-features-part-2

著者:Oleg Kyrylchuk

翻訳:精致码农

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2022/06/02

EF Core 6 新機能まとめ(一)

この記事では、EF Core 6の10の新機能をご紹介します。新しい属性アノテーション、テンポラルテーブル、スパースカラムのサポートなど、その他の新機能が含まれます。

続きを読む