Guardado de datos

Aunque las consultas permiten leer datos de la base de datos, guardar los datos significa agregar nuevas entidades a la base de datos, quitar entidades o modificar las propiedades de las entidades existentes de alguna manera. Entity Framework Core (EF Core) admite dos enfoques fundamentales para guardar datos en la base de datos.

Enfoque 1: seguimiento de cambios y SaveChanges

En muchos escenarios, el programa debe consultar algunos datos de la base de datos, realizar alguna modificación en ella y volver a guardar las modificaciones; esto a veces se conoce como "unidad de trabajo". Por ejemplo, supongamos que tiene un conjunto de blogs y desea cambiar la propiedad Url de uno de ellos. En EF, normalmente esto se hace de la siguiente manera:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.Url == "http://example.com");
    blog.Url = "http://example.com/blog";
    context.SaveChanges();
}

El código anterior realiza los pasos siguientes:

  1. Usa una consulta LINQ normal para cargar una entidad desde la base de datos (consulte Consulta de datos). Las consultas de EF realizan el seguimiento de forma predeterminada, lo que significa que EF realiza un seguimiento de las entidades cargadas en su control de cambios interno.
  2. La instancia de la entidad cargada se manipula como de costumbre, mediante la asignación de una propiedad .NET. EF no participa en este paso.
  3. Por último, se llama a DbContext.SaveChanges(). En este momento, EF detecta automáticamente los cambios al comparar las entidades con una instantánea desde el momento en que se cargaron. Los cambios detectados se conservan en la base de datos; cuando se usa una base de datos relacional, esto suele implicar el envío, por ejemplo, de SQL UPDATE para actualizar las filas pertinentes.

Tenga en cuenta que lo anterior describe una operación de actualización típica de los datos existentes, pero los principios similares abogan por agregar y quitar entidades. Interactúe con el control de cambios de EF mediante una llamada a DbSet<TEntity>.Add y Remove, que supondrá el seguimiento de los cambios. A continuación, EF aplica todos los cambios realizados en la base de datos cuando se llama a SaveChanges() (por ejemplo, a través de SQL INSERT y DELETE cuando se usa una base de datos relacional).

SaveChanges() ofrece las siguientes ventajas:

  • No es necesario escribir código para realizar un seguimiento de las entidades y propiedades que han cambiado: EF lo hace automáticamente y solo actualiza esas propiedades en la base de datos, lo que mejora el rendimiento. Imagine que las entidades cargadas se enlazan a un componente de la interfaz de usuario, permitiendo así a los usuarios cambiar cualquier propiedad que deseen; EF elimina la carga de averiguar qué entidades y propiedades se cambiaron realmente.
  • Guardar los cambios en la base de datos a veces puede ser complicado. Por ejemplo, si agrega un blog y algunas entradas para ese blog, es posible que tenga que capturar la clave generada por la base de datos para el blog insertado antes de poder insertar las entradas (ya que deben hacer referencia al blog). EF hace todo esto automáticamente, simplificando el proceso.
  • EF puede detectar problemas de simultaneidad, como cuando otra persona ha modificado una fila de la base de datos entre la consulta y SaveChanges(). Más detalles disponibles en Conflictos de simultaneidad.
  • En las bases de datos que lo admiten, SaveChanges() ajusta automáticamente varios cambios en una transacción, lo que garantiza que los datos sean coherentes si se produce un error. Más detalles disponibles en Transacciones.
  • SaveChanges() también agrupa varios cambios en muchos casos, lo que reduce significativamente el número de recorridos de ida y vuelta de la base de datos y mejora considerablemente el rendimiento. Más detalles disponibles en Actualización eficiente.

Para más información y ejemplos de código sobre el uso básico de SaveChanges(), consulte SaveChanges básico. Para más información sobre el seguimiento de cambios de EF, consulte la introducción al seguimiento de cambios.

Enfoque 2: ExecuteUpdate y ExecuteDelete ("actualización masiva")

Nota:

Esta característica se incluyó por primera vez en EF Core 7.0.

Aunque el seguimiento de cambios y SaveChanges() son una manera eficaz de guardar los cambios, tienen ciertas desventajas.

