124

Tengo una base de datos MS SQL Server 2005. En algunos procedimientos, tengo parámetros de tabla que paso a un proceso almacenado como un nvarchar (separados por comas) y los divido internamente en valores únicos. Me añadirlo a la lista de parámetros de comandos SQL como esto:Cómo pasar los parámetros de valor de la tabla al procedimiento almacenado desde el código .net

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo"; 

que tienen que migrar la base de datos a SQL Server 2008. Yo sé que hay parámetros de valor de tabla, y sé cómo usarlos en procedimientos almacenados. Pero no sé cómo pasar uno a la lista de parámetros en un comando SQL. ¿Alguien sabe la sintaxis correcta del procedimiento Parameters.Add? ¿O hay otra forma de pasar este parámetro?

+0

Echa un vistazo a esta solución: Procedimiento almacenado con parámetro con valores de tabla en EF. https://code.msdn.microsoft.com/Stored-Procedure-with-6c194514 –

+0

En un caso como este, normalmente concatenar cadenas y dividirlas en el servidor o pasar incluso un xml si tengo varias columnas. Sql es muy rápido cuando se procesa xml. Puede probar todos los métodos y verificar el tiempo de procesamiento y luego elegir el mejor método. Un XML se vería como .... El proceso en Sql Server también es simple. Con este método, siempre puede agregar un nuevo atributo al si necesita más información. –

Respuesta

209

DataTable, DbDataReader, o IEnumerable<SqlDataRecord> objetos se pueden utilizar para rellenar un parámetro de valor de tabla por el artículo de MSDN Table-Valued Parameters in SQL Server 2008 (ADO.NET).

El siguiente ejemplo ilustra utilizando un DataTable o un IEnumerable<SqlDataRecord>:

Código SQL:

CREATE TABLE dbo.PageView 
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED, 
    PageViewCount BIGINT NOT NULL 
); 
CREATE TYPE dbo.PageViewTableType AS TABLE 
(
    PageViewID BIGINT NOT NULL 
); 
CREATE PROCEDURE dbo.procMergePageView 
    @Display dbo.PageViewTableType READONLY 
AS 
BEGIN 
    MERGE INTO dbo.PageView AS T 
    USING @Display AS S 
    ON T.PageViewID = S.PageViewID 
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1 
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1); 
END 

C# Código:

private static void ExecuteProcedure(bool useDataTable, 
            string connectionString, 
            IEnumerable<long> ids) 
{ 
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    { 
     connection.Open(); 
     using (SqlCommand command = connection.CreateCommand()) 
     { 
      command.CommandText = "dbo.procMergePageView"; 
      command.CommandType = CommandType.StoredProcedure; 

      SqlParameter parameter; 
      if (useDataTable) { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateDataTable(ids)); 
      } 
      else 
      { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateSqlDataRecords(ids)); 
      } 
      parameter.SqlDbType = SqlDbType.Structured; 
      parameter.TypeName = "dbo.PageViewTableType"; 

      command.ExecuteNonQuery(); 
     } 
    } 
} 

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{ 
    DataTable table = new DataTable(); 
    table.Columns.Add("ID", typeof(long)); 
    foreach (long id in ids) 
    { 
     table.Rows.Add(id); 
    } 
    return table; 
} 

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{ 
    SqlMetaData[] metaData = new SqlMetaData[1]; 
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt); 
    SqlDataRecord record = new SqlDataRecord(metaData); 
    foreach (long id in ids) 
    { 
     record.SetInt64(0, id); 
     yield return record; 
    } 
} 
+14

+1 Excelente ejemplo. Los temas a seguir son: envíe un 'DataTable' como el valor del parámetro, establezca' SqlDbType' en 'Structured' y' TypeName' en el nombre UDT de la base de datos. –

+7

Si va a reutilizar una instancia de un tipo de referencia en un bucle (SqlDataRecord en su ejemplo), agregue un comentario sobre por qué es seguro hacerlo en esta instancia en particular. –

+2

Este código es incorrecto: los parámetros validados de la tabla vacía deberían tener su valor establecido en 'null'. 'CreateSqlDataRecords' nunca devolverá' null' si se le da un parámetro 'ids' vacío. –

25

con la respuesta de Ryan se quiere también necesita configurar el DataColumn 's Ordinal propiedad si se trata de table-valued parameter con columnas cuyos ordinales son no en orden alfabético.

A modo de ejemplo, si usted tiene el siguiente valor de la tabla que se utiliza como parámetro en SQL:

CREATE TYPE NodeFilter AS TABLE (
    ID int not null 
    Code nvarchar(10) not null, 
); 

Usted tendría que pedir sus columnas como por ejemplo en C#:

table.Columns["ID"].SetOrdinal(0); 
// this also bumps Code to ordinal of 1 
// if you have more than 2 cols then you would need to set more ordinals 

Si no lo haces, obtendrás un error de análisis, no se pudo convertir nvarchar a int.

13

Genérico

public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector) 
    { 
     var tbl = new DataTable(); 
     tbl.Columns.Add("Id", typeof(T)); 

     foreach (var item in list) 
     { 
      tbl.Rows.Add(selector.Invoke(item)); 

     } 

     return tbl; 

    } 
+0

¿Podría decirme qué paso como parámetro? Func selector? No puede ser simplemente tbl.Rows.Add (elemento) y no es necesario ese parámetro. – GDroid

+0

el selector.Invocar (elemento) selecciona la propiedad en el elemento la mayoría de los casos es un int, pero también le permite seleccionar una propiedad de cadena – Martea

+0

¿puede dar un ejemplo de cómo pongo selector allí? Tengo una lista para pasar al proceso almacenado ... – GDroid

5

La forma más limpia de trabajar con él. Asumiendo que su mesa está una lista de números enteros llamados "dbo.tvp_Int" (personalizar para su propio tipo de mesa)

crear este método de extensión ...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data) 
{ 
    if(paramCollection != null) 
    { 
     var p = paramCollection.Add(parameterName, SqlDbType.Structured); 
     p.TypeName = "dbo.tvp_Int"; 
     DataTable _dt = new DataTable() {Columns = {"Value"}}; 
     data.ForEach(value => _dt.Rows.Add(value)); 
     p.Value = _dt; 
    } 
} 

ya se puede añadir un parámetro valioso en una mesa línea en cualquier lugar simplemente haciendo esto:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds); 
+1

¿y si el paramCollection es NULL? ¿Cómo pasar el tipo vacío? – Muflix

+2

@Muflix Oscuro, los métodos de extensión realmente funcionan contra instancias nulas. Así que agregar una simple comprobación 'if (paramCollection! = Null)' en la parte superior del método estará bien – Rhumborl

+1

Respuesta actualizada con la inicial -if-check –

Cuestiones relacionadas