No es posible crear una relación de muchos a muchos con una tabla de unión personalizada. En una relación de muchos a muchos, EF administra la tabla de unión de forma interna y oculta. Es una tabla sin una clase de entidad en su modelo. Para trabajar con una tabla de unión con propiedades adicionales, tendrá que crear dos relaciones uno a muchos. Podría verse así:
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; }
}
Si ahora desea encontrar todos los comentarios de miembros con LastName
= "Smith", por ejemplo, puede escribir una consulta como esta:
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 para crear una lista de miembros con el nombre "Smith" (suponemos que hay más de uno) junto con sus comentarios, puede usar una proyección:
var membersWithComments = context.Members
.Where(m => m.LastName == "Smith")
.Select(m => new
{
Member = m,
Comments = m.MemberComments.Select(mc => mc.Comment)
})
.ToList();
Si desea encontrar todos los comentarios de un miembro con MemberId
= 1:
var commentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1)
.Select(mc => mc.Comment)
.ToList();
Ahora también puede filtrar por las propiedades en su tabla de unión (que no sería posible en una relación de muchos a muchos), por ejemplo: Filtre todos los comentarios del miembro 1 que tienen una propiedad 99 en Something
:
var filteredCommentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1 && mc.Something == 99)
.Select(mc => mc.Comment)
.ToList();
Debido a la carga lenta, las cosas podrían volverse más fáciles. Si tiene una carga Member
, debería poder obtener los comentarios sin una consulta explícita:
var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);
Supongo que la carga diferida buscará los comentarios automáticamente detrás de escena.
Editar
Solo por diversión, algunos ejemplos más sobre cómo agregar entidades y relaciones y cómo eliminarlas en este modelo:
1) Cree un miembro y dos comentarios de este miembro:
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) Agregue un tercer comentario de 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) Crear un nuevo miembro y relacionarlo con el comentario existente2:
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) Crear una relación entre el miembro2 existente y el comentario3:
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) Eliminar esta relación nuevamente:
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) Eliminar member1 y todas sus relaciones con los comentarios:
var member1 = context.Members.Where(m => m.FirstName == "Pete")
.SingleOrDefault();
if (member1 != null)
{
context.Members.Remove(member1);
context.SaveChanges();
}
Esto también elimina las relaciones MemberComments
porque las relaciones uno a muchos entre Member
yMemberComments
y entre Comment
y MemberComments
están configurados con la eliminación en cascada por convención. Y este es el caso porque MemberId
y CommentId
en MemberComment
se detectan como propiedades de clave externa para las propiedades de navegación Member
y Comment
, y dado que las propiedades FK son de tipo no anulable, int
se requiere la relación que finalmente causa la configuración de eliminación en cascada. Tiene sentido en este modelo, creo.
OnModelCreating
. El ejemplo solo se basa en convenciones de mapeo y anotaciones de datos.MemberId
yCommentId
columnas y no una tercera columna adicionalMember_CommentId
(o algo así), lo que significa que no tenía nombres coincidentes exactos a través de objetos para sus llavesExcelente respuesta de Slauma.
Solo publicaré el código para hacer esto usando la asignación fluida de API .
En su
DbContext
clase derivada, podría hacer esto:Tiene el mismo efecto que la respuesta aceptada, con un enfoque diferente, que no es ni mejor ni peor.
EDITAR:
he cambiado CreatedDate de bool a DateTime.EDIT 2: debido a la falta de tiempo, he puesto un ejemplo de una aplicación en la que estoy trabajando para asegurarme de que esto funcione.
fuente
In your classes you can easily describe a many to many relationship with properties that point to each other.
tomado de: msdn.microsoft.com/en-us/data/hh134698.aspx . Julie Lerman no puede estar equivocada.Comments
propiedadMember
. Y no puede simplemente solucionar esto cambiando el nombre de laHasMany
llamada aMemberComments
porque laMemberComment
entidad no tiene una colección inversa paraWithMany
. De hecho, debe configurar dos relaciones uno a muchos para obtener la asignación correcta.@Esteban, el código que proporcionó es correcto, gracias, pero incompleto, lo probé. Faltan propiedades en la clase "UserEmail":
Publico el código que he probado si alguien está interesado. Saludos
fuente
Quiero proponer una solución donde se puedan lograr ambos sabores de una configuración de muchos a muchos.
El "problema" es que necesitamos crear una vista que se dirija a la Tabla de unión, ya que EF valida que la tabla de un esquema se pueda asignar como máximo una vez por cada
EntitySet
.Esta respuesta se suma a lo que ya se ha dicho en las respuestas anteriores y no anula ninguno de esos enfoques, se basa en ellos.
El modelo:
La configuración:
El contexto:
A partir de Salumã (@Saluma) respuesta
Esto todavía funciona ...
... pero ahora también podría ser ...
Esto todavía funciona ...
... pero ahora también podría ser ...
Si quieres eliminar un comentario de un miembro
Si quieres
Include()
los comentarios de un miembroTodo esto se siente como el azúcar sintáctico, sin embargo, le da algunas ventajas si está dispuesto a pasar por la configuración adicional. De cualquier manera, parece ser capaz de obtener lo mejor de ambos enfoques.
fuente
EntityTypeConfiguration<EntityType>
la clave y las propiedades del tipo de entidad. Por ejemplo,Property(x => x.MemberID).HasColumnType("int").IsRequired();
parece ser redundante conpublic int MemberID { get; set; }
. ¿Podría aclarar mi confusa comprensión, por favor?TLDR; (semi-relacionado con un error del editor EF en EF6 / VS2012U5) si genera el modelo desde la base de datos y no puede ver la tabla m: m atribuida: elimine las dos tablas relacionadas -> Guardar .edmx -> Generar / agregar desde la base de datos - > Guardar.
Para aquellos que vinieron aquí preguntándose cómo obtener una relación de muchos a muchos con columnas de atributos para mostrar en el archivo EF .edmx (ya que actualmente no se mostraría y sería tratado como un conjunto de propiedades de navegación), Y generó estas clases de su tabla de base de datos (o base de datos primero en la jerga de MS, creo).
Elimine las 2 tablas en cuestión (para tomar el ejemplo de OP, Miembro y Comentario) en su .edmx y agréguelas nuevamente a través de 'Generar modelo a partir de la base de datos'. (es decir, no intente dejar que Visual Studio los actualice: eliminar, guardar, agregar, guardar)
Luego creará una tercera tabla en línea con lo que se sugiere aquí.
Esto es relevante en los casos en que se agrega una relación pura de muchos a muchos al principio, y los atributos se diseñan en la base de datos más adelante.
Esto no fue aclarado inmediatamente de este hilo / Google. Así que solo lo expongo, ya que este es el enlace n. ° 1 en Google buscando el problema, pero primero viene del lado de la base de datos.
fuente
Una forma de resolver este error es colocar el
ForeignKey
atributo en la parte superior de la propiedad que desea como clave externa y agregar la propiedad de navegación.Nota: En el
ForeignKey
atributo, entre paréntesis y comillas dobles, coloque el nombre de la clase mencionada de esta manera.fuente