c# - referencia - Entity Framework que crea una nueva entidad con relación a la entidad existente, da como resultado el intento de crear una nueva copia de la entidad existente
no se puede completar la operación porque se ha desechado el dbcontext (4)
Estoy tratando de crear un nuevo objeto de usuario con un rol específico. El "Rol" es una entidad existente en EF. He buscado en Google, y la pila se desbordó hasta que me puse cara azul, y probé todas las cosas que parecen funcionar para todos los demás. Pero cuando trato de guardar mi nuevo objeto de usuario, primero intenta crear una nueva "Función", en lugar de simplemente crear el nuevo objeto de usuario con una referencia a la Función existente.
¿Qué estoy haciendo mal?
Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;
if (myUser.ID == 0)
{
myObjectContext.Users.AddObject(myUser);
}
else
{
if (myUser.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Users.Attach(myUser);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myUser, System.Data.EntityState.Modified);
}
myObjectContext.SaveChanges(SaveOptions.None);
EDITAR - DESPUÉS DE MÁS PRUEBAS ...
Ok ... así que he descubierto alguna parte de la "causa" de todos modos. Todavía no sé por qué hace esto y necesito ayuda.
Básicamente, hay dos conjuntos de datos que estoy adjuntando a mi nuevo objeto Usuario. Uno es el "Rol", que es una tabla de FK a Rol que contiene el Rol. Esto se muestra como una propiedad de navegación en el usuario como "User.Role".
El segundo conjunto de datos es una colección de objetos llamada "FIPS", que es una relación de muchos a muchos entre el usuario y otra tabla llamada FIPS. Hay una tabla de relaciones entre ellos, que simplemente contiene dos columnas, cada una de ellas una clave externa para el usuario y FIPS, respectivamente. Los FIPS para un usuario también son una propiedad de navegación a la que se hace referencia como "User.FIPS".
Aquí está el código completo que muestra la asignación de FIPS y Role al objeto Usuario antes de guardar el contexto.
List<string> fipsList = new List<string>();
foreach (FIPS fips in myUser.FIPS)
{
fipsList.Add(fips.FIPS_Code);
}
myUser.FIPS.Clear();
foreach (string fipsCode in fipsList)
{
FIPS myFIPS = new FIPS { FIPS_Code = fipsCode };
myObjectContext.FIPSCodes.Attach(myFIPS);
myUser.FIPS.Add(myFIPS);
}
Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;
if (myUser.ID == 0)
{
myObjectContext.Users.AddObject(myUser);
}
else
{
if (myUser.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Users.Attach(myUser);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myUser, System.Data.EntityState.Modified);
}
myObjectContext.SaveChanges(SaveOptions.None);
Configuré mi reloj para verificar el estado de "myObjectContext.ObjectStateManager.GetObjectStateEntries (EntityState.Added)" para ver cuándo se agregaron cosas a esto.
Tan pronto como el primer Objeto relacionado se agrega al objeto Usuario, el segundo Objeto relacionado que aún no se ha vinculado al contexto, se agrega al contexto con un EntityState de "Added".
.... Veré si hay una manera de evitar asociar las entidades relacionadas a la entidad Usuario hasta después de que todas se hayan adjuntado al contexto.
--FOLLOWUP-- Ok ... bueno, cambié el orden del código para que las entidades relacionadas se adjuntaran al contexto antes de ser asignadas a la entidad Usuario ... pero tan pronto como se asigna la primera entidad relacionada, la segunda entidad relacionada se muestra como "agregado" en ObjectStateEntries. Entonces, lo cambié al siguiente orden:
- Adjuntar todas las entidades relacionadas al contexto.
- Eliminar las relaciones existentes en el objeto de usuario a los tipos de entidad relacionados.
- Asignar entidades relacionadas a la entidad de usuario.
- Guardar entidad de usuario.
Y ... ahora ... funciona ... Dios mío, funciona ...! =)
¿Por qué está creando una nueva instancia de su entidad de Role
si ya existe en la base de datos?
De todos modos, si desea adjuntar manualmente su nueva instancia al contexto, debería funcionar si el ID de la instancia adjunta existe en la base de datos. Pero en tu caso las siguientes líneas son un poco extrañas:
Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;
Primero crea un nuevo Rol que tiene una ID que proviene de una instancia de Role
existente ( myUser.Role
), luego adjunta su nueva instancia y, finalmente, nuevamente afecta su instancia al usuario del que proviene. Definitivamente hay algo mal aquí. Si su función ya existe (y parece ser el caso aquí cuando escribió myUser.Role.ID
en la primera línea, supongo), ¿por qué está creando una nueva instancia?
Suelta esas 3 líneas. Obtenga su papel de la base de datos. A continuación, afecte el Role
que proviene de la base de datos a la propiedad myUser.Role
Ha pasado un tiempo desde que escribí el código a continuación, pero recuerdo vagamente que me encontré con el mismo problema y que estaba ocurriendo porque el contexto que está agregando el rol que se está agregando actualmente, por lo que adjuntar el talón tiene el efecto de agregar un nuevo rol el mismo id.
En el siguiente código, primero ChangeTracker
el ChangeTracker
y uso una entrada existente si se está rastreando el rol.
// add roles that are in dto.Roles, but not in resource.Roles
// use the change tracker entry, or add a stub role
var rolesToAdd = fromDto.Roles.Where(r => !toResource.Roles.Any(role => role.Id == r)).ToList();
var roleEntries = dbContext.ChangeTracker.Entries<Role>();
foreach (var id in rolesToAdd)
{
var role = roleEntries.Where(e => e.Entity.Id == id).Select(e => e.Entity).FirstOrDefault();
if (role == null)
{
role = new Role { Id = id };
dbContext.Set<Role>().Attach(role);
}
toResource.Roles.Add(role);
}
Intente usar esto en lugar de las tres primeras líneas (que no deberían ser necesarias en absoluto, si el objeto del usuario ya sabe el ID de su rol y se descarta de todos modos):
int id = myUser.Role.ID; // Role should be NULL, if the user is actually new...
// could it be that you wanted to write myUser.RoleID?
Role myRole = myObjectContext.Roles.FirstOrDefault(x => x.ID == id);
myUser.Role = myRole;
Así es como lo hice en mi caso.
Es un caso similar en el que el Item
contiene ICollection<Attribute>
Aquí no se realiza ninguna actualización, es necesario agregar un attribute
ya existente al item
.
Primero hice un looped
través de cada attribute
dentro del item
.
Primero tuve que separarlo del local.
context.Set<Model.Attribute>().Local
.Where(x => x.Id == attr.Id)
.ToList().ForEach(p => context.Entry(p).State = EntityState.Detached);
Entonces me adjunto.
context.Set<Model.Attribute>().Attach(attr);
Luego volví a cargar los datos.
context.Entry(attr).Reload();