BatchFileProcessing(2)--实现之解析文件

本文介绍了一种批量处理用户上传的Excel、CSV和制表符分隔文件的方法,重点讲解了使用Aspose.Cells组件进行文件解析的过程,并通过多线程和SQLBulkCopy技术提升处理效率。

     前一篇写到BatchFileProcessing的流程设计,这篇总结下解析文件的实现。

     目前我的产品支持用户上传excel,csv,tab delimited等三种文件格式,鉴于office com组件性能太差,所以我们购买了aspose cells组件。下面我们以批量更新商品库存为例,阐述一下解析文件的实现。

     当我们从数据库读取到尚未处理的用户上传文件后,我们会将它下载到服务器磁盘上,然后开始解析。

  文件级别的检查下包括以下几项:

  (1)文件是否可以正常打开,如果不能正常打开,记录异常并通知客户。

  (2)文件中的关键列是否有缺失,如果有缺失,记录异常并通知客户。

  (3)文件中的列是否有重复,如果有重复,记录异常并通知客户。

  (4)文件中是否有未在模版中未定义的列,如果有未在模版中未定义的列,记录异常并通知客户。

     批量更新商品库存的业务检查下包括以下几项:

(1)商品编号是否提供,如果没有提供,记录异常到数据库。

(2)商品库存是否提供,如果没有提供,使用默认值零;如果提供了,验证是否在有效的范围内,比如0~999999,如果不合法,记录异常到数据库。

用 Aspose Cells组件读取数据:

 

  1  using  System;
  2  using  System.Collections.Generic;
  3  using  System.Linq;
  4  using  System.Text;
  5  using  Aspose.Cells;
  6 
  7  namespace  BatchFile.Jobs.FileParsers
  8  {
  9       public   class  AsposeCellParser : IExcelParser
 10      {
 11           private   string  m_fileName;
 12           private   string  m_fileExt;
 13           private  FileFormatType m_fileFormatType;
 14           private  Workbook m_workbook;
 15           private  Worksheet m_worksheet;
 16 
 17           public  AsposeCellParser( string  fileName,  string  fileExt)
 18          {
 19              m_fileName  =  fileName;
 20              m_fileExt  =  fileExt;
 21               if  (FileExtType.IsCSV(m_fileExt))
 22              {
 23                  m_fileFormatType  =  FileFormatType.CSV;
 24              }
 25               else   if  (FileExtType.IsExcel2007(m_fileExt))
 26              {
 27                  m_fileFormatType  =  FileFormatType.Excel2007Xlsx;
 28              }
 29               else   if  (FileExtType.IsExcel2003(m_fileExt))
 30              {
 31                  m_fileFormatType  =  FileFormatType.Excel2003;
 32              }
 33               else   if  (FileExtType.IsTabDelimited(m_fileExt))
 34              {
 35                  m_fileFormatType  =  FileFormatType.TabDelimited;
 36              }
 37               else
 38              {
 39                   throw   new  NotSupportedException( string .Format( " File extension({0}) cannot be supported. " , fileExt));
 40              }
 41          }
 42 
 43           #region  IExcelParser
 44 
 45           public   string  ExcelFile
 46          {
 47               get  {  return  m_fileName; }
 48          }
 49 
 50           public   int  RowCount
 51          {
 52               get  {  return  m_worksheet.Cells.MaxDataRow; }
 53          }
 54 
 55           public   int  ColumnCount
 56          {
 57               get  {  return  m_worksheet.Cells.MaxDataColumn; }
 58          }
 59 
 60           public   object  GetCellValue( int  row,  int  col)
 61          {
 62               return  m_worksheet.Cells[row, col].Value;
 63          }
 64 
 65           public   void  ActiveWorksheet( int  activeWorksheetIndex)
 66          {
 67               if  (activeWorksheetIndex  <   0   ||  activeWorksheetIndex  >=  m_workbook.Worksheets.Count)
 68              {
 69                   throw   new  ArgumentOutOfRangeException( " Worksheet index is out of range. " );
 70              }
 71              m_worksheet  =  m_workbook.Worksheets[activeWorksheetIndex];
 72          }
 73 
 74           public   void  ActiveWorksheet( string  activeWorksheetName)
 75          {
 76               if  ( string .IsNullOrWhiteSpace(activeWorksheetName))
 77              {
 78                   throw   new  ArgumentException( " Worksheet name cannot be null or empty. " );
 79              }
 80              m_worksheet  =  m_workbook.Worksheets[activeWorksheetName];
 81          }
 82 
 83           public   void  OpenFile()
 84          {
 85              m_workbook  =   new  Workbook();
 86              m_workbook.ConvertNumericData  =   false ;
 87              m_workbook.Open(m_fileName, m_fileFormatType);
 88              m_worksheet  =  m_workbook.Worksheets[ 0 ];
 89          }
 90 
 91           public   void  OpenFile( int  activeWorksheetIndex)
 92          {
 93              OpenFile();
 94              ActiveWorksheet(activeWorksheetIndex);
 95          }
 96 
 97           public   void  OpenFile( string  activeWorksheetName)
 98          {
 99              OpenFile();
100              ActiveWorksheet(activeWorksheetName);
101          }
102 
103           #endregion
104      }
105  }
 
