EF Core 2.0 の新機能

.NET Standard 2.0

EF Core は .NET Standard 2.0 をターゲットにするようになりました。つまり、.NET Core 2.0、.NET Framework 4.6.1、および .NET Standard 2.0 を実装するその他のライブラリで動作できます。 サポートされる内容の詳細については、「 サポートされている .NET 実装 」を参照してください。

Modeling

テーブル分割

2 つ以上のエンティティ型を同じテーブルにマップできるようになりました。このテーブルでは、主キー列が共有され、各行が 2 つ以上のエンティティに対応します。

テーブル分割を使用するには、テーブルを共有するすべてのエンティティ型の間で、主キーを形成する外部キー プロパティを持つ識別リレーションシップの設定が必要です。

modelBuilder.Entity<Product>()
    .HasOne(e => e.Details).WithOne(e => e.Product)
    .HasForeignKey<ProductDetails>(e => e.Id);
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<ProductDetails>().ToTable("Products");

この機能の詳細については、 テーブル分割に 関するセクションを参照してください。

所有権型

所有エンティティ型は、同じ .NET 型を別の所有エンティティ型と共有できますが、.NET 型だけでは識別できないため、別のエンティティ型からのナビゲーションが必要です。 定義ナビゲーションを含むエンティティが所有者です。 所有者に問い合わせるとき、所有されている型が既定で含まれます。

慣例により、所有型のシャドウ主キーが作成され、テーブル分割を使用して所有者と同じテーブルにマップされます。 これにより、EF6 で複合型を使用する方法と同様に、所有型を使用できます。

modelBuilder.Entity<Order>().OwnsOne(p => p.OrderDetails, cb =>
    {
        cb.OwnsOne(c => c.BillingAddress);
        cb.OwnsOne(c => c.ShippingAddress);
    });

public class Order
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
}

public class OrderDetails
{
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}

この機能の詳細については、 所有エンティティの種類 に関するセクションを参照してください。

モデル レベルのクエリ フィルター

EF Core 2.0 には、モデル レベルのクエリ フィルターと呼ぶ新機能が含まれています。 この機能を使用すると、LINQ クエリ述語 (通常は LINQ Where クエリ演算子に渡されるブール式) をメタデータ モデルのエンティティ型 (通常は OnModelCreating) で直接定義できます。 このようなフィルターは、Include またはダイレクト ナビゲーション プロパティ参照の使用など、間接的に参照されるエンティティ型を含む、それらのエンティティ型に関連するすべての LINQ クエリに自動的に適用されます。 この機能の一般的なアプリケーションは次のとおりです。

  • 論理的な削除 - エンティティ型は IsDeleted プロパティを定義します。
  • マルチテナント - エンティティ型は TenantId プロパティを定義します。

上記の 2 つのシナリオの機能を示す簡単な例を次に示します。

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(
            p => !p.IsDeleted
            && p.TenantId == this.TenantId);
    }
}

Post エンティティ型のインスタンスに対してマルチテナントと論理的な削除を実装するモデル レベルのフィルターを定義します。 DbContext インスタンス レベルのプロパティの使用に注意してください: TenantId。 モデル レベルのフィルターでは、正しいコンテキスト インスタンス (つまり、クエリを実行しているコンテキスト インスタンス) の値が使用されます。

IgnoreQueryFilters() 演算子を使用して、個々の LINQ クエリでフィルターを無効にすることができます。

制限事項

  • ナビゲーション参照は許可されません。 この機能は、フィードバックに基づいて追加される場合があります。
  • フィルターは、階層のルート エンティティ型でのみ定義できます。

データベース スカラー関数のマッピング

EF Core 2.0 には Paul Middleton の重要な貢献が含まれています。これにより、データベース スカラー関数をメソッド スタブにマッピングして、LINQ クエリで使用して SQL に変換できます。

この機能を使用する方法の簡単な説明を次に示します。

DbContextで静的メソッドを宣言し、DbFunctionAttributeで注釈を付けます。

public class BloggingContext : DbContext
{
    [DbFunction]
    public static int PostReadCount(int blogId)
    {
        throw new NotImplementedException();
    }
}

