연관 테이블에 추가 필드를 사용하여 코드를 먼저 작성하십시오.


297

이 시나리오가 있습니다.

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 와의 연관성을 어떻게 구성 합니까? 아니면 연관 테이블을 작성하는 더 좋은 방법이 있습니까?

답변:


524

사용자 정의 된 조인 테이블을 사용하여 다 대다 관계를 작성할 수 없습니다. 다 대다 관계에서 EF는 내부 및 조인 테이블을 관리합니다. 모델에 Entity 클래스가없는 테이블입니다. 추가 속성이있는 조인 테이블을 사용하려면 실제로 두 대 일 관계를 만들어야합니다. 다음과 같이 보일 수 있습니다 :

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)이 멤버에 대해 하나의 멤버와 두 개의 주석을 작성하십시오.

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의 세 번째 주석을 추가하십시오.

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

이에 관계를 삭제 MemberComments간의 일대 다 관계도 있기 때문에 MemberMemberComments사이 Comment및이 MemberComments규칙에 의해 삭제 계단식으로 설정합니다. 그리고 MemberIdCommentIdin 및 및 탐색 MemberComment속성에 대한 외래 키 속성으로 감지되고 FK 속성이 Null을 허용하지 않는 유형이기 때문에 결국 계단식 삭제 삭제를 일으키는 관계가 필요합니다. 이 모델에서는 의미가 있다고 생각합니다.MemberCommentint


1
감사합니다. 추가 정보를 제공해 주셔서 감사합니다.
hgdean

7
@ hgdean : 몇 가지 예를 스팸으로 보았습니다. 미안하지만 조인 테이블에 추가 데이터가있는 다 대다에 대한 흥미로운 모델과 질문이 때때로 발생합니다. 다음에 나는 다음에 링크 할 내용이있다. :)
Slauma

4
@Esteban : 재정의 된 것이 없습니다 OnModelCreating. 이 예는 매핑 규칙과 데이터 주석에만 의존합니다.
Slauma September

4
참고 : 유창함 API 메이크업없이이 방법을 사용하는 경우 반드시 당신이 단지와 복합 키가 있는지 데이터베이스에서 확인 MemberId하고 CommentId열이 아니라 추가로 세 번째 열 Member_CommentId- 당신이 정확히 일치하는 이름을 가지고 있지 않았 음을 의미합니다 (같은 또는 뭔가를) 키를위한 개체 간
Simon_Weaver

3
@Simon_Weaver (또는 답을 알고있는 사람) 비슷한 상황이 있지만 해당 테이블의 "MemberCommentID"기본 키를 갖고 싶습니다. 이것이 가능합니까? 현재 예외가 발생합니다. 제 질문을 살펴보십시오. 정말로 도움이 필요합니다 ... stackoverflow.com/questions/26783934/…
duxfox--

97

Slauma의 훌륭한 답변.

유창한 API 매핑을 사용 하여이 작업을 수행하는 코드를 게시합니다 .

public class User {
    public int UserID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    public ICollection<UserEmail> UserEmails { get; set; }
}

public class Email {
    public int EmailID { get; set; }
    public string Address { get; set; }

    public ICollection<UserEmail> UserEmails { get; set; }
}

public class UserEmail {
    public int UserID { get; set; }
    public int EmailID { get; set; }
    public bool IsPrimary { get; set; }
}

당신에 DbContext파생 클래스 당신이 할 수 있습니다 :

public class MyContext : DbContext {
    protected override void OnModelCreating(DbModelBuilder builder) {
        // Primary keys
        builder.Entity<User>().HasKey(q => q.UserID);
        builder.Entity<Email>().HasKey(q => q.EmailID);
        builder.Entity<UserEmail>().HasKey(q => 
            new { 
                q.UserID, q.EmailID
            });

        // Relationships
        builder.Entity<UserEmail>()
            .HasRequired(t => t.Email)
            .WithMany(t => t.UserEmails)
            .HasForeignKey(t => t.EmailID)

        builder.Entity<UserEmail>()
            .HasRequired(t => t.User)
            .WithMany(t => t.UserEmails)
            .HasForeignKey(t => t.UserID)
    }
}

