Overview
このガイドでは、MongoDB Entity Framework Core Provider を使用して、MongoDB データベース内のドキュメント間の関係をモデル化する方法を学ぶことができます。EF Core Provider は、次の関係タイプをサポートしています。
埋め込み関係: 親ドキュメント内に直接埋め込まれたサブドキュメント。これは、MongoDBでの1対1の関係と1対多の関係における推奨されるアプローチです。
手動参照: サブドキュメントとしてドキュメントを埋め込むのではなく、フィールドに ID を保存することで別のドキュメントを参照するドキュメント。このアプローチは、多対多の関係をモデル化する場合や、ドキュメントに独立してアクセスする必要がある場合に適しています。
Tip
クエリパターンに合わせたスキーマの設計
MongoDB のデータをモデル化する場合は、まずアプリケーションが最も頻繁に実行する操作を決定します。次に、これらの操作を効率的にサポートするドキュメント構造を選択します。MongoDB で最も効率的なスキーマは、関係データベースで使用するスキーマと一致しない場合があります。その代わり、どのデータが一般的に一緒に取得されるか、どの値が頻繁に変更されるか、どのデータ関係が時間とともに変化するかを考慮します。
データモデリングと関係データベースから MongoDB Server への移行について詳しくは、MongoDB Server ドキュメントの「SQL to MongoDB Mapping Chart」と「Data Modeling in MongoDBを参照してください。
埋め込み関係
サブドキュメントを親ドキュメントに埋め込むには、埋め込みドキュメントのモデルを所有エンティティとして指定します。埋め込み関係は、関係をモデル化する MongoDB ネイティブな方法であり、頻繁にまとめてアクセスするドキュメントのパフォーマンスを向上させます。
埋め込みドキュメントのとき
関連データが通常その親のコンテキストで表示され、埋め込みデータのサイズが妥当な範囲内に留まる場合は、埋め込みドキュメントを使用します。これには、アドレス、設定、または最近のアイテムの制限されたリストなどの小さな子の値のセットが含まれます。ドキュメントを埋め込むと、MongoDB Server は単一のデータベース操作で関連データを取得し、単一ドキュメント内で関連データをアトミックに更新することで、パフォーマンスを向上させることができます。
次の場合には、データの埋め込みを検討してください。
アプリケーションは通常、親データと子データを一緒に読み取ります。すべてを 1 つのクエリで取得する方が、個別に呼び出すよりも効率的です。
関連データは明確な上限なしに増加しません。制限されたデータでは、ドキュメントが無限に増加したり、MongoDB の 16MB のドキュメントサイズ制限に達したりすることはありません。
関連データはあまり変更されず、親とは独立して変更されます。これにより、親ドキュメントの書き換えによるオーバーヘッドが減少します。
注意
ネストされた階層
ネストされた所有エンティティのプロパティを更新すると、EF Core プロバイダーはルート ドキュメント全体を書き換えます。3 レベル以上のネストを持つエンティティ階層の場合、ツリの深い部分での小さな変更によって親ドキュメント全体が書き換えられるため、書き込みコストが増加します。モデルで深いネストを使用し、アプリケーションで頻繁な書き込みが実行される場合は、階層をフラット化するか、内部の所有エンティティを手動参照に置き換えることを検討します。
詳細については、MongoDB Server ドキュメントのEmbedded Dataを参照してください。
埋め込みドキュメント
所有エンティティは、次の方法で指定できます。
[所有]属性: サブドキュメントのモデル クラスに
[Owned]データ注釈属性を適用します。このアプローチは宣言的で簡潔ですが、所有型のすべての使用に全域的に適用されるため、非常に韓弱でもあります。Fluent API:
DbContext構成でOwnsOne()またはOwnsMany()メソッドを呼び出します。このアプローチにより、より多くの制御と柔軟性が得られます。モデル クラスを変更せずに、特定のエンティティに対して関係を異なるように構成できます。
次のセクションでは、単一の所有エンティティと複数の所有エンティティをモデル化するこれらのアプローチの例を示します。
1 つの所有エンティティ
親ドキュメント内に単一のドキュメントを埋め込むには、DbContext 構成で OwnsOne() メソッドを呼び出すか、[Owned] 属性を埋め込みクラスに適用します。
次の例は、埋め込まれた Address ドキュメントを含む Customer エンティティを示します。対応する構文を確認するには、[Owned] Attribute タブまたは Fluent API タブを選択します。
[] public class Address { public string Street { get; set; } = null!; public string City { get; set; } = null!; public string Country { get; set; } = null!; } public class Customer { public ObjectId Id { get; set; } public string Name { get; set; } = null!; public Address Address { get; set; } = null!; }
public class CustomerDbContext : DbContext { public DbSet<Customer> Customers { get; set; } = null!; public CustomerDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Customer>(c => { c.OwnsOne(c => c.Address); c.ToCollection("customers"); }); } }
Customer エンティティを保存すると、MongoDB は Address を Customer ドキュメント内の埋め込みドキュメントとして保存します。次の JSON は、この関係が MongoDB でどのように表示されるかを示しています。
{ "_id": ObjectId("..."), "Name": "John Doe", "Address": { "Street": "123 Main St", "City": "New York", "Country": "USA" } }
複数の所有エンティティ
親ドキュメントに複数のドキュメントを埋め込むには、OwnsMany() メソッドを呼び出すか、[Owned] 属性を埋め込みクラスに適用してコレクション プロパティを定義します。
次の例は、複数の埋め込み Order ドキュメントを含む Customer エンティティを示します。対応する構文を確認するには、[Owned] Attribute タブまたは Fluent API タブを選択します。
[] public class Order { public string Product { get; set; } = null!; public int Quantity { get; set; } } public class CustomerWithOrders { public ObjectId Id { get; set; } public string Name { get; set; } = null!; public List<Order> Orders { get; set; } = new(); }
public class OrderDbContext : DbContext { public DbSet<CustomerWithOrders> Customers { get; set; } = null!; public OrderDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<CustomerWithOrders>(c => { c.OwnsMany(c => c.Orders); c.ToCollection("customers"); }); } }
CustomerWithOrders エンティティを保存すると、MongoDB は次の JSON に示すように、Orders を埋め込みドキュメントの配列として保存します。
{ "_id": ObjectId("..."), "Name": "Jane Smith", "Orders": [ { "Product": "Laptop", "Quantity": 1 }, { "Product": "Mouse", "Quantity": 2 } ] }
手動参照
埋め込まれていないエンティティ間の関係をモデル化するには、あるドキュメントの ID を別のドキュメントのフィールドに保存することで、参照を手動で保存できます。このアプローチでは、アプリケーションコード内で関係を管理する必要があります。
参照を使用するタイミング
子セットが非常に大きくなる可能性がある場合、または関連データが頻繁に変更される場合は、親とは独立して関連データを定期的にクエリするときに参照を使用します。
次の場合には、参照の使用を検討します。
関連データは明確な上限なしに拡大します。無制限の配列はパフォーマンスの問題を引き起こし、MongoDBの16MBのドキュメントサイズ制限に達する可能性があります。
単独の関連エンティティをクエリする必要があります。親ドキュメントをスキャンせずに、子コレクションを直接クエリしてインデックス付けすることができます。
関連データは親とは独立して頻繁に変更されます。埋め込みデータの頻繁な更新には、次回ごとに親ドキュメント全体を書き換える必要があり、非効率です。
アプリケーションは通常、親データと子データを一緒に読み取りません。参照を使用することで、不要な子データの取得を回避できます。
関係が多対多または複雑である。
データを複製すると、更新オーバーヘッドが大きくなりすぎます。
詳細については、MongoDB Server ドキュメントの「参照データ」を参照してください。
参照の使用方法
次の例は、関連するエンティティへの参照を保存する方法を示しています。
public class Author { public ObjectId Id { get; set; } public string Name { get; set; } = null!; } public class Book { public ObjectId Id { get; set; } public string Title { get; set; } = null!; // Store reference to Author by storing the Author's Id public ObjectId AuthorId { get; set; } }
関連エンティティを検索するには、次の例に示すように、参照先のコレクションを個別にクエリする必要があります。
// Query a book and its author var book = db.Books.FirstOrDefault(b => b.Title == "My Book"); var author = db.Authors.FirstOrDefault(a => a.Id == book!.AuthorId);
大規模な 1 対多セットにサブセットパターンを使用する
親ドキュメントに大きな配列が含まれているが、アプリケーションで通常使用されるのが最新のアイテムまたは最も頻繁にアクセスされるアイテムのみである場合は、サブセット パターンを検討してください。
サブセット パターンでは、最も頻繁にアクセスされるアイテムをメイン ドキュメントに保存し、残りを別のコレクションに移動します。このアプローチにより、ドキュメント サイズを小さくし、より多くのワーキングセットを RAM に保存することができ、クエリ パフォーマンスを向上させることができます。
このパターンは、埋め込みのパフォーマンスの利点を得たいが、完全な埋め込み履歴によりドキュメントが大きすぎるか、読み込みのコストが高くなりすぎる場合に役立ちます。
詳細情報
MongoDB でのスキーマ設計のベストプラクティスの詳細については、MongoDB Server Docs の「MongoDB でのデータモデリングのベストプラクティス」と「スキーマの設計を参照してください。
MongoDB でのデータモデリングの詳細については、「データモデリング」 MongoDB Server ドキュメントを参照してください。
Entity Framework Core の所有エンティティタイプについて詳しくは、Microsoft Entity Framework Core ドキュメントの「所有エンティティタイプ」を参照してください。
API ドキュメント
このガイドで説明されているメソッドとタイプの詳細については、次の Microsoft API ドキュメントを参照してください。