前言:

  由于系统升级,新开发的系统对数据验证,及数据关联做了很多优化,现需要将原历史版本的数据迁移到新系统中;原数据库大约有 1千多万数据,大约 50个表。

  历史数据库命名为:A。 新系统库暂命名为 :B;

  使用  .net 4.5 控制台程序 + EF + MSSQL 数据库,由于有业务逻辑及时序处理,故只能按时序从单表一条条的写入到新库中;

化化过程:

  1、EF 如果使用多线程会出现 Sql 连接超过,或是连接不上数据库;

  2、EF 优化连接 自定义 SqlConnection,并传到入 多线程中,解决连接不上数据库的问题减少数据库连接数,但由于 EF 在 SaveChangesAsync的时候做了事务提交,但事务是不支持并行操作,故会出现异常;

  3、EF 优化事务,关闭EF默认事务  DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands = false; 这里有个坑  关闭事务对  SaveChangesAsync 无效,问题依然存在;

  4、找了很多资料总算找到可以通过  ExecuteSqlCommandAsync 执行 Sql 语句,可以关闭事务;

  5、优化成执行Sql 语句:await db.Database.ExecuteSqlCommandAsync(TransactionalBehavior.DoNotEnsureTransaction, sql, SqlParameters[]);

  经过以上优化处理后,就开始写代码:

一、关键的异步锁程序:

    /// <summary>
/// 提供异步锁
/// </summary>
class AsyncRoot : IDisposable
{
/// <summary>
/// 信号量
/// </summary>
private readonly SemaphoreSlim semaphoreSlim; /// <summary>
/// 异步锁
/// </summary>
public AsyncRoot()
: this()
{
} /// <summary>
/// 异步锁
/// </summary>
/// <param name="concurrent">允许并行的线程数</param>
public AsyncRoot(int concurrent)
{
this.semaphoreSlim = new SemaphoreSlim(concurrent, concurrent);
} /// <summary>
/// 锁住代码块
/// using( asyncRoot.Lock() ){ }
/// </summary>
/// <returns></returns>
public IDisposable Lock()
{
this.semaphoreSlim.Wait();
return new UnLocker(this.semaphoreSlim);
} /// <summary>
/// 锁住代码块
/// using( await asyncRoot.LockAsync() ){ }
/// </summary>
/// <returns></returns>
public async Task<IDisposable> LockAsync()
{
await this.semaphoreSlim.WaitAsync().ConfigureAwait(false);
return new UnLocker(this.semaphoreSlim);
} /// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
this.semaphoreSlim.Dispose();
} /// <summary>
/// 提供解锁
/// </summary>
class UnLocker : IDisposable
{
/// <summary>
/// 信号量
/// </summary>
private readonly SemaphoreSlim semaphoreSlim; /// <summary>
/// 解锁
/// </summary>
/// <param name="semaphoreSlim">信号量</param>
public UnLocker(SemaphoreSlim semaphoreSlim)
{
this.semaphoreSlim = semaphoreSlim;
} /// <summary>
/// 释放锁
/// </summary>
public void Dispose()
{
this.semaphoreSlim.Release();
}
}
}

多线层异常锁

二、对数据插入到数据库:

逻辑分析:对传入的 数据集合,拆分为单个实体操作任务,每个任务使用同一个连接独立的数据库上下文,对实体反射为 Sql 语句(其中增加主键,表名、字段名、值的判断验证),

然后通过 ExecuteSqlCommandAsync 不使用事务的方式执行 Sql 语句;具体代码见下:

//表示最大线程数
private readonly AsyncRoot root = new AsyncRoot(50);

    /// <summary>
