次のようなシナリオがあります:
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }
public virtual ICollection<Member> Members { get; set; }
}
public class MemberComment
{
public int MemberID { get; set; }
public int CommentID { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
}
どのように関連付けを設定するのですか?流暢なAPI? または、関連テーブルを作成するためのより良い方法はありますか?
ベストアンサー1
カスタマイズされた結合テーブルを使用して多対多の関係を作成することはできません。多対多の関係では、EF は結合テーブルを内部的に非表示で管理します。これは、モデル内に Entity クラスがないテーブルです。追加のプロパティを持つこのような結合テーブルを操作するには、実際には 2 つの 1 対多の関係を作成する必要があります。次のようになります。
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<MemberComment> MemberComments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }
public virtual ICollection<MemberComment> MemberComments { get; set; }
}
public class MemberComment
{
[Key, Column(Order = 0)]
public int MemberID { get; set; }
[Key, Column(Order = 1)]
public int CommentID { get; set; }
public virtual Member Member { get; set; }
public virtual Comment Comment { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
}
LastName
たとえば、= "Smith"のメンバーのすべてのコメントを検索する場合は、次のようなクエリを記述できます。
var commentsOfMembers = context.Members
.Where(m => m.LastName == "Smith")
.SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
.ToList();
... または ...
var commentsOfMembers = context.MemberComments
.Where(mc => mc.Member.LastName == "Smith")
.Select(mc => mc.Comment)
.ToList();
または、「Smith」という名前のメンバー(複数存在すると想定)のリストとそのコメントを作成するには、投影を使用できます。
var membersWithComments = context.Members
.Where(m => m.LastName == "Smith")
.Select(m => new
{
Member = m,
Comments = m.MemberComments.Select(mc => mc.Comment)
})
.ToList();
MemberId
= 1のメンバーのすべてのコメントを検索する場合:
var commentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1)
.Select(mc => mc.Comment)
.ToList();
これで、結合テーブル内のプロパティでフィルタリングすることもできます (多対多の関係では不可能です)。たとえば、プロパティが 99 であるメンバー 1 のすべてのコメントをフィルタリングしますSomething
。
var filteredCommentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1 && mc.Something == 99)
.Select(mc => mc.Comment)
.ToList();
遅延読み込みのおかげで物事が簡単になるかもしれません。読み込みが済んでいれば、Member
明示的なクエリなしでコメントを取得できるはずです。
var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);
遅延読み込みにより、コメントはバックグラウンドで自動的に取得されると思います。
編集
楽しみのために、このモデルでエンティティとリレーションシップを追加する方法と削除する方法の例をいくつか示します。
1) メンバー 1 つと、このメンバーのコメント 2 つを作成します。
var member1 = new Member { FirstName = "Pete" };
var comment1 = new Comment { Message = "Good morning!" };
var comment2 = new Comment { Message = "Good evening!" };
var memberComment1 = new MemberComment { Member = member1, Comment = comment1,
Something = 101 };
var memberComment2 = new MemberComment { Member = member1, Comment = comment2,
Something = 102 };
context.MemberComments.Add(memberComment1); // will also add member1 and comment1
context.MemberComments.Add(memberComment2); // will also add comment2
context.SaveChanges();
2) member1 の 3 番目のコメントを追加します。
var member1 = context.Members.Where(m => m.FirstName == "Pete")
.SingleOrDefault();
if (member1 != null)
{
var comment3 = new Comment { Message = "Good night!" };
var memberComment3 = new MemberComment { Member = member1,
Comment = comment3,
Something = 103 };
context.MemberComments.Add(memberComment3); // will also add comment3
context.SaveChanges();
}
3) 新しいメンバーを作成し、既存のコメント2に関連付けます。
var comment2 = context.Comments.Where(c => c.Message == "Good evening!")
.SingleOrDefault();
if (comment2 != null)
{
var member2 = new Member { FirstName = "Paul" };
var memberComment4 = new MemberComment { Member = member2,
Comment = comment2,
Something = 201 };
context.MemberComments.Add(memberComment4);
context.SaveChanges();
}
4) 既存のメンバー2とコメント3の間に関係を作成します。
var member2 = context.Members.Where(m => m.FirstName == "Paul")
.SingleOrDefault();
var comment3 = context.Comments.Where(c => c.Message == "Good night!")
.SingleOrDefault();
if (member2 != null && comment3 != null)
{
var memberComment5 = new MemberComment { Member = member2,
Comment = comment3,
Something = 202 };
context.MemberComments.Add(memberComment5);
context.SaveChanges();
}
5) この関係を再度削除します。
var memberComment5 = context.MemberComments
.Where(mc => mc.Member.FirstName == "Paul"
&& mc.Comment.Message == "Good night!")
.SingleOrDefault();
if (memberComment5 != null)
{
context.MemberComments.Remove(memberComment5);
context.SaveChanges();
}
6) member1 とコメントとの関係をすべて削除します。
var member1 = context.Members.Where(m => m.FirstName == "Pete")
.SingleOrDefault();
if (member1 != null)
{
context.Members.Remove(member1);
context.SaveChanges();
}
これにより、と間の 1 対多の関係、および と 間のMemberComments
1 対多の関係が慣例によりカスケード削除でセットアップされるため、 の関係も削除されます。 これは、 との がとナビゲーション プロパティの外部キー プロパティとして検出され、FK プロパティが非 null 型であるため、関係が必要となり、最終的にカスケード削除のセットアップが発生するためです。 このモデルでは意味があると思います。Member
MemberComments
Comment
MemberComments
MemberId
CommentId
MemberComment
Member
Comment
int