İlişkilendirme tablosundaki ek alanlarla ilk önce çoktan çoğa kod oluşturun


298

Bu senaryo var:

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

Akıcı API ile ilişkimi nasıl yapılandırabilirim ? Veya ilişkilendirme tablosunu oluşturmanın daha iyi bir yolu var mı?

Yanıtlar:


524

Özelleştirilmiş bir birleştirme tablosu ile çoktan çoğa ilişki oluşturmak mümkün değildir. Çoktan çoğa ilişkide EF birleştirme tablosunu dahili ve gizli olarak yönetir. Modelinizde Entity sınıfı olmayan bir tablo. Ek özelliklere sahip böyle bir birleştirme tablosu ile çalışmak için aslında iki bire çok ilişki oluşturmanız gerekir. Şöyle görünebilir:

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

Şimdi LastName= "Smith" ile üyelerin tüm yorumlarını bulmak istiyorsanız, örneğin şöyle bir sorgu yazabilirsiniz:

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

... ya da ...

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

Veya "Smith" adında üyelerin bir listesini oluşturmak için (birden fazla olduğunu varsayıyoruz) yorumlarıyla birlikte bir projeksiyon kullanabilirsiniz:

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

MemberId= 1 olan bir üyenin tüm yorumlarını bulmak istiyorsanız :

var commentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1)
    .Select(mc => mc.Comment)
    .ToList();

Artık birleştirme tablonuzdaki özelliklere (çoktan çoğa ilişkide mümkün olmayacaktır) göre de filtreleyebilirsiniz, örneğin: 99'da özelliği olan üye 1'in tüm yorumlarına filtre uygulayın Something:

var filteredCommentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1 && mc.Something == 99)
    .Select(mc => mc.Comment)
    .ToList();

Tembel yükleme nedeniyle işler daha kolay olabilir. Yüklemeniz varsa, Memberaçık bir sorgu olmadan yorumları alabilmeniz gerekir:

var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);

Tembel yüklemenin yorumları otomatik olarak sahne arkasına getireceğini düşünüyorum.

Düzenle

Sadece eğlenmek için birkaç örnek daha varlıklar ve ilişkiler nasıl eklenir ve bu modelde nasıl silinir:

1) Bir üye ve bu üyenin iki yorumu oluşturun:

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) Üyenin üçüncü bir yorumunu ekleyin1:

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) Yeni üye oluşturun ve mevcut yorumla ilişkilendirin2:

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) Mevcut üye2 ve yorum3 arasında ilişki oluşturun:

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) Bu ilişkiyi tekrar silin:

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) Üye1'i ve yorumlarla olan tüm ilişkilerini silin:

var member1 = context.Members.Where(m => m.FirstName == "Pete")
    .SingleOrDefault();
if (member1 != null)
{
    context.Members.Remove(member1);
    context.SaveChanges();
}

Bu ilişkileri siler MemberCommentsarasındaki bire çok ilişkileri çok çünkü Memberve MemberCommentsarasında Commentve MemberCommentsKongre tarafından silme basamaklı ile yapılandırıldı. Ve durum budur MemberIdve CommentIdin MemberCommentve Memberve Commentnavigasyon özellikleri için yabancı anahtar özellikleri olarak algılanır ve FK özellikleri null edilemeyen tipte intolduğu için nihayetinde basamaklı-silme-kurulumuna neden olan ilişki gereklidir. Bence bu modelde mantıklı.


1
Teşekkür ederim. Verdiğiniz ek bilgileri çok takdir ediyoruz.
hgdean

7
@hgdean: Birkaç örnek daha spam gönderdim, üzgünüm, ancak ilginç bir model ve katılma tablosunda ek verilerle çoktan çoğa ilgili sorular şimdi ve sonra burada ortaya çıkıyor. Şimdi bir dahaki sefere bağlantı
kuracak bir şeyim var