그것은없는 다른 접근 방식과 허용 대답과 같은 효과가 나은도 악화.

편집 : CreatedDate를 bool에서 DateTime으로 변경했습니다.

편집 2 : 시간 부족으로 인해 응용 프로그램에서 예제를 작성 하여이 작업을 수행하고 있는지 확인했습니다.


1
나는 이것이 틀렸다고 생각한다. 여기서 두 엔티티 모두에 대해 1 : M이어야하는 M : M 관계를 작성 중입니다.
CHS

1
@CHS In your classes you can easily describe a many to many relationship with properties that point to each other.: msdn.microsoft.com/en-us/data/hh134698.aspx 에서 가져 왔습니다 . Julie Lerman은 틀릴 수 없습니다.
Esteban

1
Esteban, 관계 매핑이 실제로 잘못되었습니다. @CHS가 이것에 관한 것입니다. Julie Lerman은 "진정한"다 대다 관계에 대해 이야기하고있는 반면 여기에서는 다 대다로 매핑 할 수없는 모델에 대한 예를 보여줍니다. 에 Comments속성 이 없기 때문에 매핑도 컴파일되지 않습니다 Member. 엔터티에 대한 역 수집이 없기 때문에 HasMany호출의 이름을 바꾸어이 문제를 해결할 수 는 없습니다 . 실제로 올바른 매핑을 얻으려면 두 개의 일대 다 관계 를 구성해야합니다 . MemberCommentsMemberCommentWithMany
Slauma

2
감사합니다. 이 솔루션을 따라 다 대다 매핑을 수행했습니다.
토마스. 벤츠

나는 모르지만 이것은 MySql에서 더 잘 작동합니다. 빌더가 없으면 Mysql에서 마이그레이션을 시도 할 때 오류가 발생합니다.
Rodrigo Prieto

11

@ Esteban, 제공 한 코드가 맞습니다. 감사하지만 불완전한 것으로 테스트했습니다. "UserEmail"클래스에 누락 된 특성이 있습니다.

    public UserTest UserTest { get; set; }
    public EmailTest EmailTest { get; set; }

누군가 관심이 있다면 테스트 한 코드를 게시합니다. 문안 인사

using System.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

#region example2
public class UserTest
{
    public int UserTestID { get; set; }
    public string UserTestname { get; set; }
    public string Password { get; set; }

    public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }

    public static void DoSomeTest(ApplicationDbContext context)
    {

        for (int i = 0; i < 5; i++)
        {
            var user = context.UserTest.Add(new UserTest() { UserTestname = "Test" + i });
            var address = context.EmailTest.Add(new EmailTest() { Address = "address@" + i });
        }
        context.SaveChanges();

        foreach (var user in context.UserTest.Include(t => t.UserTestEmailTests))
        {
            foreach (var address in context.EmailTest)
            {
                user.UserTestEmailTests.Add(new UserTestEmailTest() { UserTest = user, EmailTest = address, n1 = user.UserTestID, n2 = address.EmailTestID });
            }
        }
        context.SaveChanges();
    }
}

public class EmailTest
{
    public int EmailTestID { get; set; }
    public string Address { get; set; }

    public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }
}

public class UserTestEmailTest
{
    public int UserTestID { get; set; }
    public UserTest UserTest { get; set; }
    public int EmailTestID { get; set; }
    public EmailTest EmailTest { get; set; }
    public int n1 { get; set; }
    public int n2 { get; set; }


    //Call this code from ApplicationDbContext.ConfigureMapping
    //and add this lines as well:
    //public System.Data.Entity.DbSet<yournamespace.UserTest> UserTest { get; set; }
    //public System.Data.Entity.DbSet<yournamespace.EmailTest> EmailTest { get; set; }
    internal static void RelateFluent(System.Data.Entity.DbModelBuilder builder)
    {
        // Primary keys
        builder.Entity<UserTest>().HasKey(q => q.UserTestID);
        builder.Entity<EmailTest>().HasKey(q => q.EmailTestID);

        builder.Entity<UserTestEmailTest>().HasKey(q =>
            new
            {
                q.UserTestID,
                q.EmailTestID
            });

        // Relationships
        builder.Entity<UserTestEmailTest>()
            .HasRequired(t => t.EmailTest)
            .WithMany(t => t.UserTestEmailTests)
            .HasForeignKey(t => t.EmailTestID);

        builder.Entity<UserTestEmailTest>()
            .HasRequired(t => t.UserTest)
            .WithMany(t => t.UserTestEmailTests)
            .HasForeignKey(t => t.UserTestID);
    }
}
#endregion