文件检查与解析
  1    public   void  Process()
  2          {
  3              m_parser  =   new  AsposeCellParser(m_batchInventoryFile.FileName, m_fileExt);
  4 
  5               // open file
  6               try
  7              {
  8                  m_parser.OpenFile();
  9              }
 10               catch
 11              {
 12                  EmailUtil.Send(m_batchInventoryFile.UserEmail, Consts.Batch_Inventory_File_Can_Not_Open,  " File you uploaded cannot be open. " );
 13                   return ;
 14              }
 15 
 16               // resolve headers
 17              var dictMapping  =  ConfigManager.BatchFileConfig.BatchInventory.InventoryPropertyMappingDict;
 18              List < ExcelHeaderModel >  headerList  =   new  List < ExcelHeaderModel > ();
 19              List < string >  headerNameList  =   new  List < string > ();
 20               int  rowIndex  =   0 ;
 21               int  colIndex  =   0 ;
 22               for  (; colIndex  <  m_parser.ColumnCount; colIndex ++ )
 23              {
 24                   string  headerName  =  m_parser.GetCellValue(rowIndex, colIndex).ToString().Trim();
 25                  ExcelHeaderModel header  =   new  ExcelHeaderModel();
 26                  header.ColumnIndex  =  colIndex;
 27                  header.HeaderName  =  headerName;
 28                  header.IsDuplicated  =  headerNameList.Contains(headerName);
 29                  
 30                  headerList.Add(header);
 31                  headerNameList.Add(headerName);
 32              }
 33 
 34               // check missing key column or not
 35              List < string >  lostKeyList  =   new  List < string > ();
 36               foreach  (var key  in  dictMapping.Keys)
 37              {
 38                  var mapping  =  dictMapping[key];
 39                   if  (mapping.IsKey
 40                       &&   ! headerNameList.Contains(mapping.HeaderName))
 41                  {
 42                      lostKeyList.Add(mapping.HeaderName);
 43                  }
 44              }
 45               if  (lostKeyList.Count  >   0 )
 46              {
 47                   string  keys  =  lostKeyList[ 0 ];
 48                   for  ( int  i  =   1 ; i  <  lostKeyList.Count; i ++ )
 49                  {
 50                      keys  +=   string .Format( " ,{0} " , lostKeyList[i]);
 51                  }
 52 
 53                  EmailUtil.Send(m_batchInventoryFile.UserEmail,
 54                      Consts.Batch_Inventory_File_Missing_Key,
 55                       string .Format( " File you uploaded is missing key(s) {0}. " , keys));
 56                   return ;
 57              }
 58 
 59               // check exist duplicate columns or not
 60              var duplicateColumnList  =  headerList.FindAll(h  =>  h.IsDuplicated);
 61               if  (duplicateColumnList.Count  >   0 )
 62              {
 63                   string  duplicateColumns  =  duplicateColumnList[ 0 ].HeaderName;
 64                   for  ( int  i  =   1 ; i  <  duplicateColumnList.Count; i ++ )
 65                  {
 66                      duplicateColumns  +=   string .Format( " ,{0} " , duplicateColumnList[i].HeaderName);
 67                  }
 68                  EmailUtil.Send(m_batchInventoryFile.UserEmail,
 69                     Consts.Batch_Inventory_File_Having_Duplicated_Column,
 70                      string .Format( " File you uploaded has duplicated column(s) {0}. " , duplicateColumns));
 71                   return ;
 72              }
 73 
 74               // check exist undefined columns or not
 75              var undefinedColumnList  =  headerList.FindAll(h  =>   ! h.IsTemplateDefined);
 76               if  (undefinedColumnList.Count  >   0 )
 77              {
 78                   string  undefinedColumns  =  undefinedColumnList[ 0 ].HeaderName;
 79                   for  ( int  i  =   1 ; i  <  undefinedColumnList.Count; i ++ )
 80                  {
 81                      undefinedColumns  +=   string .Format( " ,{0} " , undefinedColumnList[i].HeaderName);
 82                  }
 83                  EmailUtil.Send(m_batchInventoryFile.UserEmail,
 84                     Consts.Batch_Inventory_File_Having_Undefined_Column,
 85                      string .Format( " File you uploaded has undefined column(s) {0}. " , undefinedColumns));
 86                   return ;
 87              }
 88 
 89               // mapping header according to configuration
 90               foreach  (var header  in  headerList)
 91              {
 92                   if  (dictMapping.Keys.Contains(header.HeaderName))
 93                  {
 94                      header.IsTemplateDefined  =   true ;
 95                      var mapping  =  dictMapping[header.HeaderName];
 96                      header.IsKey  =  mapping.IsKey;
 97                      header.IsTransactional  =  mapping.IsTransactional;
 98                      header.PropertyName  =  mapping.PropertyName;
 99                      header.DataType  =  mapping.DataType;
100                      header.DefaultValue  =  mapping.DefaultValue;
101                      header.ColumnName  =  mapping.ColumnName;
102                      header.IsSSBNode  =  mapping.IsSSBNode;
103                  }
104              }
105 
106               // check and extract business data
107               if  ( ! Directory.Exists(ConfigManager.BatchFileConfig.BatchInventory.File2Dir))
108              {
109                  Directory.CreateDirectory(ConfigManager.BatchFileConfig.BatchInventory.File2Dir);
110              }
111              var partitionList  =  Partitioner.Create( 1 , m_parser.RowCount  +   1 );
112              Parallel.ForEach(partitionList, (p, loopState)  =>
113                  {
114                      DataTable dt  =  CreateDataTable(headerList);
115                       for  ( int  i  =  p.Item1; i  <  p.Item2; i ++ )
116                      {
117                          DataRow dr  =  dt.NewRow();
118                          XElement xNode  =   new  XElement(ConfigManager.BatchFileConfig.BatchInventory.RootPropertyName);
119                          List < string >  rowErrorList  =   new  List < string > ();
120                         
121                           foreach  (var header  in  headerList)
122                          {
123                               object  value  =   null ;
124                               string  strValue  =   string .Empty;
125                               if  (header.IsKey  ||  header.IsTransactional  ||  header.IsSSBNode)
126                              {
127                                  value  =  m_parser.GetCellValue(i, header.ColumnIndex);
128                                  strValue  =  value.ToString().Trim();
129                                   if  ( string .IsNullOrEmpty(strValue))
130                                  {
131                                      strValue  =  header.DefaultValue;
132                                  }
133                                   // find property's validator and perform validation
134                                   if  (header.IsTemplateDefined)
135                                  {
136                                      var property  =  dictMapping[header.HeaderName];
137                                       if  ( ! PropertyValidator.Validate(property.Validator, strValue))
138                                      {
139                                          rowErrorList.Add( string .Format(property.Validator.Tips, property.PropertyName));
140                                      }
141                                  }
142                              }
143                               if  (header.IsTransactional)
144                              {
145                                  dr[header.ColumnName]  =  value;
146                              }
147                               if  (header.IsSSBNode)
148                              {
149                                  xNode.Add( new  XElement(header.PropertyName,  new  XCData(strValue)));
150                              }
151                          }
152 
153                          dr[ " BatchFileID " =  m_batchInventoryFile.TransactionNumber;
154                          dr[ " RowIndex " =  i.ToString();
155                           if  (rowErrorList.Count  ==   0 )
156                          {
157                               string  fileName  =  i.ToString()  +   " .xml " ;
158                               string  filePath  =  Path.Combine(ConfigManager.BatchFileConfig.BatchInventory.File2Dir, fileName);
159                              File.WriteAllText(filePath, xNode.ToString());
160                              dr[ " FileName " =  fileName;
161                              dr[ " CheckResult " =  Consts.Success;
162                          }
163                           else
164                          {
165                              dr[ " CheckResult " =  Consts.Failed;
166                              XElement xNodeCheckMemo  =   new  XElement( " CheckMemoList " , from error  in  rowErrorList
167                                                                                  select  new  XElement( " CheckMemo " , error));
168                              dr[ " CheckMemo " = xNodeCheckMemo.ToString();
169                          }
170                          dr[ " HasCheck " =  Consts.Yes;
171                          dt.Rows.Add(dr);
172 
173                           if  (dt.Rows.Count  ==  ConfigManager.BatchFileConfig.BatchInventory.BatchSize)
174                          {
175                               // write to DB
176                              SQLHelper.BulkCopy(dt);
177                              dt.Rows.Clear();
178                          }
179                      }
180 
181                       // process the last batch
182                       if  (dt.Rows.Count  >   0 )
183                      {
184                           // write to DB
185                          SQLHelper.BulkCopy(dt);
186                          dt.Rows.Clear();
187                      }
188                  });
189          }
190 
191           private  DataTable CreateDataTable(List < ExcelHeaderModel >  headerList)
192          {
193              DataTable dt  =   new  DataTable(ConfigManager.BatchFileConfig.BatchInventory.DataTableName);
194               foreach  (var header  in  headerList)
195              {
196                   if  (header.IsTransactional)
197                  {
198                      dt.Columns.Add(header.ColumnName, Type.GetType(header.DataType));
199                  }
200              }
201              dt.Columns.Add( " BatchFileID " typeof ( int ));
202              dt.Columns.Add( " RowIndex " typeof ( int ));
203              dt.Columns.Add( " FileName " typeof ( string ));
204              dt.Columns.Add( " HasCheck " typeof ( string ));
205              dt.Columns.Add( " CheckResult " typeof ( string ));
206              dt.Columns.Add( " CheckMemo " typeof ( string ));
207 
208               return  dt;
209          }
 
为了提高文件解析的性能使用了多线程以及SqlBulkCopy等技术。下面是SQLHelper的定义:
 1  public   static   class  SQLHelper
 2      {
 3           private   static   string  m_connectionString  =  ConfigurationManager.ConnectionStrings[ " MSSQL " ].ConnectionString;
 4           public   static   string  ConnectionString
 5          {
 6               get
 7              {
 8                   return  m_connectionString;
 9              }
10          }
11 
12           public   static   void  BulkCopy(DataTable dt,  string  destTableName, List < SqlBulkCopyColumnMapping >  columnMappingList,  int  timeoutSeconds  =   1200 )
13          {
14               using  (SqlConnection conn  =   new  SqlConnection(m_connectionString))
15              {
16                  conn.Open();
17                  SqlTransaction trans  =  conn.BeginTransaction(IsolationLevel.ReadCommitted,  " Bulk copy batch data " );
18                  SqlBulkCopy b  =   new  SqlBulkCopy(conn, SqlBulkCopyOptions.Default, trans);
19                  b.BulkCopyTimeout  =  timeoutSeconds;
20                  b.DestinationTableName  =  destTableName;
21                   if  (columnMappingList  !=   null )
22                  {
23                       foreach  (var colMapping  in  columnMappingList)
24                      {
25                          b.ColumnMappings.Add(colMapping);
26                      }
27                  }
28                  b.WriteToServer(dt);
29                  trans.Commit();
30                  b.Close();
31                  conn.Close();
32              }
33          }
34 
35           public   static   void  BulkCopy(DataTable dt)
36          {
37              List < SqlBulkCopyColumnMapping >  columnMappingList  =   new  List < SqlBulkCopyColumnMapping > ();
38               foreach  (DataColumn col  in  dt.Columns)
39              {
40                  columnMappingList.Add( new  SqlBulkCopyColumnMapping
41                  {
42                      SourceColumn  =  col.ColumnName,
43                      DestinationColumn  =  col.ColumnName
44                  });
45              }
46              BulkCopy(dt, dt.TableName, columnMappingList);
47          }
48      }

 

AI智能图表创作平台,轻松对话绘图 Next AI Draw.io 是一款融合大语言模型与 draw.io 的创新型图表绘制平台。无需掌握复杂的绘图规则,只需通过自然语言输入,即可完成图表构建、修改与增强,帮助开发者和可视化创作者大幅提升效率。无论你是想绘制 AWS 架构图、GCP 拓扑,还是一个带有动画连接器的系统结构图,这款工具都能通过智能对话快速呈现。 核心亮点 LLM驱动的图表构建 通过 Chat 接口与 AI 对话,快速生成符合语义的图表,轻松支持 draw.io XML 格式解析。 图像识别与复制增强 上传一张已有图表或架构草图,AI 自动识别结构并重建图表,可进一步优化样式或内容。 图表版本管理 内置图表历史记录系统,支持版本切换与回滚,便于团队协作与修改回溯。 交互式绘图对话体验 内置对话界面,可边聊边画图,所见即所得,轻松优化图表结构与排版。 多云架构模板一键生成 支持 AWS、GCP、Azure 架构图自动生成,适配图标库,适合开发、运维、架构师使用。 GCP架构图 动画连接器 支持为图表元素添加动态连接器,提升图表交互性与演示感。 技术架构与支持 Next.js:提供稳定高性能的前端体验 Vercel AI SDK:整合流式对话与多模型支持 react-drawio:实现图表编辑与可视化渲染 多模型接入:支持 OpenAI、Anthropic、Google、Azure、DeepSeek、Ollama 等主流 AI API claude-sonnet-4-5 专项训练:在 AWS 架构图任务上表现优异
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值