4
@Ebeban: Geçersiz kılınan bir şey yok OnModelCreating. Örnek, yalnızca haritalama kurallarına ve veri ek açıklamalarına dayanmaktadır.
Slauma

4
Not: Bu yaklaşımı Akıcı API olmadan kullanırsanız, veritabanınızda ek bir üçüncü sütun (veya bunun gibi bir şey) yerine yalnızca MemberIdve CommentIdsütunları olan bir bileşik anahtarınızın olduğunu kontrol ettiğinizden emin olun. Member_CommentIdanahtarlarınız için nesneler arasında
Simon_Weaver

3
@Simon_Weaver (ya da cevabı bilen herkes) Benzer bir durum var ama bu tablo için "MemberCommentID" birincil anahtarına sahip olmak istiyorum, bu mümkün mü değil mi? Şu anda bir istisna alıyorum, lütfen soruma bir göz atın, gerçekten yardıma ihtiyacım var ... stackoverflow.com/questions/26783934/…
duxfox--

98

Slauma'dan mükemmel cevap.

Ben sadece akıcı API eşleme kullanarak bunu yapmak için kod yayınlayacağım.

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

Senin üzerinde DbContexttüretilmiş sınıfın bunu yapabilirdi:

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

Öyle farklı bir yaklaşım ile kabul edilen cevap olarak aynı etkiye sahiptir hayır daha iyi ne de daha kötü.

EDIT: CreatedDate bool yerine DateTime değiştirdim.

DÜZENLEME 2: Zaman yetersizliğinden dolayı bunun işe yaradığından emin olmak için çalıştığım bir uygulamadan bir örnek verdim.


1
Bence bu yanlış. Burada her iki varlık için 1: M olması gereken bir M: M ilişkisi oluşturuyorsunuz.
CHS

1
@CHS In your classes you can easily describe a many to many relationship with properties that point to each other.alınmış: msdn.microsoft.com/en-us/data/hh134698.aspx . Julie Lerman yanlış olamaz.
Esteban

1
Esteban, ilişki haritalama gerçekten yanlış. @CHS bu konuda haklı. Julie Lerman "gerçek" çoktan çoğa ilişkiden bahsederken, burada çoktan çoğa eşlenemeyen bir model için bir örneğimiz var. Eşlemeniz derlenmeyecek, çünkü içinde bir Commentsmülk yok Member. Ve sadece yeniden adlandırarak bu düzeltemez HasManyçağrısı MemberCommentsnedeniyle MemberCommentvarlık için bir ters koleksiyonuna sahip değildir WithMany. Aslında , doğru eşlemeyi elde etmek için iki bire çok ilişki yapılandırmanız gerekir .
Slauma

2
Teşekkür ederim. Haritalamayı çoktan çoğa yapmak için bu çözümü izledim.
Thomas.Benz

Ben bilmiyorum ama bu MySql ile daha iyi çalışıyor. Oluşturucu olmadan, taşımayı denediğimde Mysql bana bir hata attı.
Rodrigo Prieto

11

@Ebeban, sağladığınız kod doğru, teşekkürler, ama eksik, test ettim. "UserEmail" sınıfında eksik özellikler var:

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

Birisi ilgileniyorsa test ettiğim kodu gönderirim. Saygılarımızla

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

Çoktan çoğa yapılandırmanın her iki çeşidinin de elde edilebileceği bir çözüm önermek istiyorum.

"Yakala", EF, bir şemanın tablosunun en fazla bir kez eşlenebileceğini doğruladığından, Birleştirme Tablosunu hedefleyen bir görünüm oluşturmamız gerektiğidir EntitySet.

Bu cevap, daha önceki cevaplarda söylenenlere katkıda bulunur ve bu yaklaşımların hiçbirini geçersiz kılmaz, onlar üzerine kurulur.

Model:

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

Yapılandırma:

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

Bağlam:

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) yanıt

Şimdi LastName = "Smith" ile üyelerin tüm yorumlarını bulmak istiyorsanız, örneğin şöyle bir sorgu yazabilirsiniz:

