温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C#读取Txt大数据并更新到数据库的示例分析

发布时间:2021-02-03 12:39:10 来源:亿速云 阅读:173 作者:小新 栏目:编程语言

这篇文章主要介绍了C#读取Txt大数据并更新到数据库的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

环境

  • Sqlserver 2016

  •  .net 4.5.2 

目前测试数据1300万 大约3-4分钟.(限制一次读取条数 和 线程数是 要节省服务器资源,如果调太大服务器其它应用可能就跑不了了), SqlServerDBHelper为数据库帮助类.没有什么特别的处理. 配置连接串时记录把连接池开起来

另外.以下代码中每次写都创建了连接 .之前试过一个连接反复用. 130次大约有20多次 数据库会出问题.并且需要的时间是7-8分钟 左右.

配置文件: xxx.json

[ {
 /*连接字符串 */
 "ConnStr": "",
 "FilePath": "读取的文件地址",
 /*数据库表名称 */
 "TableName": "写入的数据库表名",
 /*导入前执行的语句 */
 "ExecBeforeSql": "",
 /*导入后执行的语句 */
 "ExecAfterSql": "",
 /*映射关系 */
 "Mapping": [
 {
 "DBName": "XXX",
 "TxtName": "DDD"
 } 
 ],
 /*过滤数据的正则 当前只实现了小数据一次性读完的检查*/
 "FilterRegex": [],
 /*检查数据合法性(从数据库获取字段属性进行验证) */
 "CheckData": false,
 /*列分隔符*/
 "Separator": "\t",
 /*表头的行数*/
 "HeaderRowsNum": 1
 }
]

读取代码 : 注意 ConfigurationManager.AppSettings["frpage"] 和 ConfigurationManager.AppSettings["fr"] 需要自己配置好

//读取配置文件信息
 List<dynamic> dt = JsonConvert.DeserializeObject<List<dynamic>>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config\\ImportTxt.json")));
 LogUtil.Info("开始读取txt数据,读取配置:" + dt.Count + "条");
 if (dt.Count == 0)
 {
 return;
 }


 List<Task> li = new List<Task>();
 foreach (dynamic row in dt)
 {
 LogUtil.Info("开始处理数据:" + JsonConvert.SerializeObject(row));
 li.Add(ProcessRow(row));

 }
 Task.WaitAll(li.ToArray());
 LogUtil.Info("数据读取完毕");
