2010-03-10 24 views
5

Estoy utilizando LinqToSQL para procesar datos de SQL Server y volcarlos en un servidor iSeries para su posterior procesamiento. More details on that here.¿Qué es más rápido? Struct array o DataTable

Mi problema es que me toma aproximadamente 1.25 minutos procesar esas 350 filas de datos. Todavía estoy tratando de descifrar los resultados del Analizador de SQL Server, pero hay una TONELADA de consultas que se ejecutan. Aquí es un poco más detalle en lo que estoy haciendo:

using (CarteGraphDataDataContext db = new CarteGraphDataDataContext()) 
{ 
    var vehicles = from a in db.EquipmentMainGenerals 
        join b in db.EquipmentMainConditions on a.wdEquipmentMainGeneralOID equals b.wdEquipmentMainGeneralOID 
        where b.Retired == null 
        orderby a.VehicleId 
        select a; 

    et = new EquipmentTable[vehicles.Count()]; 

    foreach (var vehicle in vehicles) 
    { 
     // Move data to the array 

     // Rates 
     GetVehcileRates(vehicle.wdEquipmentMainGeneralOID); 

     // Build the costs accumulators 
     GetPartsAndOilCosts(vehicle.VehicleId); 
     GetAccidentAndOutRepairCosts(vehicle.wdEquipmentMainGeneralOID); 

     // Last Month's Accumulators 
     et[i].lastMonthActualGasOil = GetFuel(vehicle.wdEquipmentMainGeneralOID) + Convert.ToDecimal(oilCost); 
     et[i].lastMonthActualParts = Convert.ToDecimal(partsCost); 
     et[i].lastMonthActualLabor = GetLabor(vehicle.VehicleId); 
     et[i].lastMonthActualOutRepairs = Convert.ToDecimal(outRepairCosts); 
     et[i].lastMonthActualAccidentCosts = Convert.ToDecimal(accidentCosts); 

     // Move more data to the array 

     i++; 
    } 
} 

los métodos de obtención de todo un aspecto similar a:

private void GetPartsAndOilCosts(string vehicleKey) 
{ 
    oilCost = 0; 
    partsCost = 0; 

    using (CarteGraphDataDataContext db = new CarteGraphDataDataContext()) 
    { 
     try 
     { 
     var costs = from a in db.WorkOrders 
        join b in db.MaterialLogs on a.WorkOrderId equals b.WorkOrder 
        join c in db.Materials on b.wdMaterialMainGeneralOID equals c.wdMaterialMainGeneralOID 
        where (monthBeginDate.Date <= a.WOClosedDate && a.WOClosedDate <= monthEndDate.Date) && a.EquipmentID == vehicleKey 
        group b by c.Fuel into d 
        select new 
          { 
           isFuel = d.Key, 
           totalCost = d.Sum(b => b.Cost) 
          }; 

      foreach (var cost in costs) 
      { 
      if (cost.isFuel == 1) 
      { 
       oilCost = (double)cost.totalCost * (1 + OVERHEAD_RATE); 
      } 
      else 
      { 
       partsCost = (double)cost.totalCost * (1 + OVERHEAD_RATE); 
      } 
      } 
     } 
     catch (InvalidOperationException e) 
     { 
      oilCost = 0; 
      partsCost = 0; 
     } 
    } 

    return; 
} 

Mi pensamiento aquí está reduciendo el número de consultas a la base de datos debería acelerar el procesamiento. Si LINQ hace un SELECCIONAR para cada registro, tal vez deba cargar todos los registros en la memoria primero.

Todavía me considero un principiante con C# y OOP en general (hago principalmente programación RPG en el iSeries). Así que supongo que estoy haciendo algo estúpido. ¿Puedes ayudarme a solucionar mi estupidez (al menos con este problema)?

