我正在实现一个网页功能,该功能可以同时将大量数据(3x10 ^ 5行左右)上传到数据库中的不同表。用户将使用此数据生成一个excel文件,并将其上传到服务器。我正在使用C#MVC构建网页,并使用“ ExcelDataReader”库读取Excel文件。开始时,我使用“ .AsDataSet”方法检索数据,但在我的场景中读取DataTable极其缓慢且内存效率低下,因此我创建了自己的函数,该函数读取每行一行并将所有内容存储在列表的列表中。字符串。由于我正在处理许多与数据库中其他表具有一对多关系的表,因此我使用字典来存储在此过程中创建的实体,因此如果以后需要它们的话,检索它们将变得更加容易。从理论上讲,我的for循环中的所有操作都是O(1),所以我不明白两件事:
-花超过五分钟的时间循环遍历所有行的一半 -当达到一半时,我的视觉工作室就崩溃了,没有任何消息。
我正在使用Visual Studio 2015,我的PC统计信息为:i5 6500 +内存8GB ddr4 除了Visual Studio,我运行的唯一程序是Microsoft Edge
我的数据库中的表具有索引,因此从理论上讲,像在for循环中一样查询它们应该不会降低它的速度。
更新 我放了一些秒表,看起来“ MATRICULA”一词下面的部分正在减慢一切。执行该部分每次迭代需要00:00:00.0018949。有什么建议可以改善吗?
此外,我禁用了Visual Studio诊断工具,并且它不再崩溃,但是整个过程大约需要15分钟才能完成,而且我想使其运行得更快。
var nuevosALumnos = new List<Alumno>(cantidadFilas);
var nuevosPeriodos = new List<Periodo>();
var nuevasSecciones = new List<Seccion>(cantidadFilas);
var nuevosCursos = new List<Curso>(cantidadFilas);
var nuevasLineas = new List<Linea>();
var nuevasMatriculas = new List<Matricula>(cantidadFilas);
var periodosUsados = new Dictionary<String, Periodo>();
var alumnosUsados = new Dictionary<String, Alumno>(cantidadFilas);
var seccionesUsadas = new Dictionary<Tuple<String, String, String>, Seccion>(cantidadFilas);
var cursosUsados = new Dictionary<String, Curso>(cantidadFilas);
var lineasUsadas = new Dictionary<String, Linea>();
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
for (int i = 1; i < cantidadFilas; i++)
{
if (dataTable[i][7] != "")
{
if (!alumnosUsados.ContainsKey(dataTable[i][7] ))
{
llaveAuxiliar = dataTable[i][7] ;
Alumno buscarAlumno = context.Alumno.FirstOrDefault(x => x.codigo == llaveAuxiliar);
if (buscarAlumno == null)
{
buscarAlumno = new Alumno();
```
gathering data
```
nuevosALumnos.Add(buscarAlumno);
}
alumnosUsados.Add(buscarAlumno.codigo, buscarAlumno);
}
}
else {
datosErroneos.Add("En la fila: " + i + " columna: H el codigo de alumno es nulo");
}
if (dataTable[i][1] != "")
{
if (!periodosUsados.ContainsKey(dataTable[i][1] ))
{
llaveAuxiliar = dataTable[i][1] ;
var buscarPeriodo = context.Periodo.FirstOrDefault(x => x.codigo_periodo == llaveAuxiliar);
if (buscarPeriodo == null)
{
buscarPeriodo = new Periodo();
buscarPeriodo.codigo_periodo = dataTable[i][1] ;
// context.Periodo.Add(buscarPeriodo);
nuevosPeriodos.Add(buscarPeriodo);
}
periodosUsados.Add(dataTable[i][1] , buscarPeriodo);
}
} else
{
datosErroneos.Add("En la fila: " + i + " columna: B el codigo del ciclo es nulo");
}
if (dataTable[i][6] != ConstantHelpers.TIPO_MATRICULA_EXTRANJERO)
{
if (dataTable[i][26] != "")
{
if (!lineasUsadas.ContainsKey(dataTable[i][26] ))
{
llaveAuxiliar = dataTable[i][26] ;
var buscarLinea = context.Linea.FirstOrDefault(x => x.descripcion == llaveAuxiliar);
if (buscarLinea == null)
{
buscarLinea = new Linea();
buscarLinea.descripcion = llaveAuxiliar;
//context.Linea.Add(buscarLinea);
nuevasLineas.Add(buscarLinea);
}
lineasUsadas.Add(dataTable[i][26] , buscarLinea);
}
}
else
{
datosErroneos.Add("En la fila: " + i + " columna: AA la descripción de la linea está vacía");
}
if (dataTable[i][33] != "")
{
if (!cursosUsados.ContainsKey(dataTable[i][33] ))
{
llaveAuxiliar = dataTable[i][33] ;
var buscarCurso = context.Curso.FirstOrDefault(x => x.codigo == llaveAuxiliar);
if (buscarCurso == null)
{
buscarCurso = new Curso();
```
gathering data
```
buscarCurso.Linea = lineasUsadas[dataTable[i][26] ];
nuevosCursos.Add(buscarCurso);
}
cursosUsados.Add(dataTable[i][33] , buscarCurso);
}
}
else
{
datosErroneos.Add("En la fila: " + i + " columna: Y el codigo del curso es nulo");
}
if (dataTable[i][30] != "")
{
//codigo, periodo, curso
if (!seccionesUsadas.ContainsKey(new Tuple<string, string, string>(dataTable[i][30], dataTable[i][1], dataTable[i][24])))
{
llaveAuxiliar = dataTable[i][30] ;
llaveAuxiliar2 = dataTable[i][1] ;
llaveAuxiliar3 = dataTable[i][24] ;
var querySeccion = context.Database.SqlQuery<Seccion>("select a.* from Seccion a, periodo b, curso c where a.cursoId = c.id and a.periodoId = b.PeriodoId and b.codigo_periodo = '" + llaveAuxiliar2 + "' and c.codigo = '" + llaveAuxiliar3 + "'");
Seccion buscarSeccion;
if (querySeccion.Count() == 0)
{
buscarSeccion = new Seccion();
buscarSeccion.codigo = dataTable[i][30] ;
buscarSeccion.grupo = dataTable[i][31] ;
buscarSeccion.Curso = cursosUsados[dataTable[i][33] ];
buscarSeccion.Periodo = periodosUsados[dataTable[i][1] ];
if (Int32.TryParse(dataTable[i][32], out auxiliar))
{
buscarSeccion.curriculo = auxiliar;
}
else
{
datosErroneos.Add("En la fila: " + i + " columna: AG no hay un número");
}
//context.Seccion.Add(buscarSeccion);
nuevasSecciones.Add(buscarSeccion);
}else
{
buscarSeccion = querySeccion.ElementAt(0);
}
seccionesUsadas.Add(new Tuple<string, string, string>(dataTable[i][30], dataTable[i][1], dataTable[i][24]), buscarSeccion);
}
}
else
{
datosErroneos.Add("En la fila: " + i + " columna: AE el codigo de la sección es nula");
}
//MATRICULA
if (dataTable[i][7] != "" && dataTable[i][30] != "")
{
auxiliar = alumnosUsados[dataTable[i][7] ].id;
auxiliar2 = seccionesUsadas[new Tuple<string, string, string>(dataTable[i][30], dataTable[i][1], dataTable[i][24])].SeccionId;
var objMatricula = context.Matricula.FirstOrDefault(x => x.alumnoId == auxiliar && x.seccionId == auxiliar2);
if (objMatricula == null)
{
objMatricula = new Matricula();
objMatricula.Seccion = seccionesUsadas[new Tuple<string, string, string>(dataTable[i][30], dataTable[i][1], dataTable[i][24])];
objMatricula.Alumno = alumnosUsados[dataTable[i][7] ];
objMatricula.sede = dataTable[i][13] ;
if (Int32.TryParse(dataTable[i][35] , out auxiliar))
{
objMatricula.cantidad_matriculas = auxiliar;
}
else
{
datosErroneos.Add("En la fila: " + i + " columna: AJ no hay un número");
}
if (Int32.TryParse(dataTable[i][36] , out auxiliar))
{
objMatricula.nota = auxiliar;
objMatricula.estado = ConstantHelpers.ESTADO_ESTUDIANDO;
}
else
{
objMatricula.estado = ConstantHelpers.ESTADO_RETIRADO;
}
//context.Matricula.Add(objMatricula);
nuevasMatriculas.Add(objMatricula);
}
}
}
cantidad++;
}
答案 0 :(得分:1)
有很多事情可能会破坏性能。一次击中约300万行绝不是一个好主意。首先,您需要为所有集合和词典保留大量内存以供处理。然后,事实是DbContext对整个操作都是打开的,因此,也跟踪装入或关联到该上下文的每个单个实体。打开上下文的时间越长,跟踪的实体越多,事情就变得越慢。
接下来还有其他一些小细节可以帮助降低性能。仅检查实体是否存在而进行FirstOrDefault
是完全的性能浪费。使用.Any
。
即 if(!context.Matricula.Any(x => x.alumnoId ==辅助&& x.seccionId ==辅助2);
FirstOrDefault
返回实体数据或#null,Any
执行查询,如果该实体存在,则仅返回True或False。 =更快,更少的内存浪费。
要摆脱的主要问题是: 将处理分为可管理的块,一次最多只能说1000个。您可以加载行字典,但将其拆分为1000,在其中调用一种方法来处理新DbContext中的每1000个而不是所有记录中的一个上下文。如果您希望能够可靠地回滚更改(如果一批1000次失败),则建议使用显式事务(更安全,但速度更慢)或使用表上的标记列来指示它们处于挂起状态。在需要跟踪已成功导入的记录或需要解决的问题的地方,建议您使用ID列表而不是整个实体列表来节省内存。
答案 1 :(得分:0)
您正在使用许多词典,每个词典都需要大量内存。每次Dictionary
用尽其内部缓冲区时,都必须创建一个新缓冲区,并且调整大小后的缓冲区的大小是前一个缓冲区的两倍以上。发生这种情况是因为该大小必须是质数。内部有一个列表,其中包含预先计算的素数,但此列表的最大值为7199369。超出此点时,下一个素数由蛮力计算。因此,也许您已经到达危险区域,那里的一切不再高效。
public static readonly int[] primes = {
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131,
163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861,
5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023,
25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523,
108631, 130363, 156437, 187751, 225307, 270371, 324449,
389357, 467237, 560689, 672827, 807403, 968897, 1162687,
1395263, 1674319, 2009191, 2411033, 2893249, 3471899,
4166287, 4999559, 5999471, 7199369};