una tutorial tipo puede mvc framework first español entidad consulta construir complejo code entity-framework import loops performance savechanges

entity-framework - tutorial - mvc entity framework español



¿Cuándo debería llamar a SaveChanges() al crear miles de objetos de Entity Framework?(como durante una importación) (5)

Acabo de optimizar un problema muy similar en mi propio código y me gustaría señalar una optimización que funcionó para mí.

Descubrí que gran parte del tiempo en el procesamiento de SaveChanges, ya sea al procesar 100 o 1000 registros a la vez, está vinculado a la CPU. Entonces, al procesar los contextos con un patrón de productor / consumidor (implementado con BlockingCollection), pude hacer un mejor uso de los núcleos de CPU y obtuve de un total de 4000 cambios / segundo (según lo informado por el valor de retorno de SaveChanges) a más de 14,000 cambios / segundo. La utilización de la CPU se movió de aproximadamente 13% (tengo 8 núcleos) a aproximadamente 60%. Incluso usando múltiples hilos de consumo, apenas gravaba el (muy rápido) sistema IO de disco y el uso de CPU de SQL Server no era superior al 15%.

Al descargar el guardado en varios hilos, tiene la capacidad de ajustar tanto el número de registros antes de la confirmación como el número de hilos que realizan las operaciones de confirmación.

Descubrí que la creación de 1 hilo de productor y (Nº de núcleos de CPU) -1 hilos de consumo me permitía ajustar el número de registros comprometidos por lote, de modo que el recuento de elementos en BlockingCollection fluctuaba entre 0 y 1 (después de que un hilo de consumidor tomara uno ít). De esa forma, había suficiente trabajo para que los hilos de consumo funcionaran de manera óptima.

Este escenario, por supuesto, requiere crear un nuevo contexto para cada lote, que me parece más rápido incluso en un escenario de subproceso único para mi caso de uso.

Estoy ejecutando una importación que tendrá 1000 de registros en cada ejecución. Solo estoy buscando alguna confirmación sobre mis suposiciones:

¿Cuál de estos tiene más sentido?

  1. Ejecute SaveChanges() cada llamada AddToClassName() .
  2. Ejecute SaveChanges() cada n número de llamadas AddToClassName() .
  3. Ejecute SaveChanges() después de todas las llamadas AddToClassName() .

La primera opción es probablemente lenta ¿no? Ya que necesitará analizar los objetos EF en la memoria, generar SQL, etc.

Supongo que la segunda opción es la mejor de ambos mundos, ya que podemos ajustar una captura de prueba alrededor de esa llamada a SaveChanges() , y solo perder n cantidad de registros a la vez, si uno de ellos falla. Tal vez almacene cada lote en una lista <>. Si la llamada a SaveChanges() tiene éxito, deshágase de la lista. Si falla, registre los artículos.

La última opción probablemente también sea muy lenta, ya que cada objeto EF debería estar en memoria hasta que se SaveChanges() . Y si el ahorro no se hubiera cometido nada, ¿no?


Lo probaría primero para estar seguro. El rendimiento no tiene por qué ser tan malo.

Si necesita ingresar todas las filas en una transacción, llámelo después de toda la clase AddToClassName. Si las filas se pueden ingresar de forma independiente, guarde los cambios después de cada fila. La consistencia de la base de datos es importante.

Segunda opción que no me gusta Sería confuso para mí (desde la perspectiva del usuario final) si realicé la importación al sistema y declinaría 10 filas de 1000, solo porque 1 es malo. Puede tratar de importar 10 y, si falla, intente uno por uno y luego inicie sesión.

Prueba si lleva mucho tiempo. No escriba ''propagablemente''. Tú no lo sabes todavía Solo cuando en realidad es un problema, piense en otra solución (marc_s).

EDITAR

He hecho algunas pruebas (tiempo en milisegundos):

10000 filas:

SaveChanges () después de 1 fila: 18510,534
SaveChanges () después de 100 filas: 4350,3075
SaveChanges () después de 10000 filas: 5233,0635

50000 filas:

SaveChanges () después de 1 fila: 78496,929
SaveChanges () después de 500 filas: 22302,2835
SaveChanges () después de 50000 filas: 24022,8765

Entonces, en realidad es más rápido comprometerse después de n filas que después de todo.

Mi recomendación es:

  • SaveChanges () después de n filas.
  • Si falla una confirmación, pruébela una por una para encontrar la fila defectuosa.

Clases de prueba:

MESA:

CREATE TABLE [dbo].[TestTable]( [ID] [int] IDENTITY(1,1) NOT NULL, [SomeInt] [int] NOT NULL, [SomeVarchar] [varchar](100) NOT NULL, [SomeOtherVarchar] [varchar](50) NOT NULL, [SomeOtherInt] [int] NULL, CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]

Clase:

public class TestController : Controller { // // GET: /Test/ private readonly Random _rng = new Random(); private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string RandomString(int size) { var randomSize = _rng.Next(size); char[] buffer = new char[randomSize]; for (int i = 0; i < randomSize; i++) { buffer[i] = _chars[_rng.Next(_chars.Length)]; } return new string(buffer); } public ActionResult EFPerformance() { string result = ""; TruncateTable(); result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>"; TruncateTable(); return Content(result); } private void TruncateTable() { using (var context = new CamelTrapEntities()) { var connection = ((EntityConnection)context.Connection).StoreConnection; connection.Open(); var command = connection.CreateCommand(); command.CommandText = @"TRUNCATE TABLE TestTable"; command.ExecuteNonQuery(); } } private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows) { var startDate = DateTime.Now; using (var context = new CamelTrapEntities()) { for (int i = 1; i <= noOfRows; ++i) { var testItem = new TestTable(); testItem.SomeVarchar = RandomString(100); testItem.SomeOtherVarchar = RandomString(50); testItem.SomeInt = _rng.Next(10000); testItem.SomeOtherInt = _rng.Next(200000); context.AddToTestTable(testItem); if (i % commitAfterRows == 0) context.SaveChanges(); } } var endDate = DateTime.Now; return endDate.Subtract(startDate); } }


Lo siento, sé que este hilo es viejo, pero creo que esto podría ayudar a otras personas con este problema.

Tuve el mismo problema, pero existe la posibilidad de validar los cambios antes de que los cometas. Mi código se ve así y está funcionando bien. Con chUser.LastUpdated , chUser.LastUpdated si es una entrada nueva o solo un cambio. Porque no es posible volver a cargar una entrada que aún no está en la base de datos.

// Validate Changes var invalidChanges = _userDatabase.GetValidationErrors(); foreach (var ch in invalidChanges) { // Delete invalid User or Change var chUser = (db_User) ch.Entry.Entity; if (chUser.LastUpdated == null) { // Invalid, new User _userDatabase.db_User.Remove(chUser); Console.WriteLine("!Failed to create User: " + chUser.ContactUniqKey); } else { // Invalid Change of an Entry _userDatabase.Entry(chUser).Reload(); Console.WriteLine("!Failed to update User: " + chUser.ContactUniqKey); } } _userDatabase.SaveChanges();



Use un procedimiento almacenado.

  1. Crear un tipo de datos definido por el usuario en el servidor Sql.
  2. Crea y completa una matriz de este tipo en tu código (muy rápido).
  3. Pase la matriz a su procedimiento almacenado con una llamada (muy rápido).

Creo que esta sería la forma más fácil y rápida de hacer esto.