public async Task ProcessRow(dynamic row)
 {
 await Task.Run(() =>
 {
  AutoResetEvent AE = new AutoResetEvent(false);
  DataTable Data = null;
  string error = "", ConnStr, TableName, ExecBeforeSql, ExecAfterSql;
  Boolean IsCheck = Convert.ToBoolean(row["CheckData"]);
  TableName = Convert.ToString(row.TableName);
  ConnStr = Convert.ToString(row.ConnStr);
  ExecBeforeSql = Convert.ToString(row.ExecBeforeSql);
  ExecAfterSql = Convert.ToString(row.ExecAfterSql);
  int HeaderRowsNum = Convert.ToInt32(row.HeaderRowsNum);
  string Separator = Convert.ToString(row.Separator);

  Dictionary<string, string> dic = new Dictionary<string, string>();

  //文件达到多大时就分行读取
  int fr = 0;
  if (!int.TryParse(ConfigurationManager.AppSettings["fr"], out fr))
  {
  fr = 100;
  }
  fr = fr * 1024 * 1024;

  //分行读取一次读取多少
  int page = 0;
  if (!int.TryParse(ConfigurationManager.AppSettings["frpage"], out page))
  {
  page = 50000;
  }

  foreach (var dyn in row.Mapping)
  {
  dic.Add(Convert.ToString(dyn.TxtName), Convert.ToString(dyn.DBName));
  }


  List<string> regex = new List<string>();
  foreach (string item in row["FilterRegex"])
  {
  regex.Add(item);
  }
  string fpath = "", cpath = "";




  cpath = Convert.ToString(row["FilePath"]);
  string rootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tmp");
  if (!Directory.Exists(rootPath))
  {
  Directory.CreateDirectory(rootPath);
  }

  fpath = Path.Combine(rootPath, Path.GetFileName(cpath));
  File.Copy(cpath, fpath, true);
  LogUtil.Info("拷文件到本地已经完成.从本地读取数据操作");
  int threadCount = Environment.ProcessorCount * 3;

  FileInfo fi = new FileInfo(fpath);
  //如果文件大于100M就需要分批读取.一次50万条
  if (fi.Length > fr)
  {

  long sumCount = 0;
  StreamReader sr = new StreamReader(fi.OpenRead());  
  int headRow = 0;
  string rowstr = "";

  List<Thread> li_th = new List<Thread>();
  bool last = false;
  int ij = 0;
  LogUtil.Info("生成StreamReader成功 ");
  #region 逐行读取
  
  
  while (sr.Peek() > -1)
  {
  rowstr = sr.ReadLine();
  #region 将行数据写入DataTable
  if (headRow < HeaderRowsNum)
  {
  Data = new DataTable();
  foreach (string scol in rowstr.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries))
  {
   Data.Columns.Add(scol.Trim(), typeof(string));
  }
  headRow++;
  continue;
  }
  else
  { //行数据
  if (headRow > 1)
  {
   for (int i = 1; i < headRow && sr.Peek() > -1; i++)
   {
   rowstr += " " + sr.ReadLine();
   }
  }
  Data.Rows.Add(rowstr.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries));
  if (Data.Rows.Count < page && sr.Peek() > -1)
  {
   continue;
  }
  }
  last = (sr.Peek() == -1);
  #endregion

  sumCount += Data.Rows.Count;

  ProcessPath(Data, page, sr, ref ij, TableName, ExecBeforeSql, ExecAfterSql, dic, IsCheck, li_th);
   

  #region 检查线程等待
  if ((ij > 0 && (ij % threadCount) == 0) || last)
  {
  LogUtil.Info("完成一批次当前共写数据: " + sumCount);
  while (true)
  {
   bool isok = true;
   foreach (var item in li_th)
   {
   if (item.IsAlive)
   {
   isok = false;
   Application.DoEvents();
   Thread.Sleep(1000);
   }
   }
   if (isok)
   {
   li_th.Clear();
   break;
   }
  }

  //最后一页要等所有的执行完才能执行
  if (sr.Peek() == -1)
  {
   WriteTODB(TableName, Data, ExecBeforeSql, ExecAfterSql, dic, false, true);
   LogUtil.Info("最后一次写入完成");
  }
  LogUtil.Info(" 线程退出开始新的循环...");
  }
  Data.Clear();
  #endregion
  }
  sr.Dispose();
  #endregion
  }
  else
  {
  using (SQLServerDBHelper sdb = new SQLServerDBHelper())
  {
  sdb.OpenConnection();
  #region 一次性读取处理
  Data = LoadDataTableFromTxt(fpath, ref error, Separator, HeaderRowsNum, regex, IsCheck, dic, TableName);
  if (IsCheck)
  {
  DataRow[] rows = Data.Select("ErrorMsg is not null");
  if (rows.Length > 0)
  {
   LogUtil.Info($"读取{TableName} 数据出错 : {JsonConvert.SerializeObject(rows)}");
   return;
  }
  }

  LogUtil.Info($"读取{TableName} 的txt数据完成.共读取数据:{Data.Rows.Count}条");
  if (Data.Rows.Count == 0 || !string.IsNullOrWhiteSpace(error))
  {
  if (!string.IsNullOrWhiteSpace(error))
  {
   LogUtil.Info("读取数据出错,地址:" + Convert.ToString(row["FilePath"]) + " \r\n 错误:" + error);
  }
  return;
  }
  sdb.BgeinTransaction();
  try
  {
  WriteTODB(TableName, Data, ExecBeforeSql, ExecAfterSql, dic, sdb: sdb);
  sdb.CommitTransaction();
  LogUtil.Info(TableName + "数据更新完毕 !!");
  }
  catch (Exception ex)
  {

  LogUtil.Info(TableName + " 更新数据出错,错误:" + ex.Message + " \r\n 堆栈:" + ex.StackTrace);
  sdb.RollbackTransaction();
  }
  #endregion

  }



  }

  GC.Collect();
 });

 }

 private void ProcessPath(DataTable Data, int page, StreamReader sr, ref int ij, string TableName, string ExecBeforeSql, string ExecAfterSql, Dictionary<string, string> dic, bool IsCheck, List<Thread> li_th)
 {
 int threadCount = Environment.ProcessorCount * 4;

 string error = "";
 PoolModel p = new PoolModel { TableName = TableName, ExecBeforeSql = ExecBeforeSql, ExecAfterSql = ExecAfterSql, dic = dic };
 p.Data = Data.Copy();
 if (IsCheck)
 {
 using (SQLServerDBHelper sdb = new SQLServerDBHelper())
 {
  error = CheckData(Data, TableName, dic, sdb);
 }
 DataRow[] rows = Data.Select("ErrorMsg is not null");
 if (rows.Length > 0 || !string.IsNullOrWhiteSpace(error))
 {
  LogUtil.Info($"读取{TableName} 数据出错 : {JsonConvert.SerializeObject(rows)}\r\n错误: " + error);
  return;
 }
 }

 ij++;
 if (ij == 1)
 {

 WriteTODB(p.TableName, p.Data, p.ExecBeforeSql, p.ExecAfterSql, p.dic, true, false);
 LogUtil.Info("首次写入完成");
 }

 else if (sr.Peek() > -1)
 {

 Thread t = new Thread(d =>
 {

  PoolModel c = d as PoolModel;
  try
  {
  WriteTODB(c.TableName, c.Data, c.ExecBeforeSql, c.ExecAfterSql, c.dic, false, false);  
  }
  catch (ThreadAbortException)
  {
  LogUtil.Error("线程退出.................");
  }
  catch (Exception ex)
  {

  LogUtil.Error(c.TableName + "写入数据失败:" + ex.Message + "\r\n堆栈:" + ex.StackTrace + "\r\n 数据: " + JsonConvert.SerializeObject(c.Data));
  ExitApp();
  return;
  }

 });
 t.IsBackground = true;
 t.Start(p);
 li_th.Add(t);
 }

 }

 public void ExitApp()
 {
 Application.Exit();
 }

 public void WriteTODB(string TableName, DataTable Data, string ExecBeforeSql, string ExecAfterSql, Dictionary<string, string> dic, bool first = true, bool last = true, SQLServerDBHelper sdb = null)
 {
 bool have = false;
 if (sdb == null)
 {
 sdb = new SQLServerDBHelper();
 have = true;
 }

 if (first && !string.IsNullOrWhiteSpace(ExecBeforeSql))
 {
 LogUtil.Info(TableName + "执行前Sql :" + ExecBeforeSql);
 sdb.ExecuteNonQuery(ExecBeforeSql);
 }
 sdb.BulkCopy(Data, TableName, dic);
 if (last && !string.IsNullOrWhiteSpace(ExecAfterSql))
 {
 LogUtil.Info(TableName + "执行后Sql :" + ExecAfterSql);
 sdb.ExecuteNonQuery(ExecAfterSql);
 }
 LogUtil.Info(TableName + "本次执行完成 ");
 if (have)
 {
 sdb.Dispose();
 }
 }


 public string CheckData(DataTable dt, string dbTableName, Dictionary<string, string> dic, SQLServerDBHelper sdb)
 {
 if (string.IsNullOrWhiteSpace(dbTableName))
 {
 return "表名不能为空!";
 }
 if (dic.Count == 0)
 {
 return "映射关系数据不存在!";

 }

 List<string> errorMsg = new List<string>();
 List<string> Cols = new List<string>();
 dic.Foreach(c =>
 {
 if (!dt.Columns.Contains(c.Key))
 {
  errorMsg.Add(c.Key);
 }
 Cols.Add(c.Key);
 });

 if (errorMsg.Count > 0)
 {
 return "数据列不完整,请与映射表的数据列数量保持一致!列:" + string.Join(",", errorMsg);
 }


 //如果行数据有错误信息则添加到这一列的值里
 dt.Columns.Add(new DataColumn("ErrorMsg", typeof(string)) { DefaultValue = "" });
 string sql = @"--获取SqlServer中表结构
 SELECT syscolumns.name as ColName,systypes.name as DBType,syscolumns.isnullable,
 syscolumns.length
 FROM syscolumns, systypes
 WHERE syscolumns.xusertype = systypes.xusertype
 AND syscolumns.id = object_id(@tb) ; ";
 DataSet ds = sdb.GetDataSet(sql, new SqlParameter[] { new SqlParameter("@tb", dbTableName) });
 EnumerableRowCollection<DataRow> TableDef = ds.Tables[0].AsEnumerable();

 // string colName="";
 Object obj_val;

 //将表结构数据重组成字典.
 var dic_Def = TableDef.ToDictionary(c => Convert.ToString(c["ColName"]), d =>
 {
 string DBType = "";
 string old = Convert.ToString(d["DBType"]).ToUpper();
 DBType = GetCSharpType(old);
 return new { ColName = Convert.ToString(d["ColName"]), DBType = DBType, SqlType = old, IsNullble = Convert.ToBoolean(d["isnullable"]), Length = Convert.ToInt32(d["length"]) };
 });

 DateTime now = DateTime.Now;
 foreach (DataRow row in dt.Rows)
 {
 errorMsg.Clear();
 foreach (string colName in Cols)
 {
  if (dic.ContainsKey(colName))
  {
  if (!dic_Def.ContainsKey(dic[colName]))
  {
  return "Excel列名:" + colName + " 映射数据表字段:" + dic[colName] + "在当前数据表中不存在!";
  }
  //去掉数据两边的空格
  row[colName] = obj_val = Convert.ToString(row[colName]).Trim();
  var info = dic_Def[dic[colName]];
  //是否是DBNULL
  if (obj_val.Equals(DBNull.Value))
  {
  if (!info.IsNullble)
  {
  errorMsg.Add("列" + colName + "不能为空!");

  }
  }
  else
  {
  if (info.DBType == "String")
  {
  //time类型不用验证长度(日期的 时间部分如 17:12:30.0000)
  if (info.SqlType == "TIME")
  {
   if (!DateTime.TryParse(now.ToString("yyyy-MM-dd") + " " + obj_val.ToString(), out now))
   {
   errorMsg.Add("列" + colName + "填写的数据无效应为日期的时间部分如:17:30:12");

   }
  }
  else if (Convert.ToString(obj_val).Length > info.Length)
  {
   errorMsg.Add("列" + colName + "长度超过配置长度:" + info.Length);
  }
  }
  else
  {
  Type t = Type.GetType("System." + info.DBType);
  try
  { //如果数字中有千分位在这一步可以处理掉重新给这个列赋上正确的数值  
   row[colName] = Convert.ChangeType(obj_val, t); ;
  }
  catch (Exception ex)
  {
   errorMsg.Add("列" + colName + "填写的数据" + obj_val + "无效应为" + info.SqlType + "类型.");
  }

  }

  }
  }

 }
 row["ErrorMsg"] = string.Join(" || ", errorMsg);
 }

 return "";
 }

 /// <summary>
 /// wm 2018年11月28日13:37
 /// 将数据库常用类型转为C# 中的类名(.Net的类型名)
 /// </summary>
 /// <param name="old"></param>
 /// <returns></returns>
 private string GetCSharpType(string old)
 {
 string DBType = "";
 switch (old)
 {
 case "INT":
 case "BIGINT":
 case "SMALLINT":
  DBType = "Int32";
  break;
 case "DECIMAL":
 case "FLOAT":
 case "NUMERIC":
  DBType = "Decimal";
  break;
 case "BIT":
  DBType = "Boolean";
  break;
 case "TEXT":
 case "CHAR":
 case "NCHAR":
 case "VARCHAR":
 case "NVARCHAR":
 case "TIME":
  DBType = "String";
  break;
 case "DATE":
 case "DATETIME":
  DBType = "DateTime";
  break;
 default:
  throw new Exception("GetCSharpType数据类型" + DBType + "无法识别!");

 }

 return DBType;
 }




 public class PoolModel
 {
 public string TableName { get; set; }
 public DataTable Data { get; set; }
 public string ExecBeforeSql { get; set; }
 public string ExecAfterSql { get; set; }
 public Dictionary<string, string> dic { get; set; }

 }