3

다 대다 구성의 두 가지 맛을 모두 얻을 수있는 솔루션을 제안하고 싶습니다.

"캐치"는 조인 테이블을 대상으로하는 뷰를 만들어야합니다. EF는 스키마 테이블이 최대 한 번만 매핑 될 수 있는지 확인하기 때문입니다 EntitySet.

이 답변은 이전 답변에서 이미 말한 내용에 추가되며 이러한 접근법 중 어느 것도 무시하지 않으며 그에 기반합니다.

모델:

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 virtual ICollection<MemberCommentView> MemberComments { get; set; }
}

public class Comment
{
    public int CommentID { get; set; }
    public string Message { get; set; }

    public virtual ICollection<Member> Members { get; set; }
    public virtual ICollection<MemberCommentView> MemberComments { get; set; }
}

public class MemberCommentView
{
    public int MemberID { get; set; }
    public int CommentID { get; set; }
    public int Something { get; set; }
    public string SomethingElse { get; set; }

    public virtual Member Member { get; set; }
    public virtual Comment Comment { get; set; }
}

구성 :

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

public class MemberConfiguration : EntityTypeConfiguration<Member>
{
    public MemberConfiguration()
    {
        HasKey(x => x.MemberID);

        Property(x => x.MemberID).HasColumnType("int").IsRequired();
        Property(x => x.FirstName).HasColumnType("varchar(512)");
        Property(x => x.LastName).HasColumnType("varchar(512)")

        // configure many-to-many through internal EF EntitySet
        HasMany(s => s.Comments)
            .WithMany(c => c.Members)
            .Map(cs =>
            {
                cs.ToTable("MemberComment");
                cs.MapLeftKey("MemberID");
                cs.MapRightKey("CommentID");
            });
    }
}

public class CommentConfiguration : EntityTypeConfiguration<Comment>
{
    public CommentConfiguration()
    {
        HasKey(x => x.CommentID);

        Property(x => x.CommentID).HasColumnType("int").IsRequired();
        Property(x => x.Message).HasColumnType("varchar(max)");
    }
}

public class MemberCommentViewConfiguration : EntityTypeConfiguration<MemberCommentView>
{
    public MemberCommentViewConfiguration()
    {
        ToTable("MemberCommentView");
        HasKey(x => new { x.MemberID, x.CommentID });

        Property(x => x.MemberID).HasColumnType("int").IsRequired();
        Property(x => x.CommentID).HasColumnType("int").IsRequired();
        Property(x => x.Something).HasColumnType("int");
        Property(x => x.SomethingElse).HasColumnType("varchar(max)");

        // configure one-to-many targeting the Join Table view
        // making all of its properties available
        HasRequired(a => a.Member).WithMany(b => b.MemberComments);
        HasRequired(a => a.Comment).WithMany(b => b.MemberComments);
    }
}

문맥:

using System.Data.Entity;

public class MyContext : DbContext
{
    public DbSet<Member> Members { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<MemberCommentView> MemberComments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Configurations.Add(new MemberConfiguration());
        modelBuilder.Configurations.Add(new CommentConfiguration());
        modelBuilder.Configurations.Add(new MemberCommentViewConfiguration());

        OnModelCreatingPartial(modelBuilder);
     }
}

Saluma 's (@Saluma) 답변에서

예를 들어 LastName = "Smith"인 멤버의 모든 주석을 찾으려면 다음과 같은 쿼리를 작성할 수 있습니다.

이것은 여전히 ​​작동합니다 ...

var commentsOfMembers = context.Members
    .Where(m => m.LastName == "Smith")
    .SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
    .ToList();

...하지만 이제는 ...

var commentsOfMembers = context.Members
    .Where(m => m.LastName == "Smith")
    .SelectMany(m => m.Comments)
    .ToList();

