web-dev-qa-db-fra.com

Insertion de plusieurs enregistrements dans la base de données SQL Server à l'aide de la boucle for

Je travaille sur un projet de formulaire Windows en C #. J'essaie d'insérer plusieurs enregistrements dans la base de données SQL Server à partir d'un tableau. 

Après avoir entré la première ligne, je reçois une exception 

@UserID a déjà été déclaré. Les noms de variables doivent être uniques dans un lot de requêtes ou une procédure stockée.

Il n'y a pas de problème avec la clé primaire dans la base de données, car UserID n'est pas la clé primaire.

C'est ce que j'essaie de faire.

public static void featuresentry()
{
    SqlConnection connection = new SqlConnection(HandVeinPattern.Properties.Settings.Default.HandVeinPatternConnectionString);

    SqlCommand command = new SqlCommand();
    connection.Open();

    try
    {
        command = connection.CreateCommand();

        for (int i = 0; i < Details.modelKeyPoints.Size; i++)
        {
            command.CommandText = "INSERT INTO FEATURES(UserID, Angle, ClassID, Octave, PointX, PointY, Response, Size) VALUES(@UserID, @Angle, @ClassID, @Octave, @PointX, @PointY, @Response, @Size)";

            command.Parameters.AddWithValue("@UserID", Details.ID);
            command.Parameters.AddWithValue("@Angle", Convert.ToDouble(Details.modelKeyPoints[i].Angle));
            command.Parameters.AddWithValue("@ClassID", Convert.ToDouble(Details.modelKeyPoints[i].ClassId));
            command.Parameters.AddWithValue("@Octave", Convert.ToDouble(Details.modelKeyPoints[i].Octave));
            command.Parameters.AddWithValue("@PointX", Convert.ToDouble(Details.modelKeyPoints[i].Point.X));
            command.Parameters.AddWithValue("@PointY", Convert.ToDouble(Details.modelKeyPoints[i].Point.Y));
            command.Parameters.AddWithValue("@Response", Convert.ToDouble(Details.modelKeyPoints[i].Response));
            command.Parameters.AddWithValue("@Size", Convert.ToDouble(Details.modelKeyPoints[i].Size));

            command.ExecuteNonQuery();
        }
    }
    catch (Exception)
    {
        throw;
    }
    finally
    {
        if (connection.State == ConnectionState.Open)
        {
            connection.Close();
        }
    }
}
5
Junaid Sultan

Vous devriez faire ceci correctement :

  • définir vos paramètres une fois en dehors de la boucle
  • définir les valeurs de vos paramètres à l'intérieur de la boucle pour chaque itération
  • utilisez les blocs using(...) { ... } pour vous débarrasser du try ... catch ... finally (le bloc using garantira une élimination appropriée et rapide de vos cours, lorsqu'ils ne sont plus nécessaires)
  • arrêtez d'utiliser un try...catch si vous n'êtes pas réellement manipulant les exceptions - les renvoyer simplement (cela n'a aucun sens)

Essayez ce code:

public static void featuresentry()
{
    string connectionString = HandVeinPattern.Properties.Settings.Default.HandVeinPatternConnectionString;
    string insertQuery = "INSERT INTO FEATURES(UserID, Angle, ClassID, Octave, PointX, PointY, Response, Size) VALUES(@UserID, @Angle, @ClassID, @Octave, @PointX, @PointY, @Response, @Size)";

    using (SqlConnection connection = new SqlConnection(connectionString))
    using (SqlCommand command = new SqlCommand(insertQuery, connection))
    {
        // define your parameters ONCE outside the loop, and use EXPLICIT typing
        command.Parameters.Add("@UserID", SqlDbType.Int);
        command.Parameters.Add("@Angle", SqlDbType.Double);
        command.Parameters.Add("@ClassID", SqlDbType.Double);
        command.Parameters.Add("@Octave", SqlDbType.Double);
        command.Parameters.Add("@PointX", SqlDbType.Double);
        command.Parameters.Add("@PointY", SqlDbType.Double);
        command.Parameters.Add("@Response", SqlDbType.Double);
        command.Parameters.Add("@Size", SqlDbType.Double);

        connection.Open();

        for (int i = 0; i < Details.modelKeyPoints.Size; i++)
        {
            // now just SET the values
            command.Parameters["@UserID"].Value = Details.ID;
            command.Parameters["@Angle"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Angle);
            command.Parameters["@ClassID"].Value = Convert.ToDouble(Details.modelKeyPoints[i].ClassId);
            command.Parameters["@Octave"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Octave);
            command.Parameters["@PointX"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Point.X);
            command.Parameters["@PointY"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Point.Y);
            command.Parameters["@Response"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Response);
            command.Parameters["@Size"].Value = Convert.ToDouble(Details.modelKeyPoints[i].Size);

            command.ExecuteNonQuery();
        }
    }
}
6
marc_s

Si vous mettez command = connection.CreateCommand(); dans votre boucle for, cela fonctionnera. Le problème est que vous passez en boucle sur les paramètres de commande uniquement. Vous essayez donc d'ajouter d'autres paramètres à votre commande existante, mais ils sont déjà présents. Vous devez donc créer une nouvelle commande à chaque boucle.

2
Unicorno Marley

Afin d'obtenir les performances maximales, vous pouvez envisager un BulkInsert . Cela garantit que votre insertion est effectuée aussi rapidement que possible, car toute requête émise a un temps système (une requête volumineuse s'exécutera généralement plus rapidement que beaucoup de petites requêtes). Cela devrait ressembler à ceci:

1) définir la méthode d’extension AsDataTable à partir de ici :

   public static DataTable AsDataTable<T>(this IEnumerable<T> data)
   {
       PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
       var table = new DataTable();
       foreach (PropertyDescriptor prop in properties)
           table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
       foreach (T item in data)
       {
           DataRow row = table.NewRow();
           foreach (PropertyDescriptor prop in properties)
               row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
           table.Rows.Add(row);
       }
       return table;
   }