/// 多线程工作
/// </summary>
public class Workers
{
/// <summary>
/// 多线程锁
/// </summary>
private readonly AsyncRoot root = new AsyncRoot(); /// <summary>
/// 执行对象操作
/// </summary>
/// <param name="datas"></param>
/// <returns></returns>
public async Task RunAsync<T>(IEnumerable<T> datas) where T : class
{
//创建 Sql 连接
var connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SqlDb"].ConnectionString);
await connection.OpenAsync();
var tasks = datas.Select(item => SaveToDbAsync(item, connection));
await Task.WhenAll(tasks);
} /// <summary>
/// 单条记录保存到数据库
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="connection"></param>
/// <returns></returns>
private async Task SaveToDbAsync<T>(T data, DbConnection connection) where T : class
{
using (await root.LockAsync())
{
using (var db = new SqlDb(connection))
{
try
{
var dbset = db.Set<T>();
var tType = typeof(T);
var tableName = tType.Name;
//获取 TableAttribute 数据库中的表名
var tableAtt = Attribute.GetCustomAttribute(tType, typeof(TableAttribute)) as TableAttribute;
if (tableAtt != null)
{
tableName = tableAtt.Name;
} var sbSql = new StringBuilder(); sbSql.AppendLine("insert into " + tableName + " (");
var plist = new List<string>();
var fieldParameters = new List<SqlParameter>();
var keyFiled = "ID";
foreach (var p in typeof(T).GetProperties())
{
var pName = p.Name.ToUpper();
//获取 ColumnAttribute 数据库中的列名
var pAtt = Attribute.GetCustomAttribute(p, typeof(ColumnAttribute)) as ColumnAttribute;
if (pAtt != null)
{
pName = pAtt.Name.ToUpper();
} var keyAtt = Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) as KeyAttribute;
if (keyAtt != null || p.Name.Equals("ID", StringComparison.OrdinalIgnoreCase))
{
keyFiled = pName;
} var fieldParameter = "@" + pName;
//过滤不插入数据库中的字段
var mapAtt = Attribute.GetCustomAttribute(p, typeof(NotMappedAttribute));
if (mapAtt == null)
{
var value = p.GetValue(data, null);
//如果属性值为 Null,不插入数据库
if (value != null)
{
plist.Add(fieldParameter);
fieldParameters.Add(new SqlParameter(fieldParameter, value));
}
}
}
sbSql.Append(string.Join(",", plist.Select(item => item.Replace("@", ""))));
sbSql.Append(")values(");
sbSql.Append(string.Join(",", plist));
sbSql.Append(")");
//判断主键是否已经存在,存在就不插入数据
var ifSql = "if not exists(select 1 from [" + tableName + "] where " + keyFiled + " = @" + keyFiled + ")"; var sql = ifSql + sbSql.ToString();
await db.Database.ExecuteSqlCommandAsync(TransactionalBehavior.DoNotEnsureTransaction, sql, fieldParameters.ToArray());
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
}

多线程及对象生成 Sql插入数据库

    /// <summary>
/// Sql数据库
/// </summary>
public class SqlDb : DbContext
{
/// <summary>
/// 自定义连接
/// </summary>
/// <param name="connection">数据库连接</param>
public SqlDb(DbConnection connection) :
base(connection, false)
{
if (connection.State != System.Data.ConnectionState.Open)
{
connection.Open();
} this.Database.CommandTimeout = * ;
this.Configuration.UseDatabaseNullSemantics = true;
this.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
this.Configuration.ValidateOnSaveEnabled = false;
}
}

数据库上下文

三、注意事项:

  1、如果字段为 geography (地理位置) 类型,会出现异常,希望在使用的时候注意一下;

  2、由于集合为同一个对象,故在每次反射的对象几乎都是重复操作,可以根据实际情况增加缓存;

其它:

多线程并行操作小实例源码:https://github.com/intotf/netExample/tree/master/Tool/MultiTaskAsync

  

数据迁移最快方式,多线程并行执行 Sql插入的更多相关文章

  1. 一种可以避免数据迁移的分库分表scale-out扩容方式

    原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...

  2. [转]一种可以避免数据迁移的分库分表scale-out扩容方式

    原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...

  3. .Net5 IdentityServer4下SqlServer和Mysql数据迁移

    1.概念 以下概念从官网整理的,我也是看官网一步一步学习的 官网地址 https://identityserver4.readthedocs.io/en/latest/index.html 1.1 I ...

  4. EF6:编写你自己的code first 数据迁移操作(睡前来一篇,翻译的)

    原英文版由EF团队成员 Rowan Miller 在2013年发表,此处只作翻译备忘. 数据迁移提供了一套强类型API,用于执行通用的操作,比如CreateIndex("dbo.Blogs& ...

  5. 一种可以避免数据迁移的分库分表scale-out扩容模式

    转自: http://jm.taobao.org/ 一种可以避免数据迁移的分库分表scale-out扩容方式 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星 ...

  6. gitblit 数据迁移(复制)

    gitblit 数据迁移 完全拷贝方式: 将原服务器上的gitblit的安装目录.数据目录等相关目录拷到另一台服务器上即可,这样启动方式和使用端口及数据和原服务上的一模一样.(因为gitblit是不用 ...

  7. MySQL数据迁移到MSSQL-以小米数据库为例-测试828W最快可达到2分11秒

    这里采用.NET Framework 4.0以上版本中新出现的 ConcurrentQueue<T> 类 MSDN是这样描述的: ConcurrentQueue<T> 类是一个 ...

  8. SQL SERVER 2000/2005/2008数据库数据迁移到Oracle 10G细述

    最近参与的一个系统涉及到把SQL Server 2k的数据迁移到Oracle 10G这一非功能需求.特将涉及到相关步骤列举如下供大家参考: 环境及现有资源: 1.OS: Windows 7 Enter ...

  9. SQL Server GUID 数据迁移至MongoDB后怎样查看?

    关键字:SQL Server NEWID():BSON:MongoDB UUID 1.遇到的问题和困惑 SQL Server中的NEWID数据存储到MongoDB中会是什么样子呢?发现不能简单的通过此 ...

随机推荐

  1. RAID 2.0 技术(块虚拟化技术)

    RAID 2.0 技术(块虚拟化技术) RAID 2.0 技术(块虚拟化技术),该技术将物理的存储空间划分为若干小粒度数据块,这些小粒度的数据块均匀的分布在存储池中所有的硬盘上,然后这些小粒度的数据块 ...

  2. ES6语法:let和const

    ES6新增加了两个重要的JavaScript关键字:let和const 一.let关键字 let声明的变量只在let命令所在的代码块内有效. 1.基本语法 let a='123' 2.let和var的 ...

  3. WPF中Button的背景图片,实现禁止IsMouseOver时显示默认

    <Button x:Name="btnPickUpNum" Click="PickUpNum_OnClick" Grid.Row="1" ...

  4. SQL Server 数据库备份语句

    ); --文件名 DECLARE @date DATETIME; --日期 ); --文件存放路径 SELECT @date = GETDATE(); --获取当前时间 --根据当前时间自动生成文件名 ...

  5. VS中怎样对C#项目进行单元测试

    场景 SpringBoot+Junit在IDEA中实现查询数据库的单元测试: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/927 ...

  6. Vuex细说

    vuex 1,什么是 vuex? vuex 是一个专门为 vue.js 应用程序 开发的状态管理模式+库 它充当应用程序中所有组件的集中存储(数据状态) ,其规则确保状态只能以可预测的方式进行变更 并 ...

  7. 用AI思维给成本降温,腾讯WeTest兼容性测试直击底价!

    WeTest 导读 当AI成为各行业提高产业效率的动能,很多人开始疑惑,这架智能化的“无人机”何时在移动应用测试中真正落地?在今年的国际数码互动娱乐博览会(ChinaJoy)上,腾讯WeTest给出了 ...

  8. 前端开发工具HBuilder使用技巧以及快捷键

    创建HTML结构: h 8 (敲h激活代码块列表,按8选择第8个项目,即HTML代码块,或者敲h t Enter) 中途换行: 'Ctrl+Enter' 设置charset: m e 6 Enter ...

  9. docker部署gitlab-ce

    简介 环境准备 centos7 docker 1.13.1 gitlab-ce 安装步骤 1.首先需要从docker镜像仓库当中获取gitlab-ce的最新镜像文件,由于我本机已经获取了该镜像,所以在 ...

  10. 并发修改异常ConcurrentModificationException

    1.简述:在使用 迭代器对象遍历集合时,使用集合对象修改集合中的元素导致出现异常 public static void main(String[] args) { List<Integer> ...