¿Cómo evito que Entity Framework intente guardar / insertar objetos secundarios?

Cuando guardo una entidad con marco de entidad, naturalmente asumí que solo trataría de guardar la entidad especificada. Sin embargo, también está intentando salvar las entidades secundarias de esa entidad. Esto está causando todo tipo de problemas de integridad. ¿Cómo obligo a EF a guardar solo la entidad que deseo guardar y, por lo tanto, a ignorar todos los objetos secundarios?

Si configuro manualmente las propiedades como nulas, aparece el mensaje de error “Falló la operación: no se pudo cambiar la relación porque una o más de las propiedades de clave externa no admiten nulos”. Esto es extremadamente contraproducente ya que establecí el objeto hijo nulo específicamente para que EF lo dejara en paz.

¿Por qué no quiero guardar / insertar los objetos secundarios?

Como esto se está discutiendo en los comentarios, daré alguna justificación de por qué quiero que los objetos de mi hijo queden solos.

En la aplicación que estoy construyendo, el modelo de objetos EF no se carga desde la base de datos, sino que se usa como objetos de datos que estoy poblando al analizar un archivo plano. En el caso de los objetos secundarios, muchos de ellos se refieren a tablas de búsqueda que definen varias propiedades de la tabla padre. Por ejemplo, la ubicación geográfica de la entidad principal.

Dado que he poblado estos objetos por mí mismo, EF supone que estos son objetos nuevos y deben insertarse junto con el objeto principal. Sin embargo, estas definiciones ya existen y no quiero crear duplicados en la base de datos. Solo uso el objeto EF para hacer una búsqueda y completar la clave externa en la entidad de mi tabla principal.

Incluso con los objetos secundarios que son datos reales, tengo que guardar al padre primero y obtener una clave primaria o EF solo parece hacer un lío de cosas. Espero que esto dé alguna explicación.

Hasta donde yo sé, tienes dos opciones.

Opción 1)

Anula todos los objetos secundarios, esto asegurará que EF no agregue nada. Tampoco eliminará nada de su base de datos.

Opcion 2)

Establezca los objetos secundarios como separados del contexto utilizando el siguiente código

context.Entry(yourObject).State = EntityState.Detached 

Tenga en cuenta que no puede separar una List / Collection . Deberá pasar por encima de su lista y separar cada elemento de su lista como tal

 foreach (var item in properties) { db.Entry(item).State = EntityState.Detached; } 

Para abreviar: usa la tecla Extranjera y te salvará el día.

Supongamos que tiene una entidad escolar y una entidad de la ciudad , y esta es una relación de muchos a uno donde una ciudad tiene muchas escuelas y una escuela pertenece a una ciudad. Y suponga que las ciudades ya existen en la tabla de búsqueda, por lo que NO quiere que se vuelvan a insertar al insertar una nueva escuela.

Inicialmente, puede definir entidades como esta:

 public class City { public int Id { get; set; } public string Name { get; set; } } public class School { public int Id { get; set; } public string Name { get; set; } [Required] public City City { get; set; } } 