2) exécutez le BulkInsert réel comme ceci ( non testé ):

using (SqlConnection connection = new SqlConnection(connectionString))
{
     connection.Open();
     SqlTransaction transaction = connection.BeginTransaction();

     using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
     {
        bulkCopy.BatchSize = 100;
        bulkCopy.DestinationTableName = "dbo.FEATURES";
        try
        {
            // define mappings for columns, as property names / generated data table column names
            // is different from destination table column name
            bulkCopy.ColumnMappings.Add("ID","UserID");
            bulkCopy.ColumnMappings.Add("Angle","Angle");
            // the other mappings come here

            bulkCopy.WriteToServer(Details.modelKeyPoints.AsDataTable());
        }
        catch (Exception)
        {
            transaction.Rollback();
            connection.Close();
        }
      }

      transaction.Commit();
}

Bien sûr, si la convention sur la configuration était utilisée (les noms des propriétés de l'objet correspondraient exactement aux noms des colonnes de la table de destination), aucun mappage ne serait requis.

0
Alexei

Vous pouvez le faire en envoyant vos données sous forme de chaîne XML et les convertir en table dans une procédure stockée dans SQL. Par exemple: Supposons que j'envoie plusieurs lignes à ajouter/mettre à jour dans une table SQL, voici les étapes à suivre:

  1. Convertissez votre classe ou liste de classe en une chaîne xml en utilisant la méthode suivante:

    public static string SerializeObjectToXmlString(object value)
    
              {
              var emptyNamepsaces = new XmlSerializerNamespaces(new[] { 
                                        XmlQualifiedName.Empty });
    
        var serializer = new XmlSerializer(value.GetType());
        var settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.OmitXmlDeclaration = true;
    
        using (var stream = new StringWriter())
        using (var writer = XmlWriter.Create(stream, settings))
        {
            serializer.Serialize(writer, value, emptyNamepsaces);
            return stream.ToString();
        }
    }
    
  2. Maintenant, lors de l'envoi de données à la base de données, convertissez votre objet de classe en chaîne xml (Ici, j'utilise un cadre d'entité dans mon code, vous pouvez le faire sans l'utiliser également):

    bool AddUpdateData(List<MyClass> data)
    {
        bool returnResult = false;
        string datatXml = Helper.SerializeObjectToXmlString(data);
        var sqlparam = new List<SqlParameter>()
                     {
       new SqlParameter() { ParameterName = "dataXml", Value = datatXml}
    
                     };
        var result = this.myEntity.Repository<SQL_StoredProc_ComplexType>().ExecuteStoredProc("SQL_StoredProc", sqlparam);
        if (result != null && result.Count() > 0)
        {
            returnResult = result[0].Status == 1 ? true : false;
        }
        return returnResult;
    }
    
  3. Maintenant votre code SQL:

3.1 Déclarer une variable de table:

DECLARE @tableVariableName TABLE
(
    ID INT, Name VARCHAR(20)
)

3.2 Insérer votre chaîne XML dans la variable Table

INSERT INTO @tableVariableName
SELECT 
    Finaldata.R.value ('(ID/text())[1]', 'INT') AS ID, 
    Finaldata.R.value ('(Name/text())[1]', 'VARCHAR(20)') AS Name
FROM @MyInputXmlString.nodes ('//ArrayMyClass/MyClass') AS Finaldata (R)

3.3 Enfin, insérez cette valeur dans votre table SQL

INSERT INTO MyTable (ID, Name)                  
SELECT ID, Name          
FROM @tableVariableName

Cela vous évitera de frapper la base de données encore et encore en utilisant une boucle for.

J'espère que cela vous aidera

0
Pranav Mishra

Vous devez ajouter des paramètres de commande en dehors de la boucle ou déclarer une commande à l'intérieur de la boucle. 

Dans le premier cas, vous devrez mettre à jour la valeur de chaque paramètre comme suit:

oleDbCommand1.Parameters["@UserID"].Value = Details.ID;

Et exécutez la commande une fois les nouvelles valeurs définies.

0
Bulat