此方案也是在我公司中使用,以满足公司的批量插入数据的需求,例如第三方的对账数据此方法使用的是Expression动态生成数据转换函数,其效率和手写的原生代码差不多,和原生手写代码相比,多余的转换损失很小【最大的性能损失都是在值类型拆装箱上】
此方案和其他网上的方案有些不同的是:不是将List先转换成DataTable,然后写入SqlBulkCopy的,而是使用一个实现IDataReader的读取器包装List,每往SqlBulkCopy插入一行数据才会转换一行数据
IDataReader方案和DataTable方案相比优点
效率高:DataTable方案需要先完全转换后,才能交由SqlBulkCopy写入数据库,而IDataReader方案可以边转换边交给SqlBulkCopy写入数据库(例如:10万数据插入速度可提升30%)
占用内存少:DataTable方案需要先完全转换后,才能交由SqlBulkCopy写入数据库,需要占用大量内存,而IDataReader方案可以边转换边交给SqlBulkCopy写入数据库,无须占用过多内存
强大:因为是边写入边转换,而且EnumerableReader传入的是一个迭代器,可以实现持续插入数据的效果
2.实现原理
① 实体Model与表映射
数据库表代码
CREATE TABLE [dbo].[Person]( [Id] [BIGINT] NOT NULL, [Name] [VARCHAR](64) NOT NULL, [Age] [INT] NOT NULL, [CreateTime] [DATETIME] NULL, [Sex] [INT] NOT NULL, PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
实体类代码
public class Person { public long Id { get; set; } public string Name { get; set; } public int Age { get; set; } public DateTime? CreateTime { get; set; } public Gender Sex { get; set; } }
public enum Gender { Man = 0, Woman = 1 }
创建字段映射【如果没有此字段映射会导致数据填错位置,如果类型不对还会导致报错】【因为:没有此字段映射默认是按照列序号对应插入的】
创建映射使用的SqlBulkCopy类型的ColumnMappings属性来完成,数据列与数据库中列的映射
//创建批量插入对象 using (var copy = new SqlBulkCopy(connection, options, externalTransaction)) { foreach (var column in ModelToDataTable<TModel>.Columns) { //创建字段映射 copy.ColumnMappings.Add(column.ColumnName, column.ColumnName); } }
② 实体转换成数据行
将数据转换成数据行采用的是:反射+Expression来完成
其中反射是用于获取编写Expression所需程序类,属性等信息
其中Expression是用于生成高效转换函数其中ModelToDataTable类型利用了静态泛型类特性,实现泛型参数的缓存效果
在ModelToDataTable的静态构造函数中,生成转换函数,获取需要转换的属性信息,并存入静态只读字段中,完成缓存
③ 使用IDataReader插入数据的重载
EnumerableReader是实现了IDataReader接口的读取类,用于将模型对象,在迭代器中读取出来,并转换成数据行,可供SqlBulkCopy读取
SqlBulkCopy只会调用三个方法:GetOrdinal、Read、GetValue其中GetOrdinal只会在首行读取每个列所代表序号【需要填写:SqlBulkCopy类型的ColumnMappings属性】
其中Read方法是迭代到下一行,并调用ModelToDataTable.ToRowData.Invoke()来将模型对象转换成数据行object[]其中GetValue方法是获取当前行指定下标位置的值
3.完整代码
扩展方法类
public static class SqlConnectionExtension { /// <summary> /// 批量复制 /// </summary> /// <typeparam>插入的模型对象</typeparam> /// <param>需要批量插入的数据源</param> /// <param>数据库连接对象</param> /// <param>插入表名称【为NULL默认为实体名称】</param> /// <param>插入超时时间</param> /// <param>写入数据库一批数量【如果为0代表全部一次性插入】最合适数量【这取决于您的环境,尤其是行数和网络延迟。就个人而言,我将从BatchSize属性设置为1000行开始,然后看看其性能如何。如果可行,那么我将使行数加倍(例如增加到2000、4000等),直到性能下降或超时。否则,如果超时发生在1000,那么我将行数减少一半(例如500),直到它起作用为止。】</param> /// <param>批量复制参数</param> /// <param>执行的事务对象</param> /// <returns>插入数量</returns> public static int BulkCopy<TModel>(this SqlConnection connection, IEnumerable<TModel> source, string tableName = null, int bulkCopyTimeout = 30, int batchSize = 0, SqlBulkCopyOptions options = SqlBulkCopyOptions.Default, SqlTransaction externalTransaction = null) { //创建读取器 using (var reader = new EnumerableReader<TModel>(source)) { //创建批量插入对象 using (var copy = new SqlBulkCopy(connection, options, externalTransaction)) { //插入的表 copy.DestinationTableName = tableName ?? typeof(TModel).Name; //写入数据库一批数量 copy.BatchSize = batchSize; //超时时间 copy.BulkCopyTimeout = bulkCopyTimeout; //创建字段映射【如果没有此字段映射会导致数据填错位置,如果类型不对还会导致报错】【因为:没有此字段映射默认是按照列序号对应插入的】 foreach (var column in ModelToDataTable<TModel>.Columns) { //创建字段映射 copy.ColumnMappings.Add(column.ColumnName, column.ColumnName); } //将数据批量写入数据库 copy.WriteToServer(reader); //返回插入数据数量 return reader.Depth; } } }
(编辑:焦作站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|