/// <summary>
 /// wm 2018年11月28日13:32
 /// 获取Txt数据并对数据进行校验返回一个带有ErrorMsg列的DataTable,如果数据校验失败则该字段存放失败的原因
 /// 注意:在使用该方法前需要数据表应该已经存在
 /// </summary>
 /// <param name="isCheck">是否校验数据合法性(数据需要校验则会按传入的dbTableName获取数据库表的结构出来验证)</param>
 /// <param name="map">如果需要验证数据则此处需要传映射关系 key Excel列名,Value 数据库列名</param>
 /// <param name="dbTableName">验证数据合法性的表(即数据会插入到的表)</param>
 /// <param name="error">非数据验证上的异常返回</param>
 /// <param name="Regexs">用来过滤数据的正则</param>
 /// <param name="path">读取文件的路径</param>
 /// <param name="Separator">列分隔符</param>
 /// <param name="HeaderRowsNum">表头的行数</param>
 /// <returns>如果需求验证则返回一个带有ErrorMsg列的DataTable,如果数据校验失败则该字段存放失败的原因, 不需要验证则数据读取后直接返回DataTable</returns>
 public DataTable LoadDataTableFromTxt(string path, ref string error, string Separator, int HeaderRowsNum, List<string> Regexs = null, bool isCheck = false, Dictionary<string, string> map = null, string dbTableName = "", SQLServerDBHelper sdb = null)
 {
 DataTable dt = new DataTable();
 error = "";
 if (isCheck && (map == null || map.Count == 0 || string.IsNullOrWhiteSpace(dbTableName)))
 {
 error = "参数标明需要对表格数据进行校验,但没有指定映射表集合或数据表名.";
 return dt;
 }
 string txts = File.ReadAllText(path);
 #region 把读出来的方便数据转成DataTable

 Regexs?.ForEach(c =>
 {
 txts = new Regex(c).Replace(txts, "");
 });
 ////替换掉多表的正则
 //Regex mu_re = new Regex(@"\+[-+]{4,}\s+\+[-+\s|\w./]{4,}\+"); //FTP new Regex(@"\+[-+]{4,}\s+\+[-+\s|\w./]{4,}\+"); //原来以-分隔的 new Regex(@"-{5,}(\s)+-{5,}\s+\|.+(\s)?\|.+(\s)?\|-{5,}");
 ////去掉所有横线
 //Regex mu_r = new Regex(@"[+-]{4,}"); //FTP new Regex(@"[+-]{4,}"); //原 new Regex(@"(\|-{5,})|(-{5,})"); 
 //string s1 = mu_re.Replace(txts, "");
 //string s2 = mu_r.Replace(s1, "");
 // string[] tts = s2.Split(new string[] { "\r\n" }, StringSplitOptions.None);
 string[] tts = txts.Split(new string[] { "\r\n" }, StringSplitOptions.None);
 string[] vals;
 string s1;
 //生成表头默认第一行时表头直到遇到第一个只有一个|的内容为止(有几行表头,下面的内容就会有几行)
 int headerNum = -1;//记录表头有几列

 DataRow dr;
 //处理col重复的问题,如果有重复按第几个来命名 比如 A1 A2 
 Dictionary<string, int> col_Rep = new Dictionary<string, int>();
 string colName = "";
 bool isre = false;//记录当前是否有重复列
 int empty_HeaderRow = 0;
 for (int i = 0; i < tts.Length; i++)
 {
 s1 = tts[i];

 //还未获取出表头
 if (headerNum < HeaderRowsNum)
 {
  vals = s1.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
  foreach (string col in vals)
  {
  colName = col.Trim();

  if (col_Rep.Keys.Contains(colName))
  {
  col_Rep[colName]++;
  isre = true;
  //重复列处理
  //colName += col_Rep[colName];
  continue;
  }
  else
  {
  col_Rep.Add(colName, 1);
  }
  dt.Columns.Add(colName, typeof(string));
  }
  headerNum = (i == (HeaderRowsNum - 1)) ? HeaderRowsNum : 0;
 }
 else
 {
  if (string.IsNullOrWhiteSpace(s1.Trim()) || string.IsNullOrWhiteSpace(s1.Replace(Separator, "")))
  {
  continue;
  }
  if (isre)
  {
  error = "列:" + string.Join(",", col_Rep.Where(c => c.Value > 1).Select(c => c.Key)) + "存在重复";
  return dt;
  }


  //多行时把多行的数据加在一起处理
  if (headerNum > 1)
  {
  for (int j = 1; j < headerNum && (i + j) < tts.Length; j++)
  {
  //数据第一行最后没有| 如果没数据则直接换行了所以这里补一个空格防止数据被当空数据移除了
  s1 += " " + tts[i + j];
  }
  }
  vals = s1.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
  dr = dt.NewRow();
  dr.ItemArray = vals;
  dt.Rows.Add(dr);
  //因为本次循环结束上面会去++ 所以这里只加headerNum-1次
  i += (headerNum - 1);
 }

 }
 #endregion

 if (isCheck)
 {
 //dt.Columns.Remove("Item");
 //dt.Columns["Item1"].ColumnName = "Item";
 //dt.Columns.RemoveAt(dt.Columns.Count - 2);
 error = CheckData(dt, dbTableName, map, sdb);
 }

 return dt;

 }

感谢你能够认真阅读完这篇文章,希望小编分享的“C#读取Txt大数据并更新到数据库的示例分析”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI