使用 ODBC 连接从 DataTable 批量插入 SQL 服务器 table
Batch insert to SQL Server table from DataTable using ODBC Connection
我被要求寻找使用 C# 获取数据表输入并将其写入 SQL 服务器 table 的最有效方法。问题是解决方案必须始终使用 ODBC 连接,这排除了 sqlBulkCopy。该解决方案还必须适用于所有 SQL 服务器版本,回到 SQL Server 2008 R2。
我认为最好的方法是使用以下 SQL 语法一次批量插入 1000 行:
插入 dbo.Table1(Field1, Field2)
SELECT 值 1,值 2
联盟
SELECT 值 1,值 2
我已经编写了代码来检查 SQL 服务器上是否已经存在对应于 DataTable 输入的 table,如果不存在则创建一个。
我还编写了创建 INSERT 语句本身的代码。我苦苦挣扎的是如何从数据 table 中的行动态构建 SELECT 语句。如何访问行中的值以构建我的 SELECT 语句?我想我还需要检查每列的数据类型以确定值是否需要用单引号 (') 括起来。
这是我当前的代码:
public bool CopyDataTable(DataTable sourceTable, OdbcConnection targetConn, string targetTable)
{
OdbcTransaction tran = null;
string[] selectStatement = new string[sourceTable.Rows.Count];
// Check if targetTable exists, create it if it doesn't
if (!TableExists(targetConn, targetTable))
{
bool created = CreateTableFromDataTable(targetConn, sourceTable);
if (!created)
return false;
}
try
{
// Prepare insert statement based on sourceTable
string insertStatement = string.Format("INSERT INTO [dbo].[{0}] (", targetTable);
foreach (DataColumn dataColumn in sourceTable.Columns)
{
insertStatement += dataColumn + ",";
}
insertStatement += insertStatement.TrimEnd(',') + ") ";
// Open connection to target db
using (targetConn)
{
if (targetConn.State != ConnectionState.Open)
targetConn.Open();
tran = targetConn.BeginTransaction();
for (int i = 0; i < sourceTable.Rows.Count; i++)
{
DataRow row = sourceTable.Rows[i];
// Need to iterate through columns in row, getting values and data types and building a SELECT statement
selectStatement[i] = "SELECT ";
}
insertStatement += string.Join(" UNION ", selectStatement);
using (OdbcCommand cmd = new OdbcCommand(insertStatement, targetConn, tran))
{
cmd.ExecuteNonQuery();
}
tran.Commit();
return true;
}
}
catch
{
tran.Rollback();
return false;
}
}
如有任何建议,我们将不胜感激。此外,如果有比我建议的方法更简单的方法,那么任何细节都会很棒。
好的,因为我们不能使用存储过程或批量复制;几年前,当我对各种方法进行建模时,性能的关键决定因素是对服务器的调用次数。因此,发现将一组 MERGE 或 INSERT 语句批处理到一个由分号分隔的调用中是最快的方法。我最终对 SQL 语句进行了批处理。我认为 SQL 语句的最大大小是 32k,所以我将我的批处理分成了那个大小的单元。
(注意 - 使用 StringBuilder 而不是手动连接字符串 - 它对性能有好处)
Psuedo-code
string sqlStatement = "INSERT INTO Tab1 VALUES {0},{1},{2}";
StringBuilder sqlBatch = new StringBuilder();
foreach(DataRow row in myDataTable)
{
sqlBatch.AppendLine(string.Format(sqlStatement, row["Field1"], row["Field2"], row["Field3"]));
sqlBatch.Append(";");
}
myOdbcConnection.ExecuteSql(sqlBatch.ToString());
您需要处理批量大小的复杂性,以及在字符串替换步骤中格式化正确的字段数据类型,否则这将是最佳性能。
PhillipH 的标记解决方案是开放的几个错误和 SQL 注入。
通常你应该构建一个带参数的 DbCommand
并执行它而不是执行一个自构建的 SQL 语句。
对于 ODBC 和 OLEDB,CommandText 必须是 "INSERT INTO Tab1 VALUES ?,?,?"
,SqlClient 需要命名参数(“@”)。
参数应与底层柱的尺寸一起添加。
我被要求寻找使用 C# 获取数据表输入并将其写入 SQL 服务器 table 的最有效方法。问题是解决方案必须始终使用 ODBC 连接,这排除了 sqlBulkCopy。该解决方案还必须适用于所有 SQL 服务器版本,回到 SQL Server 2008 R2。
我认为最好的方法是使用以下 SQL 语法一次批量插入 1000 行:
插入 dbo.Table1(Field1, Field2) SELECT 值 1,值 2 联盟 SELECT 值 1,值 2
我已经编写了代码来检查 SQL 服务器上是否已经存在对应于 DataTable 输入的 table,如果不存在则创建一个。
我还编写了创建 INSERT 语句本身的代码。我苦苦挣扎的是如何从数据 table 中的行动态构建 SELECT 语句。如何访问行中的值以构建我的 SELECT 语句?我想我还需要检查每列的数据类型以确定值是否需要用单引号 (') 括起来。
这是我当前的代码:
public bool CopyDataTable(DataTable sourceTable, OdbcConnection targetConn, string targetTable)
{
OdbcTransaction tran = null;
string[] selectStatement = new string[sourceTable.Rows.Count];
// Check if targetTable exists, create it if it doesn't
if (!TableExists(targetConn, targetTable))
{
bool created = CreateTableFromDataTable(targetConn, sourceTable);
if (!created)
return false;
}
try
{
// Prepare insert statement based on sourceTable
string insertStatement = string.Format("INSERT INTO [dbo].[{0}] (", targetTable);
foreach (DataColumn dataColumn in sourceTable.Columns)
{
insertStatement += dataColumn + ",";
}
insertStatement += insertStatement.TrimEnd(',') + ") ";
// Open connection to target db
using (targetConn)
{
if (targetConn.State != ConnectionState.Open)
targetConn.Open();
tran = targetConn.BeginTransaction();
for (int i = 0; i < sourceTable.Rows.Count; i++)
{
DataRow row = sourceTable.Rows[i];
// Need to iterate through columns in row, getting values and data types and building a SELECT statement
selectStatement[i] = "SELECT ";
}
insertStatement += string.Join(" UNION ", selectStatement);
using (OdbcCommand cmd = new OdbcCommand(insertStatement, targetConn, tran))
{
cmd.ExecuteNonQuery();
}
tran.Commit();
return true;
}
}
catch
{
tran.Rollback();
return false;
}
}
如有任何建议,我们将不胜感激。此外,如果有比我建议的方法更简单的方法,那么任何细节都会很棒。
好的,因为我们不能使用存储过程或批量复制;几年前,当我对各种方法进行建模时,性能的关键决定因素是对服务器的调用次数。因此,发现将一组 MERGE 或 INSERT 语句批处理到一个由分号分隔的调用中是最快的方法。我最终对 SQL 语句进行了批处理。我认为 SQL 语句的最大大小是 32k,所以我将我的批处理分成了那个大小的单元。
(注意 - 使用 StringBuilder 而不是手动连接字符串 - 它对性能有好处)
Psuedo-code
string sqlStatement = "INSERT INTO Tab1 VALUES {0},{1},{2}";
StringBuilder sqlBatch = new StringBuilder();
foreach(DataRow row in myDataTable)
{
sqlBatch.AppendLine(string.Format(sqlStatement, row["Field1"], row["Field2"], row["Field3"]));
sqlBatch.Append(";");
}
myOdbcConnection.ExecuteSql(sqlBatch.ToString());
您需要处理批量大小的复杂性,以及在字符串替换步骤中格式化正确的字段数据类型,否则这将是最佳性能。
PhillipH 的标记解决方案是开放的几个错误和 SQL 注入。
通常你应该构建一个带参数的 DbCommand
并执行它而不是执行一个自构建的 SQL 语句。
对于 ODBC 和 OLEDB,CommandText 必须是 "INSERT INTO Tab1 VALUES ?,?,?"
,SqlClient 需要命名参数(“@
参数应与底层柱的尺寸一起添加。