このようなメソッドは自動的に登録されます。 登録すると、LINQ クエリ内のメソッドの呼び出しを SQL の関数呼び出しに変換できます。

var query =
    from p in context.Posts
    where BloggingContext.PostReadCount(p.Id) > 5
    select p;

注意すべき点:

  • 規則により、メソッドの名前は、SQL を生成するときに関数の名前 (この場合はユーザー定義関数) として使用されますが、メソッドの登録時に名前とスキーマをオーバーライドできます。
  • 現在サポートされているのはスカラー関数のみです。
  • マップされた関数をデータベースに作成する必要があります。 EF Core の移行では、作成は行われません。

コードファーストの自己完結型構成

EF6 では、 EntityTypeConfiguration から派生することで、特定のエンティティ型のコード最初の構成をカプセル化することが可能でした。 EF Core 2.0 では、このパターンを戻します。

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.HasKey(c => c.AlternateKey);
        builder.Property(c => c.Name).HasMaxLength(200);
    }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

高パフォーマンス

DbContext プーリング

ASP.NET Core アプリケーションで EF Core を使用するための基本的なパターンには、通常、カスタム DbContext 型を依存関係挿入システムに登録し、後でコントローラーのコンストラクター パラメーターを使用してその型のインスタンスを取得する必要があります。 つまり、DbContext の新しいインスタンスが要求ごとに作成されます。

バージョン 2.0 では、依存関係の挿入でカスタム DbContext 型を登録する新しい方法が導入されています。これにより、再利用可能な DbContext インスタンスのプールが透過的に導入されます。 DbContext プールを使用するには、サービスの登録時にAddDbContextPoolではなく、AddDbContextを使用します。

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

このメソッドを使用する場合、DbContext インスタンスがコントローラーによって要求された時点で、最初にプール内に使用可能なインスタンスがあるかどうかを確認します。 要求処理が完了すると、インスタンスの状態がリセットされ、インスタンス自体がプールに返されます。

これは概念的には、ADO.NET プロバイダーでの接続プールの動作に似ています。DbContext インスタンスの初期化のコストの一部を節約できるという利点があります。

制限事項

新しいメソッドでは、DbContext の OnConfiguring() メソッドで実行できる操作に関するいくつかの制限が導入されています。

Warnung

派生 DbContext クラスで独自の状態 (プライベート フィールドなど) を維持し、要求間で共有しない場合は、DbContext プールを使用しないでください。 EF Core は、DbContext インスタンスをプールに追加する前に認識されている状態のみをリセットします。

明示的にコンパイルされたクエリ

これは、大規模なシナリオで利点を提供するように設計された 2 番目のオプトイン パフォーマンス機能です。

手動または明示的にコンパイルされたクエリ API は、以前のバージョンの EF および LINQ to SQL でも使用できます。これにより、アプリケーションはクエリの変換をキャッシュして、1 回だけ計算して何度も実行できます。

一般に、EF Core はクエリ式のハッシュ表現に基づいてクエリを自動的にコンパイルおよびキャッシュできますが、このメカニズムを使用すると、ハッシュとキャッシュ参照の計算をバイパスしてパフォーマンスを向上させることができます。これにより、アプリケーションはデリゲートの呼び出しを通じて既にコンパイル済みのクエリを使用できます。

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}

Change Tracking

アタッチでは、新しいエンティティと既存のエンティティのグラフを追跡できます

EF Core では、さまざまなメカニズムを使用したキー値の自動生成がサポートされています。 この機能を使用すると、キー プロパティが CLR の既定値 (通常は 0 または null) の場合、値が生成されます。 つまり、エンティティのグラフを DbContext.Attach または DbSet.Attach に渡すことができます。EF Core では、キーが既に設定されているエンティティが Unchanged としてマークされますが、キー セットを持たないエンティティは Addedとしてマークされます。 これにより、生成されたキーを使用するときに、新しいエンティティと既存のエンティティが混在するグラフを簡単にアタッチできます。 DbContext.Updateキー セットを持つエンティティがDbSet.UpdateではなくModifiedとしてマークされている点を除き、Unchanged同じように動作します。

クエリ

LINQ 変換の改善

