2008-10-16 39 views
50

A menudo he tenido que cargar varios elementos a un registro particular en la base de datos. Por ejemplo: una página web muestra elementos para incluir en un único informe, todos los cuales son registros en la base de datos (el informe es un registro en la tabla Informe, los elementos son registros en la tabla Artículo). Un usuario selecciona elementos para incluir en un solo informe a través de una aplicación web, y digamos que seleccionan 3 elementos y los envían. El proceso agregará estos 3 elementos a este informe agregando registros a una tabla llamada ReportItems (ReportId, ItemId).Pasando List <> a SQL Stored Procedure

Actualmente, me gustaría hacer algo como esto en el código:

public void AddItemsToReport(string connStr, int Id, List<int> itemList) 
{ 
    Database db = DatabaseFactory.CreateDatabase(connStr); 

    string sqlCommand = "AddItemsToReport" 
    DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand); 

    string items = ""; 
    foreach (int i in itemList) 
     items += string.Format("{0}~", i); 

    if (items.Length > 0) 
     items = items.Substring(0, items.Length - 1); 

    // Add parameters 
    db.AddInParameter(dbCommand, "ReportId", DbType.Int32, Id); 
    db.AddInParameter(dbCommand, "Items", DbType.String, perms); 
    db.ExecuteNonQuery(dbCommand); 
} 

y esto en el procedimiento almacenado:

INSERT INTO ReportItem (ReportId,ItemId) 
SELECT @ReportId, 
      Id 
FROM  fn_GetIntTableFromList(@Items,'~') 

Cuando la función devuelve una tabla de una columna de números enteros.

Mi pregunta es: ¿hay una mejor manera de manejar algo como esto? Tenga en cuenta que no estoy preguntando sobre la normalización de la base de datos ni nada de eso, mi pregunta se relaciona específicamente con el código.

Respuesta

19

Su cadena de unirse a la lógica, probablemente, se puede simplificar:

string items = 
    string.Join("~", itemList.Select(item=>item.ToString()).ToArray()); 

que va a ahorrar algo de concatenación de cadenas, que es costoso en .Net.

No creo que haya ningún problema con la forma en que está guardando los artículos. Está limitando los viajes a la base de datos, lo cual es algo bueno. Si su estructura de datos fuera más compleja que una lista de ints, sugeriría XML.

Nota: Me preguntaron en los comentarios si esto nos ahorraría cualquier concatenación de cadenas (lo hace indeeed). Creo que es una excelente pregunta y me gustaría seguir con eso.

Si extrae una cadena abierta.Únete con Reflector y verás que Microsoft está usando un par de técnicas inseguras (en el sentido de .Net), que incluyen el uso de un puntero y una estructura llamada UnSafeCharBuffer. Lo que están haciendo, cuando realmente lo reducen, es usar punteros para caminar sobre una cuerda vacía y construir la unión. Recuerde que la razón principal por la cual la concatenación de cadenas es tan costosa en .Net es que un nuevo objeto de cadena se coloca en el montón para cada concatenación, porque la cadena es inmutable. Esas operaciones de memoria son costosas. String.Join (..) es esencialmente asignar la memoria una vez, luego operar sobre ella con un puntero. Muy rapido.

+0

¿Es realmente va a salvar de la concatenación? ¿Sabes si el método se implementó usando StringBuilder? –

+0

Sí, lo salvará de la concatenación. String.Join usa un UnsafeCharBuffer para hacer el concat (mirarlo en Reflector). En lo que respecta al LINQ, eso es solo enumerar a través de la lista llamando a ToString(), que es inevitable, y crear una matriz. La creación de matriz puede ser costosa. –

+0

No sabía que pudieras hacerlo así, ¡gracias por el consejo! –

8

Un problema potencial con su técnica es que no maneja listas muy grandes: puede exceder la longitud máxima de cadena para su base de datos.Utilizo un método de ayuda que concatena los valores enteros en una enumeración de secuencias, cada una de las cuales es menor que un máximo especificado (la siguiente implementación también comprueba opcionalmente para y elimina duplicados ids):

public static IEnumerable<string> ConcatenateValues(IEnumerable<int> values, string separator, int maxLength, bool skipDuplicates) 
{ 
    IDictionary<int, string> valueDictionary = null; 
    StringBuilder sb = new StringBuilder(); 
    if (skipDuplicates) 
    { 
     valueDictionary = new Dictionary<int, string>(); 
    } 
    foreach (int value in values) 
    { 
     if (skipDuplicates) 
     { 
      if (valueDictionary.ContainsKey(value)) continue; 
      valueDictionary.Add(value, ""); 
     } 
     string s = value.ToString(CultureInfo.InvariantCulture); 
     if ((sb.Length + separator.Length + s.Length) > maxLength) 
     { 
      // Max length reached, yield the result and start again 
      if (sb.Length > 0) yield return sb.ToString(); 
      sb.Length = 0; 
     } 
     if (sb.Length > 0) sb.Append(separator); 
     sb.Append(s); 
    } 
    // Yield whatever's left over 
    if (sb.Length > 0) yield return sb.ToString(); 
} 

Entonces que lo utilice algo así como:

using(SqlCommand command = ...) 
{ 
    command.Connection = ...; 
    command.Transaction = ...; // if in a transaction 
    SqlParameter parameter = command.Parameters.Add("@Items", ...); 
    foreach(string itemList in ConcatenateValues(values, "~", 8000, false)) 
    { 
     parameter.Value = itemList; 
     command.ExecuteNonQuery(); 
    } 
} 
+1

Buen punto, no había pensado en exceder las longitudes de las cuerdas. Aunque dudo que tenga casos que incluso se acerquen, es bueno aprender esto, ¡gracias! –

34

Si va a SQL Server 2008 es una opción para usted, hay una nueva característica llamada "valores de tabla de parámetros" para resolver este problema exacto

Consulte más detalles en TVP here y here o simplemente solicite a Google los "parámetros de tabla de SQL Server 2008": encontrará mucha información y muestras.

Muy recomendable - si se puede mover a SQL Server 2008 ...

5

por qué no usar un parámetro con valores de tabla? http://msdn.microsoft.com/en-us/library/bb675163.aspx

+0

Porque requiere Tipo de tabla definida por el usuario, que puede permanecer en db permanentemente a menos que garantice que elimina este tipo después de cada invocación. – alpav

+0

¿Eso es un problema? – GaTechThomas

+0

Son un poco difíciles de administrar, si tiene que guiar los cambios a una implementación en vivo, por ejemplo. – philw