Crea il codice prima, molti a molti, con campi aggiuntivi nella tabella delle associazioni

Ho questo scenario:

public class Member { public int MemberID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection Comments { get; set; } } public class Comment { public int CommentID { get; set; } public string Message { get; set; } public virtual ICollection 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; } } 

Come configuro la mia associazione con l’ API fluente ? O c’è un modo migliore per creare la tabella di associazione?

Non è ansible creare una relazione molti-a-molti con una tabella di join personalizzata. In una relazione molti-a-molti, EF gestisce la tabella di join internamente e nascosta. È una tabella senza una class Entity nel tuo modello. Per lavorare con una tabella di join simile con proprietà aggiuntive, è necessario creare in realtà due relazioni uno-a-molti. Potrebbe assomigliare a questo:

 public class Member { public int MemberID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection MemberComments { get; set; } } public class Comment { public int CommentID { get; set; } public string Message { get; set; } public virtual ICollection 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; } } 

Se ora desideri trovare tutti i commenti dei membri con LastName = “Rossi” per esempio puoi scrivere una query come questa:

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

…o…

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

O per creare un elenco di membri con il nome “Smith” (presumiamo che ce ne siano più di uno) insieme ai loro commenti è ansible utilizzare una proiezione:

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

Se si desidera trovare tutti i commenti di un membro con MemberId = 1:

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

Ora puoi anche filtrare le proprietà nella tabella di join (che non sarebbe ansible in una relazione molti-a-molti), ad esempio: Filtra tutti i commenti del membro 1 che hanno una proprietà 99 in Something :

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

A causa del caricamento lento, le cose potrebbero diventare più facili. Se hai un Member caricato dovresti essere in grado di ottenere i commenti senza una query esplicita:

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

Immagino che il caricamento pigro recupererà automaticamente i commenti dietro le quinte.

modificare

Solo per divertimento alcuni esempi più come aggiungere quadro e relazioni e come eliminarle in questo modello:

1) Crea un membro e due commenti di questo membro:

 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) Aggiungi un terzo commento di 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) Crea un nuovo membro e collegalo al commento esistente2:

 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) Creare una relazione tra membro esistente2 e commento3:

 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) Elimina nuovamente questa relazione:

 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) Elimina membro1 e tutti i suoi rapporti con i commenti:

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

Questo elimina le relazioni anche in MemberComments perché le relazioni uno-a-molti tra Member e MemberComments e tra Comment e MemberComments sono configurate con eliminazione per convenzione. E questo è il caso perché MemberId e CommentId in MemberComment vengono rilevati come proprietà della chiave esterna per le proprietà di navigazione Member e Comment e poiché le proprietà FK sono di tipo non nullable int è richiesta la relazione che determina infine l’installazione di eliminazione della sovrapposizione. Ha senso in questo modello, penso.

Ottima risposta di Slauma.

Inserirò il codice per farlo utilizzando la mapping API fluente.

 public class User { public int UserID { get; set; } public string Username { get; set; } public string Password { get; set; } public ICollection UserEmails { get; set; } } public class Email { public int EmailID { get; set; } public string Address { get; set; } public ICollection UserEmails { get; set; } } public class UserEmail { public int UserID { get; set; } public int EmailID { get; set; } public bool IsPrimary { get; set; } } 

