C # SQL Server - Passer une liste à une procédure stockée

112

J'appelle une procédure stockée SQL Server à partir de mon code 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();

Mon proc stocké est assez standard mais fait une jointure avec QueryTable(d'où la nécessité d'utiliser un proc stocké).

Maintenant: je veux ajouter une liste de chaînes List<string>,, au jeu de paramètres. Par exemple, ma requête proc stockée ressemble à ceci:

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

Cependant, la liste des chaînes est dynamique et longue de quelques centaines.

Existe-t-il un moyen de passer une liste de chaînes List<string>au proc stocké ??? Ou y a-t-il une meilleure façon de faire cela?

Merci beaucoup, Brett

Brett
la source
Éventuellement, duplicata de stackoverflow.com/questions/209686/…
Andrey Agibalov
Quelle version de SQL Server ?? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 et plus récent a le concept de "paramètres table" (voir la réponse de Redth pour plus de détails)
marc_s
Astuce sans rapport: le SqlDataReader est également IDisposable et doit donc être dans un usingbloc.
Richardissimo

Réponses:

179

Si vous utilisez SQL Server 2008, il existe une nouvelle fonctionnalité appelée un type de table défini par l'utilisateur. Voici un exemple de son utilisation:

Créez votre type de table défini par l'utilisateur:

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

Ensuite, vous devez l'utiliser correctement dans votre procédure stockée:

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

Enfin, voici quelques sql pour l'utiliser 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());
          }
         }
    }
}

Pour l'exécuter à partir 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
Redth
la source
10
Doit-il définir un datatable pour définir la valeur du paramètre? Une autre approche légère?
ca9163d9
2
Nous l'avons essayé mais nous avons trouvé que son inconvénient n'était pas pris en charge par le framework Enitiy
Bishoy Hanna
7
SOYEZ PRUDENT AVEC CETTE SOLUTION SI VOUS ÊTES UTILISATEUR LINQ2SQL, CAR ELLE NE SUPPORTE PAS LES TYPES DE TABLE DÉFINIS PAR L'UTILISATEUR EN TANT QUE PARAMÈTRES !!! Une solution de contournement peut être trouvée dans la réponse de Jon Raynor, en utilisant des listes séparées par des virgules et une fonction d'analyseur, mais cela présente également des inconvénients ....
Fazi
3
@Fazi dans ce cas, n'utilisez pas Linq2SQL. Il est préférable de concaténer et d'analyser les chaînes en T-SQL
Panagiotis Kanavos
2
Ouais, alors comment exécutez-vous cela à partir de SSMS?
Sinaesthetic
21

Le modèle typique dans cette situation est de transmettre les éléments dans une liste délimitée par des virgules, puis en SQL de les diviser en une table que vous pouvez utiliser. La plupart des gens créent généralement une fonction spécifiée pour faire cela comme:

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

Et puis vous pouvez l'utiliser dans d'autres requêtes.

Tejs
la source
17
laissez-moi ajouter un lien pour une implémentation de dbo.SplitCommaString pour être complet: goo.gl/P9ROs
Veli Gebrev
3
Et que se passe-t-il lorsqu'il y a une virgule dans l'un de vos champs de données?
Kevin Panko
3
Délimitez-le avec des tuyaux à la place.
Alex In Paris
@AlexInParis Que faire s'il y a un tuyau dans l'un de vos champs de données?
RayLoveless
2
Ensuite, utilisez quelque chose qui ne figure pas dans vos champs de données. Nettoyez vos données, si nécessaire, mais peu de données que j'ai jamais vues ont déjà utilisé des tuyaux. S'ils sont absolument nécessaires, trouvez un autre caractère comme ¤ ou §.
Alex In Paris
9

Non, les tableaux / listes ne peuvent pas être transmis directement à SQL Server.

Les options suivantes sont disponibles:

  1. Passer une liste délimitée par des virgules, puis avoir une fonction en SQL, diviser la liste. La liste délimitée par des virgules sera très probablement passée sous la forme d'un Nvarchar ()
  2. Transmettez xml et demandez à une fonction dans SQL Server d'analyser le XML pour chaque valeur de la liste
  3. Utiliser le nouveau type de table défini par l'utilisateur (SQL 2008)
  4. Construisez dynamiquement le SQL et transmettez la liste brute en tant que "1,2,3,4" et créez l'instruction SQL. Ceci est sujet aux attaques par injection SQL, mais cela fonctionnera.
Jon Raynor
la source
2

Oui, définissez le paramètre proc stocké sur VARCHAR(...) Et puis passez les valeurs séparées par des virgules à une procédure stockée.

Si vous utilisez Sql Server 2008, vous pouvez tirer parti de TVP ( paramètres de valeur de table ): SQL 2008 TVP et LINQ si la structure de QueryTable est plus complexe que le tableau de chaînes, sinon ce serait excessif car le type de table doit être créé dans SQl Server

sll
la source
2

Créez un datatable avec une colonne au lieu de List et ajoutez des chaînes à la table. Vous pouvez passer ce datatable en tant que type structuré et effectuer une autre jointure avec le champ title de votre table.

faim
la source
c'est la voie à suivre. J'ai en fait créé une table du côté de la base de données et chargée d'écrire bcp sur le serveur.
décédé
0

Le seul moyen dont je suis conscient est de créer une liste CSV, puis de la passer sous forme de chaîne. Ensuite, du côté SP, divisez-le et faites tout ce dont vous avez besoin.

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

créer un TYPE comme table et le nommer "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

Créez une procédure comme ci-dessus et nommez-la comme "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();
                    }

dans le c #, essayez ceci

Deepalakshmi
la source