Сначала создайте код, многие ко многим, с дополнительными полями в таблице ассоциаций
У меня есть такой сценарий:
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; }
}
Как настроить мою связь с fluent API? Или есть лучший способ создать таблицу ассоциаций?
6 ответов:
невозможно создать отношения "многие ко многим" с помощью настраиваемой таблицы соединений. В отношениях "многие ко многим" EF управляет таблицей соединений внутренне и скрыто. Это таблица без класса сущности в модель. Для работы с такой таблицей соединений с дополнительными свойствами вам придется создать фактически два отношения "один ко многим". Это может выглядеть так:
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();
или создать список членов с именем "Смит" (Мы предполагаем, что есть более одного) вместе с их комментариями вы можете использовать проекцию:
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();
теперь вы также можете фильтровать по свойствам в вашей таблице соединений (что было бы невозможно в отношениях "многие ко многим"), например: фильтровать все комментарии члена 1, которые имеют 99 в свойстве
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);
я думаю, что ленивая загрузка будет автоматически получать комментарии за кулисами.
Edit
просто для удовольствия еще несколько примеров, как добавить сущности и отношения и как удалите их в этой модели:
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) создать новый член и связать его с существующим 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
тоже потому, что отношения один-ко-многим междуMember
иMemberComments
и междуComment
иMemberComments
устанавливаются с каскадным удалением по соглашению. И это так, потому чтоMemberId
иCommentId
наMemberComment
определяются как свойства внешнего ключа дляMember
иComment
свойства навигации и поскольку свойства FK имеют тип nullableint
требуется связь, которая наконец, вызывает каскадные удаления-установки. Смысл в этой модели, я думаю.
отлично ответ Slauma.
Я просто опубликую код, чтобы сделать это с помощью fluent 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: Из-за нехватки времени я разместил пример из приложения, над которым я работаю, чтобы убедиться, что это работает.
@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
TLDR; (полу-связанный с ошибкой редактора EF в EF6 / VS2012U5) Если вы создаете модель из БД и не видите атрибутивную таблицу m:m: удалите две связанные таблицы -> сохранить .edmx - > создать / добавить из базы данных - > сохранить.
для тех, кто пришел сюда интересно, как получить много-ко-многим отношения со столбцами атрибутов, чтобы показать в EF .edmx-файл (поскольку он в настоящее время не отображается и рассматривается как набор навигационных свойств), и вы создали их классы из вашей таблицы базы данных (или базы данных-сначала в MS lingo, я считаю.)
удалите 2 таблицы, о которых идет речь (чтобы взять пример OP, член и комментарий) в вашем .edmx и добавить их снова через "создать модель из базы данных". (т. е. не пытайтесь позволить Visual Studio обновить их - удалить, сохранить, добавить, сохранить)
затем он создаст 3-ю таблицу в соответствии с тем, что предлагается здесь.
Это актуально в тех случаях, когда чистое отношение "многие ко многим" является сначала добавляется, а атрибуты разрабатываются в БД позже.
Это было не сразу понятно из этой темы/погуглить. Так что просто положить его там, как это ссылка #1 на Google ищет проблему, но приходит со стороны БД в первую очередь.
один из способов решить эту ошибку, чтобы поставить
ForeignKey
атрибут поверх свойства, которое вы хотите использовать в качестве внешнего ключа, и добавьте свойство навигации.Примечание:
ForeignKey
атрибут, между скобками и двойными кавычками, поместите имя класса, указанного таким образом.
Я хочу предложить решение, в котором могут быть достигнуты оба варианта конфигурации "многие ко многим".
"catch" нам нужно создать представление, предназначенное для таблицы Join, так как 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) ответ
если вы теперь хотите найти все комментарии членов с фамилией = "Смит" например вы можете написать такой запрос:
это все еще работает...
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);
если вы хотите участника
var member = context.Members .Where(m => m.FirstName == "John", m.LastName == "Smith") .Include(m => m.Comments);
все это похоже на синтаксический сахар, однако он дает вам несколько льгот, если вы готовы пройти дополнительную конфигурацию. Любой таким образом, Вы, кажется, сможете получить лучшее из обоих подходов.