Sulla tua class derivata DbContext puoi fare questo:

 public class MyContext : DbContext { protected override void OnModelCreating(DbModelBuilder builder) { // Primary keys builder.Entity().HasKey(q => q.UserID); builder.Entity().HasKey(q => q.EmailID); builder.Entity().HasKey(q => new { q.UserID, q.EmailID }); // Relationships builder.Entity() .HasRequired(t => t.Email) .WithMany(t => t.UserEmails) .HasForeignKey(t => t.EmailID) builder.Entity() .HasRequired(t => t.User) .WithMany(t => t.UserEmails) .HasForeignKey(t => t.UserID) } } 

Ha lo stesso effetto della risposta accettata, con un approccio diverso, che non è migliore né peggiore.

EDIT: ho modificato CreatedDate da bool a DateTime.

EDIT 2: A causa della mancanza di tempo, ho inserito un esempio da un’applicazione a cui sto lavorando per assicurarmi che funzioni.

@ Esteban, il codice che hai fornito è giusto, grazie, ma incompleto, l’ho provato. Ci sono proprietà mancanti nella class “UserEmail”:

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

Inserisco il codice che ho testato se qualcuno è interessato. Saluti

 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 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 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 UserTest { get; set; } //public System.Data.Entity.DbSet EmailTest { get; set; } internal static void RelateFluent(System.Data.Entity.DbModelBuilder builder) { // Primary keys builder.Entity().HasKey(q => q.UserTestID); builder.Entity().HasKey(q => q.EmailTestID); builder.Entity().HasKey(q => new { q.UserTestID, q.EmailTestID }); // Relationships builder.Entity() .HasRequired(t => t.EmailTest) .WithMany(t => t.UserTestEmailTests) .HasForeignKey(t => t.EmailTestID); builder.Entity() .HasRequired(t => t.UserTest) .WithMany(t => t.UserTestEmailTests) .HasForeignKey(t => t.UserTestID); } } #endregion 

TLDR; (semi-correlato a un bug dell’editor EF in EF6 / VS2012U5) se si genera il modello da DB e non si può vedere la tabella m: m attribuita: Elimina le due tabelle correlate -> Salva .edmx -> Genera / aggiungi dal database – > Salva.

Per coloro che sono venuti qui chiedendosi come ottenere una relazione molti-a-molti con colonne di attributi da mostrare nel file .edmx di EF (come al momento non dovrebbe essere visualizzato e trattato come un insieme di proprietà di navigazione), E hai generato queste classi credo che dalla tua tabella di database (o database-prima nel gergo della MS).

Elimina le 2 tabelle in questione (per prendere l’esempio OP, membro e commento) nel tuo .edmx e aggiungili di nuovo tramite “Genera modello dal database”. (es. non tentare di consentire a Visual Studio di aggiornarli – eliminare, salvare, aggiungere, salvare)

Creerà quindi una terza tabella in linea con quanto suggerito qui.

Ciò è rilevante nei casi in cui una relazione pura molti-a-molti viene aggiunta all’inizio e gli attributi sono progettati nel DB in seguito.

Questo non è stato immediatamente chiaro da questo thread / su Google. Quindi limitiamoci a pubblicarlo perché questo è il link n. 1 su Google che cerca il problema, ma che viene prima dal lato DB.

Un modo per risolvere questo errore consiste nel mettere l’attributo ForeignKey sopra la proprietà che si desidera come chiave esterna e aggiungere la proprietà di navigazione.

Nota: nell’attributo ForeignKey , tra parentesi e virgolette, posizionare il nome della class a cui si fa riferimento in questo modo.

inserisci la descrizione dell'immagine qui

Voglio proporre una soluzione in cui sia ansible ottenere entrambi i sapori di una configurazione molti-a-molti.

Il “catch” è che dobbiamo creare una vista che punti alla tabella Join, poiché EF convalida che la tabella di uno schema può essere mappata al massimo una volta per EntitySet .

Questa risposta si aggiunge a ciò che è già stato detto nelle risposte precedenti e non sovrascrive nessuno di questi approcci, si basa su di essi.

Il modello:

 public class Member { public int MemberID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection Comments { get; set; } public virtual ICollection MemberComments { get; set; } } public class Comment { public int CommentID { get; set; } public string Message { get; set; } public virtual ICollection Members { get; set; } public virtual ICollection 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; } } 

La configurazione:

 using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; public class MemberConfiguration : EntityTypeConfiguration { 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 { public CommentConfiguration() { HasKey(x => x.CommentID); Property(x => x.CommentID).HasColumnType("int").IsRequired(); Property(x => x.Message).HasColumnType("varchar(max)"); } } public class MemberCommentViewConfiguration : EntityTypeConfiguration { 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); } } 

Il contesto:

 using System.Data.Entity; public class MyContext : DbContext { public DbSet Members { get; set; } public DbSet Comments { get; set; } public DbSet 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); } } 

Dalla risposta di Saluma (@Saluma)

Se ora desideri trovare tutti i commenti dei membri con Cognome = “Rossi” per esempio puoi scrivere una query come questa:

Questo funziona ancora …

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

… ma ora potrebbe anche essere …

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

O per creare un elenco di membri con il nome “Smith” (presumiamo che ce ne siano più di uno) insieme ai loro commenti è ansible utilizzare una proiezione:

Questo funziona ancora …

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

… ma ora potrebbe anche essere …

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

Se si desidera rimuovere un commento da un membro

 var comment = ... // assume comment from member John Smith var member = ... // assume member John Smith member.Comments.Remove(comment); 

Se si desidera Include() commenti di un membro

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

Tutto questo sembra zucchero sintattico, tuttavia ti offre alcuni vantaggi se sei disposto a passare attraverso la configurazione aggiuntiva. In entrambi i casi sembra che tu sia in grado di ottenere il meglio da entrambi gli approcci.