En primer lugar, SaveChanges() requiere que consulte y realice un seguimiento de todas las entidades que vayan a modificar o eliminar. Si necesita, por ejemplo, eliminar todos los blogs con clasificación por debajo de un umbral determinado, debe consultar, materializar y realizar el seguimiento de un número quizá enorme de filas y que SaveChanges() genere una instrucción DELETE para cada una de ellas. Las bases de datos relacionales proporcionan una alternativa mucho más eficaz: se puede enviar un solo comando DELETE, especificando las filas que se van a eliminar con una cláusula WHERE, pero el modelo SaveChanges() no permite generarlo.

Para admitir este escenario de "actualización masiva", puede usar ExecuteDelete de la siguiente manera:

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

Esto permite expresar una instrucción SQL DELETE a través de operadores LINQ normales, como una consulta LINQ normal, lo que hace que se ejecute el siguiente código SQL en la base de datos:

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Se ejecuta de forma muy eficaz en la base de datos, sin cargar datos de la base de datos ni el control de cambios de EF. Del mismo modo, ExecuteUpdate permite expresar una instrucción SQL UPDATE.

Aunque no cambie las entidades de forma masiva, es posible que sepa exactamente qué propiedades de la entidad desea cambiar. El uso de la API de seguimiento de cambios para realizar el cambio puede ser demasiado complejo: requiere crear una instancia de entidad, realizar un seguimiento mediante Attach, realizar los cambios y, por último, llamar a SaveChanges(). Para estos escenarios, ExecuteUpdate y ExecuteDelete pueden ser una manera considerablemente más sencilla de expresar la misma operación.

Por último, tanto el seguimiento de cambios como el propio SaveChanges() imponen una cierta sobrecarga en tiempo de ejecución. Si está escribiendo una aplicación de alto rendimiento, ExecuteUpdate y ExecuteDelete permiten evitar estos componentes y generar eficazmente la instrucción que desee.

Sin embargo, tenga en cuenta que ExecuteUpdate y ExecuteDelete también tienen ciertas limitaciones:

  • Estos métodos se ejecutan inmediatamente y actualmente no se pueden procesar por lotes con otras operaciones. Por otro lado, SaveChanges() procesa por lotes varias operaciones juntas.
  • Dado que no se realiza seguimiento de cambios, es responsabilidad del usuario saber exactamente qué entidades y propiedades deben cambiarse. Esto puede significar un seguimiento de código más manual y pormenorizado de lo que necesita cambio y lo que no.
  • Además, dado como no hay seguimiento de cambios, estos métodos no aplican automáticamente el control de simultaneidad al conservar los cambios. Sin embargo, puede agregar explícitamente una cláusula Where para implementar el control de simultaneidad usted mismo.
  • Actualmente solo se admiten la actualización y la eliminación; la inserción debe realizarse con DbSet<TEntity>.Add y SaveChanges().

Para más información y ejemplos de código, consulte ExecuteUpdate y ExecuteDelete.

Resumen

A continuación se muestran algunas directrices de cuándo usar cada enfoque. Tenga en cuenta que estas no son normas estrictas, sino guías útiles:

  • Si no sabe con antelación qué cambios se realizarán, use SaveChanges; detectará automáticamente qué cambios se deben aplicar. Escenarios de ejemplo:
    • "Quiero cargar un blog de la base de datos y mostrar un formulario que permita al usuario cambiarlo"
  • Si necesita manipular un grafo de objetos (es decir, varios objetos interconectados), use SaveChanges; averiguará el orden adecuado de los cambios y cómo vincularlo todo junto.
    • "Quiero actualizar un blog, cambiar algunas de sus publicaciones y eliminar otras"
  • Si desea cambiar un número quizá elevado de entidades en función de algún criterio, use ExecuteUpdate y ExecuteDelete. Escenarios de ejemplo:
    • "Quiero darles un aumento a todos los empleados"
    • "Quiero eliminar todos los blogs cuyo nombre comience por X"
  • Si ya sabe exactamente qué entidades desea modificar y cómo desea cambiarlas, use ExecuteUpdate y ExecuteDelete. Escenarios de ejemplo:
    • "Quiero eliminar el blog que se llama 'Foo'"
    • "Quiero cambiar el nombre del blog con el identificador 5 a 'Bar'"