在 IBatisNet 没有 IBatis4Java 的 startBatch() 函数,这让我们批量插入大量数据时很受困扰。本文介绍了如何在 IBatisNet 和 ADO.Net 中批量插入数据。
说到批量插入,我们有以下解决方案:
1) 直接执行用 SqlCommand 执行 INSERT 语句,一条一条的插入。无疑,这样效率最低。
2) 把拼接INSERT 语句,一次插入多条;这样性能不好说,最大的问题就是 SqlCommand 有参数限制(2100个)。
3) 利用 SqlBulkCopy 或SqlDataAdapter 的 Update 方法,可以实现批量的更新/插入,但是数据是基于 DataRow 的,并且最终 DataRow 还是要转换为 SqlCommand,对于 IBatisNet Mapping,显然我们不想这样做。
最好能怎么样呢?
我的思路就是批量执行 SqlCommand,因为 IBatisNet 最终也需要转化 statement 为 DbCommand,因此只要得到把 statement 和 parameters 混合以后的 SqlCommand,就可以了。
首先,我们要能够批量执行 SqlCommand,这里是直接反射调用 SqlDataAdapter 的批处理函数实现的,这些方法受保护,所以只能反射调用。
代码如下:
然后是我们需要一个批量插入的模板 statement
最后是重点,转化 statement 和 parameterObject 为 SqlCommand 并加入批处理操作执行。
经测试,在我的老牛车上速度良好,插入三万条14个字段的记录只需要几秒。
即使不需要使用 IBatisNet,你也也可以使用 SqlBatcher 完成常规的批量的命令执行。
搜了下 cnblogs 和 Google ,没有发现 IBatisNet 对于这个问题的解决方法,于是就自己解决并共享,希望对各位有用。
说到批量插入,我们有以下解决方案:
1) 直接执行用 SqlCommand 执行 INSERT 语句,一条一条的插入。无疑,这样效率最低。
2) 把拼接INSERT 语句,一次插入多条;这样性能不好说,最大的问题就是 SqlCommand 有参数限制(2100个)。
3) 利用 SqlBulkCopy 或SqlDataAdapter 的 Update 方法,可以实现批量的更新/插入,但是数据是基于 DataRow 的,并且最终 DataRow 还是要转换为 SqlCommand,对于 IBatisNet Mapping,显然我们不想这样做。
最好能怎么样呢?
我的思路就是批量执行 SqlCommand,因为 IBatisNet 最终也需要转化 statement 为 DbCommand,因此只要得到把 statement 和 parameters 混合以后的 SqlCommand,就可以了。
首先,我们要能够批量执行 SqlCommand,这里是直接反射调用 SqlDataAdapter 的批处理函数实现的,这些方法受保护,所以只能反射调用。
代码如下:
/**
<code>
<revsion>$Rev: 34 $</revision>
<owner name="Zealic" mail="rszealic@gmail.com" />
</code>
**/
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
namespace YourApplicationOrLibrary.Data
{
/// <summary>
/// 为 Sql Server 提供批量处理操作。
/// </summary>
public class SqlBatcher
{
private MethodInfo m_AddToBatch;
private MethodInfo m_ClearBatch;
private MethodInfo m_InitializeBatching;
private MethodInfo m_ExecuteBatch;
private SqlDataAdapter m_Adapter;
private bool _Started;
/// <summary>
/// 构造一个新的 SqlBatcher。
/// </summary>
public SqlBatcher()
{
Type type = typeof(SqlDataAdapter);
m_AddToBatch = type.GetMethod("AddToBatch", BindingFlags.NonPublic | BindingFlags.Instance);
m_ClearBatch = type.GetMethod("ClearBatch", BindingFlags.NonPublic | BindingFlags.Instance);
m_InitializeBatching = type.GetMethod("InitializeBatching", BindingFlags.NonPublic | BindingFlags.Instance);
m_ExecuteBatch = type.GetMethod("ExecuteBatch", BindingFlags.NonPublic | BindingFlags.Instance);
}
/// <summary>
/// 获得批处理是否正在批处理状态。
/// </summary>
public bool Started
{
get { return _Started; }
}
/// <summary>
/// 开始批处理。
/// </summary>
/// <param name="connection">连接。</param>
public void StartBatch(SqlConnection connection)
{
if (_Started) return;
SqlCommand command = new SqlCommand();
command.Connection = connection;
m_Adapter = new SqlDataAdapter();
m_Adapter.InsertCommand = command;
m_InitializeBatching.Invoke(m_Adapter, null);
_Started = true;
}
/// <summary>
/// 添加批命令。
/// </summary>
/// <param name="command">命令</param>
public void AddToBatch(IDbCommand command)
{
if (!_Started) throw new InvalidOperationException();
m_AddToBatch.Invoke(m_Adapter, new object[1] { command });
}
/// <summary>
/// 执行批处理。
/// </summary>
/// <returns>影响的数据行数。</returns>
public int ExecuteBatch()
{
if (!_Started) throw new InvalidOperationException();
return (int)m_ExecuteBatch.Invoke(m_Adapter, null);
}
/// <summary>
/// 结束批处理。
/// </summary>
public void EndBatch()
{
if (_Started)
{
ClearBatch();
m_Adapter.Dispose();
m_Adapter = null;
_Started = false;
}
}
/// <summary>
/// 清空保存的批命令。
/// </summary>
public void ClearBatch()
{
if (!_Started) throw new InvalidOperationException();
m_ClearBatch.Invoke(m_Adapter, null);
}
}
}
<code>
<revsion>$Rev: 34 $</revision>
<owner name="Zealic" mail="rszealic@gmail.com" />
</code>
**/
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
namespace YourApplicationOrLibrary.Data
{
/// <summary>
/// 为 Sql Server 提供批量处理操作。
/// </summary>
public class SqlBatcher
{
private MethodInfo m_AddToBatch;
private MethodInfo m_ClearBatch;
private MethodInfo m_InitializeBatching;
private MethodInfo m_ExecuteBatch;
private SqlDataAdapter m_Adapter;
private bool _Started;
/// <summary>
/// 构造一个新的 SqlBatcher。
/// </summary>
public SqlBatcher()
{
Type type = typeof(SqlDataAdapter);
m_AddToBatch = type.GetMethod("AddToBatch", BindingFlags.NonPublic | BindingFlags.Instance);
m_ClearBatch = type.GetMethod("ClearBatch", BindingFlags.NonPublic | BindingFlags.Instance);
m_InitializeBatching = type.GetMethod("InitializeBatching", BindingFlags.NonPublic | BindingFlags.Instance);
m_ExecuteBatch = type.GetMethod("ExecuteBatch", BindingFlags.NonPublic | BindingFlags.Instance);
}
/// <summary>
/// 获得批处理是否正在批处理状态。
/// </summary>
public bool Started
{
get { return _Started; }
}
/// <summary>
/// 开始批处理。
/// </summary>
/// <param name="connection">连接。</param>
public void StartBatch(SqlConnection connection)
{
if (_Started) return;
SqlCommand command = new SqlCommand();
command.Connection = connection;
m_Adapter = new SqlDataAdapter();
m_Adapter.InsertCommand = command;
m_InitializeBatching.Invoke(m_Adapter, null);
_Started = true;
}
/// <summary>
/// 添加批命令。
/// </summary>
/// <param name="command">命令</param>
public void AddToBatch(IDbCommand command)
{
if (!_Started) throw new InvalidOperationException();
m_AddToBatch.Invoke(m_Adapter, new object[1] { command });
}
/// <summary>
/// 执行批处理。
/// </summary>
/// <returns>影响的数据行数。</returns>
public int ExecuteBatch()
{
if (!_Started) throw new InvalidOperationException();
return (int)m_ExecuteBatch.Invoke(m_Adapter, null);
}
/// <summary>
/// 结束批处理。
/// </summary>
public void EndBatch()
{
if (_Started)
{
ClearBatch();
m_Adapter.Dispose();
m_Adapter = null;
_Started = false;
}
}
/// <summary>
/// 清空保存的批命令。
/// </summary>
public void ClearBatch()
{
if (!_Started) throw new InvalidOperationException();
m_ClearBatch.Invoke(m_Adapter, null);
}
}
}
然后是我们需要一个批量插入的模板 statement
<insert id="SaveArray" parameterClass="TelnetRecord">
INSERT INTO [TelnetRecord]([state],[file_name]) VALUES(#State#,#FileName#)
</insert>
INSERT INTO [TelnetRecord]([state],[file_name]) VALUES(#State#,#FileName#)
</insert>
最后是重点,转化 statement 和 parameterObject 为 SqlCommand 并加入批处理操作执行。
public class TelnetRecordDao
{
//由于IBatisNet 用 DbCommandDecorator 包装了 DbCommand,因此必须使用反射取得该字段
private static readonly FieldInfo m_InnerCommandField = typeof(IBatisNet.DataMapper.Commands.DbCommandDecorator).GetField("_innerDbCommand", BindingFlags.Instance | BindingFlags.NonPublic);
private SqlBatcher m_Batcher = new SqlBatcher();
// 转化 IBatis 包装后的 DbCommand 为 原始的 DbCommand
private IDbCommand GetCommand(IDbCommand ibatCommand)
{
return (IDbCommand)m_InnerCommandField.GetValue(ibatCommand);
}
// 批量保存
public int Save(TelnetRecord[] recordArray)
{
if (recordArray == null)
throw new ArgumentNullException("recordArray");
if (recordArray.Length < 1)
throw new ArgumentException("recordArray");
ISqlMapper mapper = Mapper.Instance();
ISqlMapSession session = mapper.LocalSession;
IMappedStatement mappedStatment = mapper.GetMappedStatement("SaveArray");
IStatement st = mappedStatment.Statement;
IPreparedCommand pc = mappedStatment.PreparedCommand;
// 执行批处理命令
m_Batcher.StartBatch(session.Connection as SqlConnection);
RequestScope request = st.Sql.GetRequestScope(mappedStatment, recordArray[0], session);
foreach (TelnetRecord record in recordArray)
{
pc.Create(request, session, st, record);
m_Batcher.AddToBatch(GetCommand(request.IDbCommand));
}
session.OpenConnection();
int ret = m_Batcher.ExecuteBatch();
m_Batcher.EndBatch();
return ret;
}
}
{
//由于IBatisNet 用 DbCommandDecorator 包装了 DbCommand,因此必须使用反射取得该字段
private static readonly FieldInfo m_InnerCommandField = typeof(IBatisNet.DataMapper.Commands.DbCommandDecorator).GetField("_innerDbCommand", BindingFlags.Instance | BindingFlags.NonPublic);
private SqlBatcher m_Batcher = new SqlBatcher();
// 转化 IBatis 包装后的 DbCommand 为 原始的 DbCommand
private IDbCommand GetCommand(IDbCommand ibatCommand)
{
return (IDbCommand)m_InnerCommandField.GetValue(ibatCommand);
}
// 批量保存
public int Save(TelnetRecord[] recordArray)
{
if (recordArray == null)
throw new ArgumentNullException("recordArray");
if (recordArray.Length < 1)
throw new ArgumentException("recordArray");
ISqlMapper mapper = Mapper.Instance();
ISqlMapSession session = mapper.LocalSession;
IMappedStatement mappedStatment = mapper.GetMappedStatement("SaveArray");
IStatement st = mappedStatment.Statement;
IPreparedCommand pc = mappedStatment.PreparedCommand;
// 执行批处理命令
m_Batcher.StartBatch(session.Connection as SqlConnection);
RequestScope request = st.Sql.GetRequestScope(mappedStatment, recordArray[0], session);
foreach (TelnetRecord record in recordArray)
{
pc.Create(request, session, st, record);
m_Batcher.AddToBatch(GetCommand(request.IDbCommand));
}
session.OpenConnection();
int ret = m_Batcher.ExecuteBatch();
m_Batcher.EndBatch();
return ret;
}
}
经测试,在我的老牛车上速度良好,插入三万条14个字段的记录只需要几秒。
即使不需要使用 IBatisNet,你也也可以使用 SqlBatcher 完成常规的批量的命令执行。
搜了下 cnblogs 和 Google ,没有发现 IBatisNet 对于这个问题的解决方法,于是就自己解决并共享,希望对各位有用。