Bu hala işe yarıyor ...

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

... ama şimdi de olabilir ...

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

Veya "Smith" adında üyelerin bir listesini oluşturmak için (birden fazla olduğunu varsayıyoruz) yorumlarıyla birlikte bir projeksiyon kullanabilirsiniz:

Bu hala işe yarıyor ...

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

... ama şimdi de olabilir ...

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

Bir üyeden yorumu kaldırmak istiyorsanız

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

member.Comments.Remove(comment);

Include()Bir üyenin yorumunu yapmak istiyorsanız

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

Tüm bunlar sözdizimsel şeker gibi hissettirir, ancak ek yapılandırmadan geçmeye istekli olmanız durumunda size birkaç avantaj sağlar. Her iki şekilde de her iki yaklaşımdan da en iyi şekilde yararlanabilirsiniz.


LINQ sorgularını yazarken artan okunabilirliği takdir ediyorum. Sadece bu yöntemi benimsemem gerekebilir. Sormak zorundayım, EF EntitySet veritabanındaki görünümü de otomatik olarak güncelliyor mu? Bunun EF5.0 planında açıklanan [Şeker] 'e benzediğini kabul eder misiniz? github.com/dotnet/EntityFramework.Docs/blob/master/…
Krptodr

EntityTypeConfiguration<EntityType>Varlık türünün anahtarını ve özelliklerini neden yeniden tanımladığınızı merak ediyorum . Örneğin Property(x => x.MemberID).HasColumnType("int").IsRequired();, gereksiz gibi görünüyor public int MemberID { get; set; }. Kafa karıştırıcı anlayışımı temizler misiniz lütfen?
dakika

0

TLDR; (EF6 / VS2012U5'teki bir EF editör hatasıyla yarı ilişkili) DB'den bir model oluşturursanız ve m: m tablosunu göremiyorsanız: İlgili iki tabloyu silin -> .edmx'i kaydet -> Veritabanından oluştur / ekle - > Kaydet.

Buraya gelip EF .edmx dosyasında göstermek için özellik sütunları ile çoktan çoğa ilişkinin nasıl elde edileceğini merak edenler için (şu anda bir gezinme özellikleri kümesi olarak gösterilmeyecek ve işlem görmeyeceği için) VE bu sınıfları oluşturdunuz veritabanı tablonuzdan (veya veritabanı ilk MS lingo, inanıyorum.)

.Edmx'inizdeki söz konusu 2 tabloyu silin (OP örneğini, Üyesi ve Yorumu almak için) ve 'Veritabanından model oluştur' ile tekrar ekleyin. (örneğin, Visual Studio'nun bunları güncellemesine izin vermeyin - silme, kaydetme, ekleme, kaydetme)

Daha sonra burada önerilenlerle uyumlu bir 3. tablo oluşturacaktır.

Bu, ilk başta saf çoktan çoğa ilişkinin eklendiği ve özniteliklerin daha sonra DB'de tasarlandığı durumlarda geçerlidir.

Bu konudan / Google'dan hemen anlaşılmadı. Bu nedenle, sorunu Google'a yönlendiren 1 numaralı bağlantı olduğu için, ancak önce DB tarafından gelen olarak ortaya koyuyor.


0

Bu hatayı çözmenin bir yolu, ForeignKey niteliği yabancı anahtar olarak istediğiniz özelliğin üstüne ve navigation özelliğini eklemektir.

Not: ForeignKeyÖznitelikte, parantezler ve çift tırnak işaretleri arasında, söz konusu sınıfın adını bu şekilde yerleştirin.

resim açıklamasını buraya girin


Lütfen yanıtın kendisine asgari bir açıklama ekleyin, çünkü sağlanan bağlantı gelecekte kullanılamayabilir.
n4m31ess_c0d3r

2
Sınıf değil , navigation özelliğinin adı olmalıdır .
aaron
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.