Y puede hacer la inserción en la Escuela de esta manera (suponiendo que ya tiene la propiedad de la Ciudad asignada al newItem ):

 public School Insert(School newItem) { using (var context = new DatabaseContext()) { context.Set().Add(newItem); // use the following statement so that City won't be inserted context.Entry(newItem.City).State = EntityState.Unchanged; context.SaveChanges(); return newItem; } } 

El enfoque anterior puede funcionar perfectamente en este caso, sin embargo, prefiero el enfoque de clave extranjera que para mí es más claro y flexible. Vea la solución actualizada a continuación:

 public class City { public int Id { get; set; } public string Name { get; set; } } public class School { public int Id { get; set; } public string Name { get; set; } [ForeignKey("City_Id")] public City City { get; set; } [Required] public int City_Id { get; set; } } 

De esta manera, usted define explícitamente que la escuela tiene una clave externa City_Id y se refiere a la entidad de la ciudad . Entonces, cuando se trata de la inserción de la escuela , puedes hacer:

  public School Insert(School newItem, int cityId) { if(cityId <= 0) { throw new Exception("City ID no provided"); } newItem.City = null; newItem.City_Id = cityId; using (var context = new DatabaseContext()) { context.Set().Add(newItem); context.SaveChanges(); return newItem; } } 

En este caso, especifiques explícitamente City_Id del nuevo registro y quitas City del gráfico para que EF no se moleste en agregarlo al contexto junto con School .

Aunque en la primera impresión el enfoque de la clave extranjera parece más complicado, pero créanme, esta mentalidad les ahorrará mucho tiempo cuando se trata de insertar una relación de muchos a muchos (las imágenes tienen una relación entre la escuela y el alumno, y el estudiante tiene una propiedad de la ciudad) y así sucesivamente.

Espero que esto sea útil para ti.

Una de las soluciones sugeridas es asignar la propiedad de navegación desde el mismo contexto de base de datos. En esta solución, la propiedad de navegación asignada desde fuera del contexto de la base de datos sería reemplazada. Por favor, mira el siguiente ejemplo para la ilustración.

 class Company{ public int Id{get;set;} public Virtual Department department{get; set;} } class Department{ public int Id{get; set;} public String Name{get; set;} } 

Guardando en la base de datos:

  Company company = new Company(); company.department = new Department(){Id = 45}; //an Department object with Id = 45 exists in database. using(CompanyContext db = new CompanyContext()){ Department department = db.Departments.Find(company.department.Id); company.department = department; db.Companies.Add(company); db.SaveChanges(); } 

Microsoft enlista esto como una característica, sin embargo, esto me parece molesto. Si el objeto del departamento asociado con el objeto de la empresa tiene Id que ya existe en la base de datos, ¿por qué EF no solo asocia el objeto de la compañía con el objeto de la base de datos? ¿Por qué deberíamos tener que cuidar de la asociación por nosotros mismos? Cuidar la propiedad de navegación durante la adición de un nuevo objeto es algo así como mover las operaciones de la base de datos de SQL a C #, engorroso para los desarrolladores.

Primero, debe saber que hay dos formas de actualizar la entidad en EF.

  • Objetos adjuntos

    Cuando cambia la relación de los objetos adjuntos al contexto del objeto mediante uno de los métodos descritos anteriormente, Entity Framework necesita mantener sincronizadas las claves externas, las referencias y las colecciones.

  • Objetos desconectados

    Si está trabajando con objetos desconectados, debe administrar la sincronización manualmente.

En la aplicación que estoy construyendo, el modelo de objetos EF no se carga desde la base de datos, sino que se usa como objetos de datos que estoy poblando al analizar un archivo plano.

Eso significa que está trabajando con un objeto desconectado, pero no está claro si está utilizando una asociación independiente o una asociación de clave externa .

  • Añadir

    Al agregar una entidad nueva con un objeto secundario existente (objeto que existe en la base de datos), si EF no realiza el seguimiento del objeto secundario, el objeto secundario se volverá a insertar. A menos que conecte manualmente el objeto secundario primero.

     db.Entity(entity.ChildObject).State = EntityState.Modified; db.Entity(entity).State = EntityState.Added; 
  • Actualizar

    Puede marcar la entidad como modificada, luego todas las propiedades escalares se actualizarán y las propiedades de navegación simplemente se ignorarán.

     db.Entity(entity).State = EntityState.Modified; 

Graph Diff

Si desea simplificar el código cuando trabaja con un objeto desconectado, puede probar una biblioteca gráfica de diff .

Aquí está la introducción, Introducción a GraphDiff para Entity Framework Code First – Permitir actualizaciones automáticas de un gráfico de entidades separadas .

Código de muestra

  • Inserte la entidad si no existe; de ​​lo contrario, actualice.

     db.UpdateGraph(entity); 
  • Insertar entidad si no existe; de ​​lo contrario, actualice Y inserte el objeto hijo si no existe; de ​​lo contrario, actualice.

     db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject)); 

Si solo desea almacenar cambios en un objeto principal y evitar almacenar cambios en cualquiera de sus objetos secundarios , entonces ¿por qué no simplemente hacer lo siguiente?

 using (var ctx = new MyContext()) { ctx.Parents.Attach(parent); ctx.Entry(parent).State = EntityState.Added; // or EntityState.Modified ctx.SaveChanges(); } 

La primera línea adjunta el objeto principal y el gráfico completo de sus objetos secundarios dependientes al contexto en estado Unchanged .

La segunda línea cambia el estado para el objeto primario solamente, dejando a sus hijos en el estado Unchanged .

Tenga en cuenta que utilizo un contexto recién creado, por lo que evita guardar otros cambios en la base de datos.

La mejor forma de hacerlo es eliminando la función SaveChanges en su contexto de datos.

  public override int SaveChanges() { var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added); // Do your thing, like changing the state to detached return base.SaveChanges(); } 

Esto funcionó para mí:

 // temporarily 'detach' the child entity/collection to have EF not attempting to handle them var temp = entity.ChildCollection; entity.ChildCollection = new HashSet(); .... do other stuff context.SaveChanges(); entity.ChildCollection = temp; 

Sé que es una publicación anterior, sin embargo, si está utilizando el enfoque de primer código, puede lograr el resultado deseado utilizando el siguiente código en su archivo de asignación.

 Ignore(parentObject => parentObject.ChildObjectOrCollection); 

Esto básicamente le indicará a EF que excluya la propiedad “ChildObjectOrCollection” del modelo para que no se asocie a la base de datos.

Lo que hemos hecho es antes de agregar el padre al dbset, desconectar las colecciones secundarias del padre, asegurándonos de empujar las colecciones existentes a otras variables para permitir trabajar más tarde con ellas, y luego reemplazar las colecciones hijo actuales con nuevas colecciones vacías. Establecer las colecciones secundarias como nulas / nada parecía fallar para nosotros. Después de hacer eso, agrega el padre al dbset. De esta forma, los niños no se agregan hasta que usted lo desee.

Tengo el mismo problema cuando bash guardar el perfil, ya saludo la tabla y nuevo para crear el perfil. Cuando inserto el perfil, también lo inserto en saludo. Así que lo intenté antes de guardar cambios ().

db.Entry (Profile.Salutation) .State = EntityState.Unchanged;