C # SQL Server: pasar una lista a un procedimiento almacenado

111

Estoy llamando a un procedimiento almacenado de SQL Server desde mi código C #:

using (SqlConnection conn = new SqlConnection(connstring))
{
   conn.Open();
   using (SqlCommand cmd = new SqlCommand("InsertQuerySPROC", conn))
   {
      cmd.CommandType = CommandType.StoredProcedure;

      var STableParameter = cmd.Parameters.AddWithValue("@QueryTable", QueryTable);
      var NDistanceParameter = cmd.Parameters.AddWithValue("@NDistanceThreshold", NDistanceThreshold);
      var RDistanceParameter = cmd.Parameters.AddWithValue(@"RDistanceThreshold", RDistanceThreshold);

      STableParameter .SqlDbType = SqlDbType.Structured;
      NDistanceParameter.SqlDbType = SqlDbType.Int;
      RDistanceParameter.SqlDbType = SqlDbType.Int;

      // Execute the query
      SqlDataReader QueryReader = cmd.ExecuteReader();

Mi proceso almacenado es bastante estándar pero se une con QueryTable(de ahí la necesidad de usar un proceso almacenado).

Ahora: quiero agregar una lista de cadenas List<string>, al conjunto de parámetros. Por ejemplo, mi consulta proc almacenada es así:

SELECT feature 
FROM table1 t1 
INNER JOIN @QueryTable t2 ON t1.fid = t2.fid 
WHERE title IN <LIST_OF_STRINGS_GOES_HERE>

Sin embargo, la lista de cadenas es dinámica y tiene unos cientos de longitud.

¿Hay alguna forma de pasar una lista de cadenas List<string>al proceso almacenado? ¿O hay una mejor manera de hacer esto?

Muchas gracias, Brett

Brett
fuente
Posiblemente, duplicado de stackoverflow.com/questions/209686/…
Andrey Agibalov
¿Qué versión de SQL Server? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 y versiones posteriores tienen el concepto de "parámetros con valores de tabla" (consulte la respuesta de Redth para obtener más detalles)
marc_s
Consejo no relacionado: el SqlDataReader también es IDisposable, por lo que debería estar en un usingbloque.
Richardissimo

Respuestas:

178

Si está utilizando SQL Server 2008, hay una nueva función llamada Tipo de tabla definida por el usuario. A continuación, se muestra un ejemplo de cómo utilizarlo:

Cree su tipo de tabla definido por el usuario:

CREATE TYPE [dbo].[StringList] AS TABLE(
    [Item] [NVARCHAR](MAX) NULL
);

A continuación, debe usarlo correctamente en su procedimiento almacenado:

CREATE PROCEDURE [dbo].[sp_UseStringList]
    @list StringList READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.Item FROM @list l;
END

Finalmente, aquí hay algo de sql para usarlo en c #:

using (var con = new SqlConnection(connstring))
{
    con.Open();

    using (SqlCommand cmd = new SqlCommand("exec sp_UseStringList @list", con))
    {
        using (var table = new DataTable()) {
          table.Columns.Add("Item", typeof(string));

          for (int i = 0; i < 10; i++)
            table.Rows.Add("Item " + i.ToString());

          var pList = new SqlParameter("@list", SqlDbType.Structured);
          pList.TypeName = "dbo.StringList";
          pList.Value = table;

          cmd.Parameters.Add(pList);

          using (var dr = cmd.ExecuteReader())
          {
            while (dr.Read())
                Console.WriteLine(dr["Item"].ToString());
          }
         }
    }
}

Para ejecutar esto desde SSMS

DECLARE @list AS StringList

INSERT INTO @list VALUES ('Apple')
INSERT INTO @list VALUES ('Banana')
INSERT INTO @list VALUES ('Orange')

-- Alternatively, you can populate @list with an INSERT-SELECT
INSERT INTO @list
   SELECT Name FROM Fruits

EXEC sp_UseStringList @list
Redth
fuente
10
¿Tiene que definir una tabla de datos para establecer el valor del parámetro? ¿Algún otro enfoque ligero?
ca9163d9
2
Lo probamos pero descubrimos que su inconveniente no es compatible con el marco de Enitiy
Bishoy Hanna
7
¡¡¡TENGA CUIDADO CON ESTA SOLUCIÓN SI USTED ESTÁ UTILIZANDO EN LINQ2SQL, YA QUE NO SOPORTA LOS TIPOS DE TABLA DEFINIDOS POR EL USUARIO COMO PARÁMETROS !!! Se puede encontrar una solución en la respuesta de Jon Raynor, usando listas separadas por comas y una función de analizador, sin embargo, esto también tiene inconvenientes ...
Fazi
3
@Fazi en este caso, no use Linq2SQL. Es preferible la concatenación de cadenas y el análisis en T-SQL
Panagiotis Kanavos
2
Sí, entonces, ¿cómo se ejecuta realmente esto desde SSMS?
Sinaesthetic
21

El patrón típico en esta situación es pasar los elementos en una lista delimitada por comas, y luego en SQL dividirlos en una tabla que puede usar. La mayoría de las personas suelen crear una función específica para hacer esto como:

 INSERT INTO <SomeTempTable>
 SELECT item FROM dbo.SplitCommaString(@myParameter)

Y luego puedes usarlo en otras consultas.

Tejs
fuente
17
permítanme incluir un enlace para una implementación dbo.SplitCommaString para completar: goo.gl/P9ROs
Veli Gebrev
3
¿Y qué sucede cuando hay una coma en uno de sus campos de datos?
Kevin Panko
3
En su lugar, delimítelo con tuberías.
Alex en París
@AlexInParis ¿Qué pasa si hay una tubería en uno de sus campos de datos?
RayLoveless
2
Luego use algo que no esté en sus campos de datos. Limpie sus datos, si es necesario, pero no muchos de los datos que he visto han usado tuberías. Si son absolutamente necesarios, busque algún otro carácter como ¤ o §.
Alex en París
9

No, las matrices / listas no se pueden pasar directamente a SQL Server.

Las siguientes opciones están disponibles:

  1. Pasar una lista delimitada por comas y luego tener una función en SQL dividir la lista. La lista delimitada por comas probablemente se pasará como un Nvarchar ()
  2. Pase xml y tenga una función en SQL Server para analizar el XML para cada valor en la lista
  3. Utilice el nuevo tipo de tabla definida por el usuario (SQL 2008)
  4. Construya dinámicamente el SQL y pase la lista sin formato como "1,2,3,4" y cree la declaración SQL. Esto es propenso a ataques de inyección SQL, pero funcionará.
Jon Raynor
fuente
2

Sí, establezca el parámetro Stored proc como VARCHAR(...) Y luego pase valores separados por comas a un procedimiento almacenado.

Si está utilizando Sql Server 2008, puede aprovechar TVP ( Parámetros de valor de tabla ): SQL 2008 TVP y LINQ si la estructura de QueryTable es más compleja que la matriz de cadenas, de lo contrario sería una exageración porque requiere que se cree el tipo de tabla dentro de SQl Server

sll
fuente
2

Cree una tabla de datos con una columna en lugar de List y agregue cadenas a la tabla. Puede pasar esta tabla de datos como tipo estructurado y realizar otra combinación con el campo de título de su tabla.

hambriento
fuente
ese es el camino a seguir. De hecho, creé una tabla en el lado de la base de datos y la cargué con bcp write en el servidor.
dier
0

La única forma que conozco es crear una lista CSV y luego pasarla como cadena. Luego, en el lado SP, simplemente divídalo y haga lo que necesite.

Andrey Agibalov
fuente
0
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);

cree un TIPO como tabla y asígnele el nombre "StringList1"

create PROCEDURE [dbo].[sp_UseStringList1]
@list StringList1 READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.item,l.counts FROM @list l;
    SELECT l.item,l.counts into tempTable FROM @list l;
 End

Cree un procedimiento como el anterior y asígnele el nombre "UserStringList1" s

String strConnection = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString.ToString();
            SqlConnection con = new SqlConnection(strConnection);
            con.Open();
            var table = new DataTable();

            table.Columns.Add("Item", typeof(string));
            table.Columns.Add("count", typeof(string));

            for (int i = 0; i < 10; i++)
            {
                table.Rows.Add(i.ToString(), (i+i).ToString());

            }
                SqlCommand cmd = new SqlCommand("exec sp_UseStringList1 @list", con);


                    var pList = new SqlParameter("@list", SqlDbType.Structured);
                    pList.TypeName = "dbo.StringList1";
                    pList.Value = table;

                    cmd.Parameters.Add(pList);
                    string result = string.Empty;
                    string counts = string.Empty;
                    var dr = cmd.ExecuteReader();

                    while (dr.Read())
                    {
                        result += dr["Item"].ToString();
                        counts += dr["counts"].ToString();
                    }

en c #, prueba esto

Deepalakshmi
fuente