web-dev-qa-db-fra.com

Comment devrais-je insérer plusieurs enregistrements multiples?

J'ai une classe nommée Entry déclarée comme ceci: 

class Entry{
    string Id {get;set;}
    string Name {get;set;}
}  

et ensuite une méthode qui acceptera plusieurs de ces objets Entry à insérer dans la base de données à l'aide de ADO.NET: 

static void InsertEntries(IEnumerable<Entry> entries){
    //build a SqlCommand object
    using(SqlCommand cmd = new SqlCommand()){
        ...
        const string refcmdText = "INSERT INTO Entries (id, name) VALUES (@id{0},@name{0});";
        int count = 0;
        string query = string.Empty;
        //build a large query
        foreach(var entry in entries){
            query += string.Format(refcmdText, count);
            cmd.Parameters.AddWithValue(string.Format("@id{0}",count), entry.Id);
            cmd.Parameters.AddWithValue(string.Format("@name{0}",count), entry.Name);
            count++;
        }
        cmd.CommandText=query;
        //and then execute the command
        ...
    }
}  

Et ma question est la suivante: devrais-je continuer à utiliser la méthode ci-dessus pour envoyer plusieurs instructions d'insertion (construire une chaîne géante d'instructions d'insertion et leurs paramètres et l'envoyer sur le réseau) ou dois-je conserver une connexion ouverte et envoyer une seule instruction d'insertion? pour chaque Entry comme ceci: 

using(SqlCommand cmd = new SqlCommand(){
    using(SqlConnection conn = new SqlConnection(){
        //assign connection string and open connection
        ...
        cmd.Connection = conn;
        foreach(var entry in entries){
            cmd.CommandText= "INSERT INTO Entries (id, name) VALUES (@id,@name);";
            cmd.Parameters.AddWithValue("@id", entry.Id);
            cmd.Parameters.AddWithValue("@name", entry.Name);
            cmd.ExecuteNonQuery();
        }
    }
 }  

Qu'est-ce que tu penses? Y aura-t-il une différence de performance dans le serveur SQL entre les deux? Y a-t-il d'autres conséquences dont je devrais être au courant?

25
bottlenecked

Si j'étais vous, je ne les utiliserais pas.

L'inconvénient du premier est que les noms de paramètres peuvent entrer en collision si la liste contient les mêmes valeurs.

L’inconvénient du second problème est que vous créez des commandes et des paramètres pour chaque entité.

Le meilleur moyen est de faire en sorte que le texte et les paramètres de la commande soient construits une fois (utilisez Parameters.Add pour ajouter les paramètres), modifiez leurs valeurs dans la boucle et exécutez la commande. Ainsi, la déclaration ne sera préparée qu’une fois. Vous devez également ouvrir la connexion avant de démarrer la boucle et la fermer après celle-ci.

23
Giorgi
static void InsertSettings(IEnumerable<Entry> settings) {
    using (SqlConnection oConnection = new SqlConnection("Data Source=(local);Initial Catalog=Wip;Integrated Security=True")) {
        oConnection.Open();
        using (SqlTransaction oTransaction = oConnection.BeginTransaction()) {
            using (SqlCommand oCommand = oConnection.CreateCommand()) {
                oCommand.Transaction = oTransaction;
                oCommand.CommandType = CommandType.Text;
                oCommand.CommandText = "INSERT INTO [Setting] ([Key], [Value]) VALUES (@key, @value);";
                oCommand.Parameters.Add(new SqlParameter("@key", SqlDbType.NChar));
                oCommand.Parameters.Add(new SqlParameter("@value", SqlDbType.NChar));
                try {
                    foreach (var oSetting in settings) {
                        oCommand.Parameters["@key"].Value = oSetting.Key;
                        oCommand.Parameters["@value"].Value = oSetting.Value;
                        if (oCommand.ExecuteNonQuery() != 1) {
                            //'handled as needed, 
                            //' but this snippet will throw an exception to force a rollback
                            throw new InvalidProgramException();
                        }
                    }
                    oTransaction.Commit();
                } catch (Exception) {
                    oTransaction.Rollback();
                    oConnection.Close();
                    throw;
                }
            }
        }
    }
}
46
AMissico

Vous devez exécuter la commande sur chaque boucle au lieu de créer une énorme commande Text (btw, StringBuilder est créé pour cela) La connexion sous-jacente ne se fermera pas et ne se rouvrira pas pour chaque boucle. Laissez le gestionnaire de pool de connexions gérer cela. Consultez ce lien pour plus d'informations: Optimisation du regroupement de connexions ADO.NET dans les applications ASP.NET

Si vous voulez vous assurer que chaque commande est exécutée avec succès, vous pouvez utiliser un Transaction et Rollback si nécessaire,

8
Rango

Quand il y a beaucoup d'entrées, pensez à utiliser SqlBulkCopy . La performance est beaucoup plus rapide qu'une série d'inserts simples.

2
Tim Mahy

Suivi de @Tim Mahy - Il existe deux manières de nourrir SqlBulkCopy: un DataReader ou via DataTable. Voici le code pour DataTable:

DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("Id", typeof(string)));
dt.Columns.Add(new DataColumn("Name", typeof(string)));
foreach (Entry entry in entries)
    dt.Rows.Add(new string[] { entry.Id, entry.Name });

using (SqlBulkCopy bc = new SqlBulkCopy(connection))
{   // the following 3 lines might not be neccessary
    bc.DestinationTableName = "Entries";
    bc.ColumnMappings.Add("Id", "Id");
    bc.ColumnMappings.Add("Name", "Name");

    bc.WriteToServer(dt);
}
1
simaglei

Vous pouvez directement insérer une DataTable si elle est créée correctement.

Tout d’abord, assurez-vous que les colonnes de la table d’accès ont le même nom de colonne et le même type. Ensuite, vous pouvez utiliser cette fonction qui, à mon avis, est très rapide et élégante.

public void AccessBulkCopy(DataTable table)
{
    foreach (DataRow r in table.Rows)
        r.SetAdded();

    var myAdapter = new OleDbDataAdapter("SELECT * FROM " + table.TableName, _myAccessConn);

    var cbr = new OleDbCommandBuilder(myAdapter);
    cbr.QuotePrefix = "[";
    cbr.QuoteSuffix = "]";
    cbr.GetInsertCommand(true);

    myAdapter.Update(table);
}
0
0014