bot*_*ked 28 c# ado.net sql-server-2005 multiple-insert
我有一个名为Entry这样的类:
class Entry{
string Id {get;set;}
string Name {get;set;}
}
Run Code Online (Sandbox Code Playgroud)
然后Entry使用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
...
}
}
Run Code Online (Sandbox Code Playgroud)
我的问题是:我应该继续使用上面的方式发送多个insert语句(构建一个巨大的insert语句及其参数字符串并通过网络发送),或者我应该保持一个打开的连接并发送一个insert语句对于每个人Entry这样:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
你怎么看?两者之间的Sql Server会有性能差异吗?我应该注意其他任何后果吗?
AMi*_*ico 51
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[0].Value = oSetting.Key;
oCommand.Parameters[1].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();
throw;
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Gio*_*rgi 26
如果我是你,我就不会使用其中任何一个.
第一个缺点是如果列表中有相同的值,参数名称可能会发生冲突.
第二个缺点是您正在为每个实体创建命令和参数.
最好的方法是将命令文本和参数构造一次(用于Parameters.Add添加参数)在循环中更改它们的值并执行命令.这样,该声明只准备一次.您还应该在开始循环之前打开连接并在之后关闭它.
Ian*_*oyd 10
真正可怕的方法是将每个INSERT语句作为自己的批处理执行:
第 1 批:
INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd);
Run Code Online (Sandbox Code Playgroud)
第 2 批:
INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked);
Run Code Online (Sandbox Code Playgroud)
第 3 批:
INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz);
Run Code Online (Sandbox Code Playgroud)
第 4 批:
INSERT INTO Entries (id, name) VALUES (4, 'Giorgi);
Run Code Online (Sandbox Code Playgroud)
第 5 批:
INSERT INTO Entries (id, name) VALUES (5, 'AMissico);
Run Code Online (Sandbox Code Playgroud)
注意:出于说明目的,参数化、错误检查和任何其他挑剔的东西都被省略了。
这是真正的、可怕的、可怕的做事方式。它提供了真正糟糕的性能,因为您每次都会遭受网络往返时间。
一个更好的解决方案是将所有INSERT语句批处理为一个批处理:
第 1 批:
INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd');
INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked');
INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz');
INSERT INTO Entries (id, name) VALUES (4, 'Giorgi');
INSERT INTO Entries (id, name) VALUES (5, 'AMissico');
Run Code Online (Sandbox Code Playgroud)
这样你只会遭受一次往返。这个版本有巨大的性能优势;快 5 倍。
更好的是使用VALUES子句:
INSERT INTO Entries (id, name)
VALUES
(1, 'Ian Boyd'),
(2, 'Bottlenecked'),
(3, 'Marek Grzenkowicz'),
(4, 'Giorgi'),
(5, 'AMissico');
Run Code Online (Sandbox Code Playgroud)
与 5 个单独的INSERTs 版本相比,这为您提供了一些性能改进;它让服务器做它擅长的事情:在集合上操作:
SQL Sever 喜欢对数据集进行操作;这就是维京人的地方!
为清楚起见,上面的 T-SQL 示例删除了所有参数化内容。但实际上你想参数化查询
但是布鲁诺有一个重要的观点;SQL Server 的驱动程序只允许您批量包含 2,100 个参数。上面的查询有两个值:
@id,@name
如果您在单个批次中导入 1,051 行,那就是 2,102 个参数 - 您将收到错误消息:
此 RPC 请求中提供的参数过多
这就是为什么我通常一次插入 5 或 10 行。每批添加更多行并没有太大提高性能 - 收益递减。
它将参数数量保持在较低水平,不会接近 T-SQL 批量大小限制。还有一个事实是,一个VALUES子句无论如何都被限制为 1000 个元组。
您的第一种方法很好,但确实存在以下问题:
所以目标是生成一个字符串,例如:
INSERT INTO Entries (id, name) VALUES
(@p1, @p2),
(@p3, @p4),
(@p5, @p6),
(@p7, @p8),
(@p9, @p10)
Run Code Online (Sandbox Code Playgroud)
我会根据我的裤子改变你的代码
IEnumerable<Entry> entries = GetStuffToInsert();
SqlCommand cmd = new SqlCommand();
StringBuilder sql = new StringBuilder();
Int32 batchSize = 0; //how many rows we have build up so far
Int32 p = 1; //the current paramter name (i.e. "@p1") we're going to use
foreach(var entry in entries)
{
//Build the names of the parameters
String pId = String.Format("@p{0}", p); //the "Id" parameter name (i.e. "p1")
String pName = String.Format("@p{0}", p+1); //the "Name" parameter name (i.e. "p2")
p += 2;
//Build a single "(p1, p2)" row
String row = String.Format("({0}, {1})", pId, pName); //a single values tuple
//Add the row to our running SQL batch
if (batchSize > 0)
sb.AppendLine(",");
sb.Append(row);
batchSize += 1;
//Add the parameter values for this row
cmd.Parameters.Add(pID, System.Data.SqlDbType.Int ).Value = entry.Id;
cmd.Parameters.Add(pName, System.Data.SqlDbType.String).Value = entry.Name;
if (batchSize >= 5)
{
String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
sb.ToString();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
sb.Clear();
batchSize = 0;
p = 1;
}
}
//handle the last few stragglers
if (batchSize > 0)
{
String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
sb.ToString();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
Run Code Online (Sandbox Code Playgroud)
你应该在每个循环上执行命令,而不是构建一个巨大的命令Text(顺便说一句,StringBuilder就是这样做的)底层连接不会关闭并重新打开每个循环,让连接池管理器处理这个.有关更多信息,请查看此链接:在ASP.NET应用程序中调整ADO.NET连接池
如果要确保成功执行每个命令,可以根据需要使用事务和回滚,
小智 5
@Tim Mahy的后续操作-有两种可能的方式来馈送SqlBulkCopy:DataReader或通过DataTable。这是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);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
80495 次 |
| 最近记录: |