(メモリ内ではなく) データベースで評価されるロジックが多くなり、データベースから不必要に取得されるデータが少なくなり、クエリを正常に実行できるようになります。

GroupJoin の機能強化

この作業により、グループ結合用に生成される SQL が向上します。 グループ結合は、多くの場合、オプションのナビゲーション プロパティに対するサブクエリの結果です。

FromSql と ExecuteSqlCommand での文字列補間

C# 6 では、文字列補間が導入されました。これは、C# 式を文字列リテラルに直接埋め込む機能であり、実行時に文字列を構築する優れた方法を提供します。 EF Core 2.0 では、 FromSqlExecuteSqlCommandという生の SQL 文字列を受け入れる 2 つのプライマリ API に補間文字列の特別なサポートを追加しました。 この新しいサポートにより、C# 文字列補間を "安全" な方法で使用できます。 つまり、実行時に SQL を動的に構築するときに発生する可能性がある一般的な SQL インジェクションミスから保護する方法です。

次に例を示します。

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>()
        .FromSql($@"
            SELECT *
            FROM ""Customers""
            WHERE ""City"" = {city} AND
                ""ContactTitle"" = {contactTitle}")
            .ToArray();
  }

この例では、SQL 書式指定文字列に 2 つの変数が埋め込まれています。 EF Core では、次の SQL が生成されます。

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
    AND ""ContactTitle"" = @p1

EF.Functions.Like()

EF.Functions プロパティを追加しました。これは、EF Core またはプロバイダーがデータベース関数や演算子にマップするメソッドを定義し、それを LINQ クエリで呼び出せるようにするために使用します。 このようなメソッドの最初の例は Like():

var aCustomers =
    from c in context.Customers
    where EF.Functions.Like(c.Name, "a%")
    select c;

Like() にはメモリ内実装が付属しているため、メモリ内データベースに対して作業する場合や、クライアント側で述語の評価を行う必要がある場合に便利です。

データベース管理

DbContext のスキャフォールディングにおける複数形対応フック

EF Core 2.0 では、エンティティ型名の単数化と DbSet 名の複数形化に使用される新しい IPluralizer サービスが導入されています。 既定の実装は no-opであるため、これはユーザーが自分の複数形を簡単にプラグインできるフックにすぎません。

開発者がカスタムの複数形化機能を実装する様子を以下に示します。

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        services.AddSingleton<IPluralizer, MyPluralizer>();
    }
}

public class MyPluralizer : IPluralizer
{
    public string Pluralize(string name)
    {
        return Inflector.Inflector.Pluralize(name) ?? name;
    }

    public string Singularize(string name)
    {
        return Inflector.Inflector.Singularize(name) ?? name;
    }
}

その他

ADO.NET SQLite プロバイダーをSQLitePCL.rawに移動する

これにより、さまざまなプラットフォームでネイティブ SQLite バイナリを配布するための Microsoft.Data.Sqlite のより堅牢なソリューションが提供されます。

モデルごとに 1 つのプロバイダーのみ

プロバイダーがモデルと対話する方法を大幅に強化し、規則、注釈、Fluent API がさまざまなプロバイダーと連携する方法を簡略化します。

EF Core 2.0 では、使用されるプロバイダーごとに異なる IModel が ビルドされるようになりました。 通常、アプリケーションに対しては、その動作が透過的です。 これにより、一般的なリレーショナル メタデータの概念へのアクセスが常に、.Relational.SqlServerなどではなく、.Sqliteの呼び出しによって行われるよう、下位レベルのメタデータ API の簡略化が容易になりました。

統合ログと診断

ログ記録 (ILogger に基づく) と診断 (DiagnosticSource に基づく) メカニズムで、より多くのコードが共有されるようになりました。

ILogger に送信されたメッセージのイベント ID が 2.0 で変更されました。 イベント ID は、EF Core コード全体で一意になりました。 これらのメッセージは、MVC などによって使用される構造化ログ記録の標準パターンにも従うようになりました。

ロガー カテゴリも変更されました。 DbLoggerCategory を介してアクセスされるカテゴリの既知のセットが作成されました。

DiagnosticSource イベントでは、対応する ILogger メッセージと同じイベント ID 名が使用されるようになりました。