C # SQL Server - passando uma lista para um procedimento armazenado


112

Estou chamando um procedimento armazenado do SQL Server do meu 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();

Meu proc armazenado é bastante padrão, mas faz uma junção com QueryTable(daí a necessidade de usar um proc armazenado).

Agora: desejo adicionar uma lista de strings,, List<string>ao conjunto de parâmetros. Por exemplo, minha consulta proc armazenada é assim:

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

No entanto, a lista de strings é dinâmica e com algumas centenas de comprimento.

Existe uma maneira de passar uma lista de strings List<string>para o procedimento armazenado ?? Ou há uma maneira melhor de fazer isto?

Muito obrigado, brett



Qual versão do SQL Server ?? 2005 ?? 2008 ?? R2 2008 ?? O SQL Server 2008 e mais recentes têm o conceito de "parâmetros com valor de tabela" (consulte a resposta de Redth para obter detalhes)
marc_s

Dica não relacionada: o SqlDataReader também é IDisposível, portanto, deve estar em um usingbloco.
Richardissimo

Respostas:


179

Se você estiver usando o SQL Server 2008, há um novo recurso chamado Tipo de Tabela Definida pelo Usuário. Aqui está um exemplo de como usá-lo:

Crie seu tipo de tabela definida pelo usuário:

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

Em seguida, você precisa usá-lo corretamente em seu procedimento armazenado:

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, aqui está algum sql para usar em 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 executar isso de 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

10
Tem que definir uma tabela de dados para definir o valor do parâmetro? Qualquer outra abordagem leve?
ca9163d9

2
Nós tentamos, mas encontramos a desvantagem de não ser compatível com o framework Enitiy
Bishoy Hanna

7
TENHA CUIDADO COM ESTA SOLUÇÃO SE VOCÊ ESTIVER USANDO LINQ2SQL, POIS ELE NÃO SUPORTA TIPOS DE TABELAS DEFINIDOS PELO USUÁRIO COMO PARÂMETROS !!! Uma solução alternativa pode ser encontrada na resposta de Jon Raynor, usando listas separadas por vírgulas e uma função analisadora, no entanto, isso também tem desvantagens ...
Fazi

3
@Fazi neste caso, não use Linq2SQL. É preferível a concatenação e análise de strings em T-SQL
Panagiotis Kanavos

2
Sim, então como você realmente executa isso a partir do SSMS?
Sinestésico

21

O padrão típico nesta situação é passar os elementos em uma lista delimitada por vírgulas e, em seguida, em SQL dividir isso em uma tabela que você possa usar. A maioria das pessoas geralmente cria uma função específica para fazer isso, como:

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

E então você pode usá-lo em outras consultas.


17
deixe-me lançar um link para uma implementação de dbo.SplitCommaString para integridade: goo.gl/P9ROs
Veli Gebrev

3
E o que acontece quando há uma vírgula em um de seus campos de dados?
Kevin Panko de

3
Em vez disso, delimite-o com tubos.
Alex em Paris

@AlexInParis E se houver um tubo em um de seus campos de dados?
RayLoveless,

2
Em seguida, use algo que não está em seus campos de dados. Limpe seus dados, se necessário, mas não muitos dados que eu já vi usando tubos. Se eles forem absolutamente necessários, encontre algum outro caractere como ¤ ou §.
Alex em Paris,

9

Não, arrays / listas não podem ser passados ​​diretamente para o SQL Server.

As seguintes opções estão disponíveis:

  1. Passar uma lista delimitada por vírgulas e depois ter uma função em SQL divide a lista. A lista delimitada por vírgulas provavelmente será passada como um Nvarchar ()
  2. Passe xml e tenha uma função no SQL Server para analisar o XML para cada valor na lista
  3. Use o novo tipo de tabela definido pelo usuário (SQL 2008)
  4. Crie dinamicamente o SQL e passe a lista bruta como "1,2,3,4" e crie a instrução SQL. Isso está sujeito a ataques de injeção de SQL, mas funcionará.

2

Sim, torne o parâmetro Stored proc como VARCHAR(...) E então passe os valores separados por vírgula para um procedimento armazenado.

Se você estiver usando o Sql Server 2008, você pode aproveitar TVP ( Parâmetros de valor de tabela ): SQL 2008 TVP e LINQ se a estrutura de QueryTable for mais complexa do que a matriz de strings, caso contrário, seria um exagero porque exige que o tipo de tabela seja criado no SQl Server


2

Faça uma tabela de dados com uma coluna em vez de List e adicione strings à tabela. Você pode passar esta tabela de dados como tipo estruturado e realizar outra junção com o campo de título de sua tabela.


esse é o caminho a percorrer. Na verdade, criei uma tabela do lado do banco de dados e carreguei o bcp write to server.
dier


0

A única maneira que conheço é criando uma lista CSV e depois passando-a como string. Então, no lado do SP, apenas divida e faça o que for necessário.


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

crie um TYPE como tabela e nomeie-o como "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

Crie um procedimento como acima e nomeie-o como "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();
                    }

no c #, tente isto

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.