c# - foreign - entity framework one to many
¿Cómo definir la relación de muchos a muchos a través de Fluent API Entity Framework? (1)
Los términos Left
and Right
en MapLeftKey
y MapRightKey
en el mapeo many-to-many con Fluent API pueden malinterpretarse y supongo que su problema está causado por este malentendido.
Se podría pensar que significa que describen las columnas "izquierda" y "derecha" en la tabla de unión de muchos a muchos. Ese es realmente el caso si permite que EF Code-First cree la base de datos y se una a la tabla en función de su asignación Fluent.
Pero no es necesariamente el caso cuando crea una asignación a una base de datos existente.
Para ilustrar esto con el ejemplo prototípico many-to-many de un modelo de Role
User
, suponga que tiene una base de datos existente con una tabla de Users
, Roles
y RoleUsers
:
Ahora, desea asignar este esquema de tabla a un modelo simple:
public class User
{
public User()
{
Roles = new List<Role>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
}
Y agrega el mapeo Fluent para la entidad Users
(debe hacerlo de esta manera, porque por convención el modelo anterior sería uno a muchos y no puede comenzar desde el lado de la entidad Role
porque no tiene una colección de Users
):
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
{
m.MapLeftKey("RoleId"); // because it is the "left" column, isn''t it?
m.MapRightKey("UserId"); // because it is the "right" column, isn''t it?
m.ToTable("RoleUsers");
});
Este mapeo es incorrecto y si intentas poner a "Anna" en el papel de "Marketing" ...
var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);
anna.Roles.Add(marketing);
ctx.SaveChanges();
... SaveChanges
arrojará exactamente la excepción que está teniendo. El motivo queda claro al capturar el comando SQL que se envía con SaveChanges
:
exec sp_executesql N''insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
'',N''@0 int,@1 int'',@0=1,@1=2
Entonces, EF quiere insertar aquí una fila en la tabla de combinación RoleUsers
con un RoleId
de 1
y una UserId
de 2
que está causando la restricción de restricción de clave externa porque no hay ningún usuario con UserId
2
en la tabla de Users
.
En otras palabras, la asignación anterior ha configurado la columna RoleId
como la clave externa para los Users
tabla y la columna UserId
como la clave externa para Roles
tabla. Para corregir el mapeo tenemos que usar el nombre de la columna "izquierda" en la tabla de MapRightKey
en MapRightKey
y la columna "derecha" en MapLeftKey
:
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
En realidad, al mirar Intellisense, la descripción aclara lo que realmente significa "izquierda" y "derecha":
MapLeftKey
Configura el nombre de la (s) columna (s) para la clave externa izquierda. La clave externa izquierda representa la propiedad de navegación especificada en la llamada HasMany.
MapRightKey
Configura el nombre de la (s) columna (s) para la clave externa correcta. La clave externa derecha representa la propiedad de navegación especificada en la llamada WithMany.
Por lo tanto, "Izquierda" y "Derecha" se refieren al orden en que aparecen las entidades en el mapeo Fluent, no al orden de las columnas en la tabla de unión. El orden en la tabla en realidad no importa, puede cambiarlo sin romper nada porque el INSERT
enviado por EF es un INSERT
"extendido" que también contiene los nombres de las columnas y no solo los valores.
Quizás MapFirstEntityKey
y MapSecondEntityKey
hubieran sido una elección menos engañosa de esos nombres de método, o tal vez MapSourceEntityKey
y MapTargetEntityKey
.
Esta fue una larga publicación sobre dos palabras.
Si mi suposición es correcta de que tiene algo que ver con su problema, entonces diría que su primera asignación es incorrecta y que solo necesita la segunda y correcta asignación.
A continuación está mi modelo:
public class TMUrl
{
//many other properties
//only property with type Keyword
public List<Keyword> Keywords{get;set;}
}
public class Keyword
{
//many other properties
//only property with type TMUrl
public List<TMUrl> Urls{get;set;}
}
Entonces, claramente, ambas entidades tienen una relación de muchos a muchos. Elegí una API apta para contarle a la entidad-marco sobre esta relación, es decir,
modelBuilder.Entity<TMUrl>
.HasMany(s => s.Keywords)
.WithMany(s => s.URLs).Map(s =>
{
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
});
pero cuando lo hago
url.Keywords.Add(dbKey); //where url is object of TMUrl,
//dbKey is an existing/new object of Keyword
db.SaveChanges();
Me sale una excepción
An error occurred while saving entities that do not expose foreign key
properties for their relationships....
InnerException:
The INSERT statement conflicted with the FOREIGN KEY constraint
"KeywordMaster_Keyword". The conflict occurred in database "DbName",
table "dbo.KeywordMaster", column ''Id''.The statement has been terminated.
pero cuando agrego Configuration desde el otro lado, todo funciona bien. es decir
modelBuilder.Entity<KeyWord>
.HasMany(s => s.URLs)
.WithMany(s => s.Keywords)
.Map(s =>
{
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
});
¿Por qué?. Por qué tengo que agregar la configuración de ambas entidades, donde he leído aquí y en muchos otros lugares, la configuración de una de las entidades debería hacer.
¿Cuál es el caso, cuando debería agregar configuración para ambas entidades involucradas en la relación?
Necesito entender esto. Por qué. Por favor ayuda.