또는 "Smith"라는 이름을 가진 멤버 목록을 만들려면 (둘 이상 있다고 가정) 투영을 사용할 수 있습니다.

이것은 여전히 ​​작동합니다 ...

var membersWithComments = context.Members
    .Where(m => m.LastName == "Smith")
    .Select(m => new
    {
        Member = m,
        Comments = m.MemberComments.Select(mc => mc.Comment)
    })
    .ToList();

...하지만 이제는 ...

var membersWithComments = context.Members
    .Where(m => m.LastName == "Smith")
    .Select(m => new
    {
        Member = m,
        m.Comments
    })
        .ToList();

멤버에서 주석을 제거하려는 경우

var comment = ... // assume comment from member John Smith
var member = ... // assume member John Smith

member.Comments.Remove(comment);

Include()회원의 의견을 듣고 싶은 경우

var member = context.Members
    .Where(m => m.FirstName == "John", m.LastName == "Smith")
    .Include(m => m.Comments);

이것은 모두 구문 설탕처럼 느껴지지만 추가 구성을 기꺼이 수행하면 약간의 특권을 얻습니다. 어느 쪽이든 당신은 두 가지 접근법을 모두 최대한 활용할 수있는 것 같습니다.


LINQ 쿼리를 입력 할 때 가독성이 향상되었습니다. 이 방법을 채택해야 할 수도 있습니다. EF EntitySet이 데이터베이스의 뷰도 자동으로 업데이트합니까? EF5.0 계획에 설명 된대로 [Sugar]와 유사하게 보일 것입니까? github.com/dotnet/EntityFramework.Docs/blob/master/…
Krptodr

EntityTypeConfiguration<EntityType>엔터티 유형의 키와 속성 을 재정의하는 이유가 궁금 합니다. 예를 Property(x => x.MemberID).HasColumnType("int").IsRequired();들어와 중복되는 것 같습니다 public int MemberID { get; set; }. 혼란스러워 이해 해주시겠습니까?

0

TLDR; (EF6 / VS2012U5의 EF 편집기 버그와 반 관련) DB에서 모델을 생성하고 해당 m : m 테이블을 볼 수없는 경우 : 두 관련 테이블 삭제-> .edmx 저장-> 데이터베이스에서 생성 / 추가- > 저장.

여기에 온 사람들은 EF .edmx 파일에 표시 할 속성 열과 다 대다 관계를 얻는 방법을 궁금해하는 사람들을 위해 (현재 탐색 속성 집합으로 표시되지 않고 처리하지 않기 때문에) 이러한 클래스를 생성했습니다. 귀하의 데이터베이스 테이블 (또는 MS lingo의 데이터베이스 우선)에서 믿습니다.

.edmx에서 문제의 테이블 2 개를 삭제하고 (OP 예제, 멤버 및 설명을 가져 오려면) '데이터베이스에서 모델 생성'을 통해 다시 추가하십시오. (즉, Visual Studio에서 업데이트하도록하지 마십시오. 삭제, 저장, 추가, 저장)

그런 다음 여기에 제안 된 내용에 따라 세 번째 테이블을 만듭니다.

이것은 순수한 다 대 다 관계가 처음에 추가되고 속성이 나중에 DB에서 설계되는 경우에 관련됩니다.

이 스레드 / 인터넷 검색에서 즉시 명확하지 않았습니다. Google에서 링크 # 1이므로 문제를 찾고 있지만 DB 측에서 먼저 오는 것처럼 링크를 넣으십시오.


0

이 오류를 해결하는 한 가지 방법 ForeignKey은 외래 키로 원하는 속성 위에 속성 을 배치 하고 탐색 속성을 추가하는 것입니다.

참고 : ForeignKey속성에서 괄호와 큰 따옴표 사이에 이러한 방식으로 참조되는 클래스 이름을 배치하십시오.

여기에 이미지 설명을 입력하십시오


제공된 링크는 나중에 사용할 수 없게 될 수 있으므로 답변 자체에 최소한의 설명을 추가하십시오.
n4m31ess_c0d3r

2
클래스가 아닌 navigation 속성 의 이름 이어야합니다.
aaron
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.