リレーションシップの定義と管理 (Entity Framework)
Entity Framework では、アソシエーションを使用してエンティティどうしを関連付けることができます。 このエンティティ間のリレーションシップは、概念モデルの Association 要素で定義されます。 各リレーションシップには、エンティティ型と型の多重度 (1、0 または 1、多) について説明した 2 つの End が含まれています。 リレーションシップは、リレーションシップのどちらの End がプリンシパル ロールでどちらの End が依存しているかを記した参照制約によって管理することができます。
.NET Framework バージョン 4 からは概念モデルに外部キーを含めることができるようになりました。 Entity Data Model ウィザードの [モデルに外部キー列を含める] オプションは既定で選択されています。 このオプションを選択すると、生成されたエンティティ オブジェクトには、外部キー列にマッピングするスカラー プロパティが含まれています。 外部キー プロパティを含めることにより、依存オブジェクトの外部キーの値を変更してリレーションシップを作成または変更することができます。 このような種類のアソシエーションは外部キー アソシエーションと呼ばれます。
外部キー列が概念モデルに含まれていない場合、アソシエーション情報は独立したオブジェクトとして管理されます。 リレーションシップは外部キー プロパティではなくオブジェクト参照によって追跡され、ObjectStateManager で ObjectStateEntry オブジェクトとして表されます。このような種類のアソシエーションは独立アソシエーションと呼ばれます。 独立アソシエーションを変更するには、アソシエーションに参加する各エンティティ用に生成されるナビゲーション プロパティを変更するのが最も一般的な方法です。
いずれのアソシエーションでも、各オブジェクトは参加する各リレーションシップに対してナビゲーション プロパティを持つことができます。 ナビゲーション プロパティを使用すると、リレーションシップ内を両方向に移動して管理し、多重度が 1 またはゼロか 1 の場合には参照オブジェクトを返し、多重度が多数の場合にはオブジェクトのコレクションを返すことができます。
リレーションシップの混在
モデルでは 1 つまたは両方のアソシエーションを使用するよう選択することができます。 ただし外部キーのみが含まれるテーブル (純粋結合テーブルとも呼ぶ) をモデルに含めた場合、モデルで外部キー アソシエーションを使用するように指定しても、独立アソシエーションを使用して純粋多対多リレーションシップが管理されます。 Entity Data Model ウィザードでは純粋結合テーブルにマップするエンティティは作成されません。
リレーションシップの作成および変更
Entity Framework では、複数の方法でリレーションシップを作成および変更できます。
新しいオブジェクトをナビゲーション プロパティに割り当てる。 次のコードでは、
order
エンティティとcustomer
エンティティのリレーションシップを使用します。 オブジェクトがオブジェクト コンテキストにアタッチされていると、order
はcustomer.Orders
コレクションに追加され、order
オブジェクトの対応する外部キー プロパティは顧客のキー プロパティ値に設定されます。order.Customer = customer
エンティティ コレクションのオブジェクトを削除または追加する。 たとえば、Add メソッドを使用して Order 型のオブジェクトを
customer.Orders
コレクションに追加します。 この操作は、特定のorder
およびcustomer
の間のリレーションシップを作成します。 オブジェクトがオブジェクト コンテキストにアタッチされていると、order
オブジェクトの顧客参照および外部キー プロパティは適切なcustomer
に設定されます。customer.Orders.Add(order)
外部キー アソシエーションでは、次の例に示すように、外部キー プロパティに新しい値を割り当てることができます。 参照が追加済み状態である場合、SaveChanges が呼び出されるまで参照ナビゲーション プロパティは新しいオブジェクトのキー値と同期がとられません。 同期が行われない理由は、追加されたオブジェクトが保存されるまでオブジェクト コンテキストに永久キーが含まれていないからです。 詳細については、「エンティティ キーの使用 (Entity Framework)」を参照してください。 リレーションシップを設定してすぐに新しいオブジェクトを完全に同期する必要がある場合は、上記の 2 つの方法のいずれかを使用してください。
order.CustomerID = newCustomer.CustomerID order.CustomerID = null
特定のオブジェクトのエンティティ キーを作成する。 このキーを持つオブジェクトが既にオブジェクト コンテキストに存在する場合、CreateEntityKey メソッドは既存のオブジェクトの EntityKey を返します。 このメソッドは .NET Framework 3.5 SP1 との互換性を維持するために提供されています。
order.CustomerReference.EntityKey = ctx.CreateEntityKey("EntitySetName", newObject)
上記で説明した方法のいずれかを使用してオブジェクト コンテキストにアタッチされたオブジェクトのリレーションシップを変更する場合、Entity Framework で外部キー、参照、コレクションの同期を維持する必要があります。 Entity Framework は、特定の種類の対象オブジェクトでこの同期を管理します。
Entity Data Model ツールによってコード生成されたエンティティ オブジェクト。
IEntityWithChangeTracker インターフェイスを実装するカスタム オブジェクト。
プロキシを持つ POCO エンティティ。 詳細については、「POCO プロキシの作成要件 (Entity Framework)」を参照してください。
プロキシなしで POCO エンティティを使用している場合、DetectChanges メソッドを呼び出してオブジェクト コンテキストの関連オブジェクトを同期する必要があります。 接続解除されたオブジェクトを使用している場合は、手動で同期を管理する必要があります。
外部キー プロパティとナビゲーション プロパティを使用して関連オブジェクトを関連付ける方法を次の例に示します。 外部キー アソシエーションでは、両方の方法でリレーションシップを作成または変更できます。 独立アソシエーションでは、外部キー プロパティは使用できません。
' The following example creates a new StudentGrade object and associates
' the StudentGrade with the Course and Person by
' setting the foreign key properties.
Using context As New SchoolEntities()
' The database will generate the EnrollmentID.
' To create the association between the Course and StudentGrade,
' and the Student and the StudentGrade, set the foreign key property
' to the ID of the principal.
Dim newStudentGrade = New StudentGrade With
{
.EnrollmentID = 0,
.Grade = CDec(4.0),
.CourseID = 4022,
.StudentID = 17
}
' Adding the new object to the context will synchronize
' the references with the foreign keys on the newStudentGrade object.
context.StudentGrades.AddObject(newStudentGrade)
' You can access Course and Student objects on the newStudentGrade object
' without loading the references explicitly because
' the lazy loading option is set to true in the constructor of SchoolEntities.
Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID)
Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID)
context.SaveChanges()
End Using
' The following example creates a new StudentGrade and associates
' the StudentGrade with the Course and Person by
' setting the navigation properties to the Course and Person objects that were returned
' by the query.
' You do not need to call AddObject() in order to add the grade object
' to the context, because when you assign the reference
' to the navigation property the objects on both ends get synchronized by the Entity Framework.
' Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method
' is called if your objects do not meet the change tracking requirements.
Using context = New SchoolEntities()
Dim courseID = 4022
Dim course = (From c In context.Courses
Where c.CourseID = courseID
Select c).First()
Dim personID = 17
Dim student = (From p In context.People
Where p.PersonID = personID
Select p).First()
' The database will generate the EnrollmentID.
' Use the navigation properties to create the association between the objects.
Dim newStudentGrade = New StudentGrade With
{
.EnrollmentID = 0,
.Grade = CDec(4.0),
.Course = course,
.Person = student
}
context.SaveChanges()
End Using
// The following example creates a new StudentGrade object and associates
// the StudentGrade with the Course and Person by
// setting the foreign key properties.
using (SchoolEntities context = new SchoolEntities())
{
StudentGrade newStudentGrade = new StudentGrade
{
// The database will generate the EnrollmentID.
EnrollmentID = 0,
Grade = 4.0M,
// To create the association between the Course and StudentGrade,
// and the Student and the StudentGrade, set the foreign key property
// to the ID of the principal.
CourseID = 4022,
StudentID = 17,
};
// Adding the new object to the context will synchronize
// the references with the foreign keys on the newStudentGrade object.
context.StudentGrades.AddObject(newStudentGrade);
// You can access Course and Student objects on the newStudentGrade object
// without loading the references explicitly because
// the lazy loading option is set to true in the constructor of SchoolEntities.
Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID);
Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID);
context.SaveChanges();
}
// The following example creates a new StudentGrade and associates
// the StudentGrade with the Course and Person by
// setting the navigation properties to the Course and Person objects that were returned
// by the query.
// You do not need to call AddObject() in order to add the grade object
// to the context, because when you assign the reference
// to the navigation property the objects on both ends get synchronized by the Entity Framework.
// Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method
// is called if your objects do not meet the change tracking requirements.
using (var context = new SchoolEntities())
{
int courseID = 4022;
var course = (from c in context.Courses
where c.CourseID == courseID
select c).First();
int personID = 17;
var student = (from p in context.People
where p.PersonID == personID
select p).First();
StudentGrade grade = new StudentGrade
{
// The database will generate the EnrollmentID.
Grade = 4.0M,
// Use the navigation properties to create the association between the objects.
Course = course,
Person = student
};
context.SaveChanges();
}
状態の変更
外部キー アソシエーションでは、リレーションシップを変更すると、Unchanged 状態の依存オブジェクトの状態が Modified に変更されます。
独立リレーションシップでは、リレーションシップを変更しても依存オブジェクトの状態は更新されません。
同時実行の管理
外部キー アソシエーションと独立アソシエーションの場合、ConcurrencyMode 属性を fixed に設定し、エンティティ キーおよび概念レイヤーで定義されるその他のプロパティに基づいて同時実行チェックを行います。
ただし独立アソシエーションの場合、リレーションシップの同時実行チェックは常にリレーションシップの両方の End のオブジェクトに対して実行されます。 このチェックでは、関連した End の元のキー値が検証されます。 オブジェクトがオブジェクト コンテキストからデタッチされているときにリレーションシップを変更した場合、元のリレーションシップを (クエリのやり直しか元のキー値の送信を行うことによって) 作成し直し、オブジェクトをオブジェクト コンテキストにアタッチしてから、そのオブジェクト コンテキストのリレーションシップに適切な変更を加えます。
重複するキーの操作
重複するキーは、キーの一部のプロパティがエンティティの別のキーの一部でもある複合キーです。 独立アソシエーションで重複するキーを持つことはできません。 重複するキーを持つ外部キー アソシエーションを変更する場合は、オブジェクト参照を使用するよりも外部キー値を変更することをお勧めします。
関連オブジェクトの読み込み
外部キー アソシエーションで依存オブジェクトの関連 End を読み込むと、現在メモリ内にある依存の外部キー値に基づいて関連オブジェクトが読み込まれます。
// Get the order where currently AddressID = 1. SalesOrderHeader order = context.SalesOrderHeaders.First(o=>o.SalesOrderID == orderId);
// Use BillToAddressID foreign key property
// to change the association. order.BillToAddressID = 2
order.AddressReference.Load();
独立アソシエーションの場合、依存オブジェクトの関連 End は現在データベース内にある外部キー値に基づいてクエリが行われます。 ただしリレーションシップが変更され、依存オブジェクトの参照プロパティがオブジェクト コンテキストに読み込まれた別のプリンシパル オブジェクトを指す場合、Entity Framework はクライアント上に定義されたとおりにリレーションシップを作成しようとします。
リレーションシップの識別と非識別に関する考慮事項
プリンシパル エンティティの主キーが依存エンティティの主キーの一部でもある場合、リレーションシップは識別リレーションシップです。 識別リレーションシップでは、依存エンティティは必ずプリンシパル エンティティと共に存在します。 この制約のため、識別リレーションシップでは次の動作が発生します。
プリンシパル オブジェクトを削除すると、依存オブジェクトも削除されます。 これは、リレーションシップのモデルで
<OnDelete Action="Cascade" />
を指定するのと同じ動作です。リレーションシップを削除すると、依存オブジェクトが削除されます。 EntityCollection の Remove メソッドを呼び出すと、リレーションシップと依存オブジェクトの両方が削除の対象としてマークされます。
新しい依存オブジェクトを作成する場合は、SaveChanges を呼び出す前に、プリンシパル オブジェクトがオブジェクト コンテキストまたはデータ ソース内に存在する必要があります。 プリンシパル オブジェクトが存在しない場合、InvalidOperationException が発生します。
非識別リレーションシップでモデルが外部キー アソシエーションに基づいている場合、プリンシパル オブジェクトを削除すると、依存オブジェクトの外部キーが null に設定されます (Null が許容される場合)。 プリンシパルがなければ存在できない依存オブジェクトの場合、手動で依存オブジェクトを削除するか、新しいプリンシパル オブジェクトを依存オブジェクトに割り当てる必要があります。 2 つ目の方法として、モデルで <OnDelete Action="Cascade" />
を指定することで、関連したプリンシパル オブジェクトが削除されたときに依存オブジェクトも削除されるようにすることができます。
このセクションの内容
方法: 外部キーのプロパティを使用してオブジェクト間のリレーションシップを変更する
EntityReference を使用してオブジェクト間のリレーションシップを変更する方法 (Entity Framework)
方法: POCO エンティティ間のリレーションシップを変更 (Entity Framework)
参照
処理手順
EntityReference を使用してオブジェクト間のリレーションシップを変更する方法 (Entity Framework)
方法: 外部キーのプロパティを使用してオブジェクト間のリレーションシップを変更する
概念
オブジェクトの作成、追加、変更、および削除 (Entity Framework)
POCO エンティティの使用 (Entity Framework)