Actualización: Pensé en volver y actualizar lo que he descubierto. Parece que la base de datos estaba mal diseñada. Lo que LINQ estaba generando en el fondo era un código altamente ineficiente. No digo que el LINQ sea malo, simplemente fue malo para esta base de datos. Convertí a una configuración .XSD rápidamente lanzada. El tiempo de procesamiento pasó de 1.25 minutos a 15 segundos. Una vez que hago un rediseño adecuado, solo puedo adivinar que me ahorraré algunos segundos más de eso. Gracias a todos por sus comentarios. Intentaré LINQ nuevamente algún otro día en una mejor base de datos.

+0

Mis 2 centavos, tablas de datos no tienen ningún lugar en el código de esta década. –

Respuesta

7

Hay algunas cosas que me lugar en su código:

  1. se consulta la base de datos varias veces para cada elemento de consulta los 'vehículos var', es posible que desee volver a escribir esa consulta de modo que menos de base de datos las consultas son necesarias.
  2. Cuando no necesita todas las propiedades de la entidad consultada, o necesita subentidades de esa entidad, es mejor que el rendimiento use un tipo anónimo en su select. LINQ to SQL lo analizará y recuperará menos datos de su base de datos. Tal selección podría verse así: select new { a.VehicleId, a.Name }
  3. La consulta en el GetPartsAndOilCosts puede optimizarse poniendo el cálculo cost.totalCost * (1 + OVERHEAD_RATE) en la consulta LINQ. De esta forma, la consulta se puede ejecutar en la base de datos por completo, lo que debería hacerlo mucho más rápido.
  4. Usted está haciendo un Count() en la consulta var vehicles, pero solo lo usa para determinar el tamaño de la matriz. Mientras que LINQ to SQL hará una consulta muy eficiente de SELECT count(*), se necesita un viaje redondo adicional a la base de datos. Además de eso (dependiendo de su nivel de aislamiento), el tiempo que comience a repetir la consulta podría agregarse un elemento. En ese caso, su matriz es demasiado pequeña y se lanzará un ArrayIndexOutOfBoundsException. Simplemente puede usar .ToArray() en la consulta o crear un List<EquipmentTable> y llamar al .ToArray() en eso. Esto normalmente será lo suficientemente rápido, especialmente cuando solo tienes 380 elementos en esta colección y sin duda será más rápido que tener un viaje de ida y vuelta adicional a la base de datos (el recuento).
  5. Como probablemente ya esperas, la cantidad de consultas a la base de datos es el problema real. El cambio entre struct array o DataTable no será muy diferente.
  6. Después de optimizar tantas consultas como pueda, comience a analizar las consultas que quedan (utilizando el generador de perfiles SQL) y optimice estas consultas utilizando el asistente de ajuste del índice. Propondrá algunos índices nuevos para usted, que podrían acelerar considerablemente las cosas.

Una pequeña explicación adicional para el punto # 1. Lo que estamos haciendo aquí es un poco como esto:

lo que debe tratar de lograr es eliminar la sub consulta query2, ya que se está ejecutando en cada fila de la consulta superior. Por lo que podría terminar con algo como esto:

var query = 
    from x in A 
    from y in B 
    where x.Value == y.Value 
    select something; 

foreach (var row in query) 
{ 
} 

Por supuesto, este ejemplo es simplista y en la vida real se pone bastantes complicados (como ya se ha notado). En su caso también porque tiene muchas de esas 'consultas secundarias'. Puede llevarte algo de tiempo hacerlo bien, especialmente con tu falta de conocimiento de LINQ to SQL (como dijiste a ti mismo).

Si no puede resolverlo, siempre puede volver a preguntar aquí en Stackoverflow, pero recuerde quitar su problema al mínimo posible, porque no es divertido leer el desorden de alguien (no estamos obteniendo pagado por esto) :-) Buena suerte.

+0

@Steven: Lo siento, estoy confundido en el # 1. No estoy seguro de cómo hacerlo. Todos tus otros puntos que estoy investigando. –

+0

@Mike: actualicé mi respuesta. – Steven

+0

Gracias por la actualización. Lo miraré. –

Cuestiones relacionadas