Primero lo probaría 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 hiciera una importación al sistema y disminuiría 10 filas de 1000, solo porque 1 es malo. Puede intentar importar 10 y, si falla, intente uno por uno y luego inicie sesión.
Prueba si lleva mucho tiempo. No escriba "propagable". Aún no lo sabes. Solo cuando sea realmente 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, inténtelo uno por uno 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
{
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);
}
}
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 que procese 100 o 1000 registros a la vez, depende de la CPU. Entonces, al procesar los contextos con un patrón productor / consumidor (implementado con BlockingCollection), pude hacer un uso mucho mejor de los núcleos de la CPU y obtuve 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 pasó de aproximadamente el 13% (tengo 8 núcleos) a aproximadamente el 60%. Incluso usando múltiples hilos de consumo, apenas gravé el sistema de E / S de disco (muy rápido) y la utilización de la CPU de SQL Server no fue superior al 15%.
Al descargar el guardado en varios subprocesos, tiene la capacidad de ajustar tanto el número de registros antes de la confirmación como la cantidad de subprocesos que realizan las operaciones de confirmación.
Descubrí que la creación de 1 subproceso de productor y (# de núcleos de CPU) -1 subprocesos de consumidor me permitió ajustar la cantidad de registros comprometidos por lote de modo que el recuento de elementos en BlockingCollection fluctuara entre 0 y 1 (después de que un subproceso de consumidor tomó uno articulo). De esa manera, había suficiente trabajo para que los subprocesos consumidores funcionaran de manera óptima.
Este escenario, por supuesto, requiere la creación de un nuevo contexto para cada lote, que encuentro que es más rápido incluso en un escenario de un solo subproceso para mi caso de uso.
fuente
Si necesita importar miles de registros, usaría algo como SqlBulkCopy, y no Entity Framework para eso.
fuente
Utilice un procedimiento almacenado.
Creo que esta sería la forma más fácil y rápida de hacerlo.
fuente
Lo siento, sé que este hilo es antiguo, 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 confirmarlos. Mi código se ve así y está funcionando bien. Con el
chUser.LastUpdated
compruebo si es una entrada nueva o solo un cambio. Porque no es posible recargar 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();
fuente
saveChanges()
puede eliminar los que causarían un Error.SaveChanges
llamada. No aborda ese problema. Tenga en cuenta que existen más razones potenciales para que SaveChanges falle que los errores de validación. Por cierto, también puede marcar entidades como enUnchanged
lugar de recargarlas / eliminarlas.SaveChanges
falla. Y esto resuelve el problema. Si esta publicación realmente te molesta en este hilo, puedo eliminar esto, mi problema está resuelto, solo intento ayudar a otrosGetValidationErrors()
¿"falsifica" una llamada a la base de datos y recupera errores o qué? Gracias por responder :)