1. ホーム
  2. c#

[解決済み】アソシエーションテーブルにフィールドを追加して、多対多で先にコードを作成する

2022-03-27 11:57:19

質問

こんなシナリオがあります。

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 ? または、アソシエーション・テーブルを作成する良い方法はありますか?

どのように解決するのですか?

カスタマイズされた結合テーブルで多対多のリレーションシップを作成することはできません。多対多のリレーションシップでは、EFは結合テーブルを内部で非表示に管理します。これは、モデル内にエンティティ・クラスがないテーブルです。このような追加プロパティを持つ結合テーブルを使用するには、実際に2つの一対多のリレーションを作成する必要があります。それは次のようになります。

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 = 例えば、次のようなクエリです。

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) 新しいメンバーを作成し、既存のcomment2と関連付ける。

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) 既存の member2 と comment3 の間にリレーションを作成する。

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();
}

のリレーションを削除します。 MemberComments の間の1対多の関係も MemberMemberComments との間の CommentMemberComments は、慣習的にcascading deleteで設定されています。そして、このようなケースは MemberIdCommentIdMemberComment の外部キー・プロパティとして検出されます。 MemberComment ナビゲーションプロパティであり、FK プロパティは non-nullable 型であるため int が必要であり、最終的にcascading-delete-setupが発生します。このモデルには意味があるのだと思います。