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?
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.
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;
}
}
}
}
}
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,
Quand il y a beaucoup d'entrées, pensez à utiliser SqlBulkCopy . La performance est beaucoup plus rapide qu'une série d'inserts simples.
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);
}
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);
}