LinqToExcel.Extend 源码分析
废话不多说,我们直接来分析源码,首先我们查看目录结构
目录结构.png目录结构功能
- Extend 通用扩展方法
- Parameter 公共实体类
- Parser 解析器
- Validate 验证工具集
展开目录结构,我们能够更加请详细的分析出每个目录所完成的功能模块。
这里主要讲解工具集中最重要的一个模块Validate
要设计,我们就一定要知道自己想怎么做。
如果我对外提供接口调用,怎么样的方式是最方便,让人容易理解的,我就是朝着这个方向做的。
我希望的结果是
实例化验证对象,参数是验证文件的路径
调用验证方法,可以区分工作表验证,可以选择添加或不添加逻辑验证
验证成功或失败都返回一个对象,如果验证失败,返回的对象中要包含出错的信息(尽可能细化)
基于上述的设计理念
我定义了三个对象
RowValidate 行验证
WorkSheetValidate 工作表验证
WorkBookValidate 工作簿验证
RowValidate 行验证
RowValidate对象执行的调用方是WorkSheetValidate
Validate<T>执行返回值为 得到当前行的的出错信息集合
/// <summary>
/// 行验证
/// </summary>
public class RowValidate
{
public static string GetCellStation(int rowIndex, int columnIndex)
{
int i = columnIndex % 26;
string cellRef = Convert.ToChar(65 + i).ToString() + (rowIndex + 1);
return cellRef;
}
public static List<ErrCell> Validate<T>(int rowIndex, List<string> colNames, List<int> colIndexs, List<string> rowCellValues)
{
List<ErrCell> errCells = new List<ErrCell>();
T singleT = Activator.CreateInstance<T>();
foreach (PropertyInfo pi in singleT.GetType().GetProperties())
{
var propertyAttribute = (Attribute.GetCustomAttribute(pi, typeof(ExcelColumnAttribute)));
if (propertyAttribute == null)
{
continue;
}
var proName = ((ExcelColumnAttribute)propertyAttribute).ColumnName;
for (int colIndex = 0; colIndex < colNames.Count; colIndex++)
{
try
{
if (proName.Equals(colNames[colIndex], StringComparison.OrdinalIgnoreCase))
{
string fieldName = pi.PropertyType.GetUnderlyingType().Name;
string cellValue = rowCellValues[colIndex];
if (!String.IsNullOrWhiteSpace(cellValue))
{
//如果是日期类型,特殊判断
if (fieldName.Equals("DateTime"))
{
string data = "";
try
{
data = cellValue.ToDateTimeValue();
}
catch (Exception)
{
data = DateTime.Parse(cellValue).ToString();
}
}
cellValue.CastTo(pi.PropertyType);
}
}
}
catch (Exception ex)
{
errCells.Add(new ErrCell()
{
RowIndex = rowIndex,
ColumnIndex = colIndexs[colIndex],
Name = GetCellStation(rowIndex, colIndexs[colIndex]),
ErrMsg = ex.Message
});
}
}
}
return errCells;
}
}
WorkBookValidate 工作簿验证
WorkBookValidate是根的验证对象。我们首先看构造函数,参数为filePath,在构造函数中,我们做的操作是:实例化N个WorkSheetValidate对象。
定义索引器,这样可以通过外部调用WorkSheetValidate的验证方法
/// <summary>
/// 工作簿验证
/// </summary>
public class WorkBookValidate
{
public string FilePath { get; set; }
private List<WorkSheetValidate> _workSheetList = new List<WorkSheetValidate>();
public List<WorkSheetValidate> WorkSheetList
{
get { return _workSheetList; }
set { _workSheetList = value; }
}
public WorkSheetValidate this[string sheetName]
{
get
{
foreach (WorkSheetValidate sheetParameterContainer in _workSheetList)
{
if (sheetParameterContainer.SheetName.Equals(sheetName))
{
return sheetParameterContainer;
}
}
throw new Exception("工作表不存在");
}
}
public WorkSheetValidate this[int sheetIndex]
{
get
{
foreach (WorkSheetValidate sheetParameterContainer in _workSheetList)
{
if (sheetParameterContainer.SheetIndex.Equals(sheetIndex))
{
return sheetParameterContainer;
}
}
throw new Exception("工作表不存在");
}
}
/// <summary>
///
/// </summary>
/// <param name="filePath">路径</param>
public WorkBookValidate(string filePath)
{
FilePath = filePath;
var excel = new ExcelQueryFactory(filePath);
List<string> worksheetNames = excel.GetWorksheetNames().ToList();
int sheetIndex = 0;
foreach (var sheetName in worksheetNames)
{
WorkSheetList.Add(new WorkSheetValidate(filePath, sheetName, sheetIndex++));
}
}
}
WorkSheetValidate 工作表验证
这是这三个验证模块中最复杂的一个,代码就不贴全部的图了,主要讲解一下重要的地方。
首先也是构造函数,这个构造函数主要是给WorkBookVaidate调用
public WorkSheetValidate(string filePath, string sheetName, int sheetIndex)
{
FilePath = filePath;
SheetName = sheetName;
SheetIndex = sheetIndex;
TootarIndex = 0;
}
验证方法说明
这是一个泛型方法,方法逻辑很简单
首先验证数据有效性 ValidateParameter
如果返回的错误集合为空,验证逻辑有效性ValidateMatching
最后返回验证集合
public Verification StartValidate<T>(List<CellMatching<T>> rowValidates = null)
{
List<ErrCell> errCells = this.ValidateParameter<T>(TootarIndex);
if (!errCells.Any())
{
TootarIndex += 1;
errCells.AddRange(this.ValidateMatching<T>(rowValidates, TootarIndex));
}
Verification validate = new Verification();
if (errCells.Any())
{
validate = new Verification()
{
IfPass = false,
ErrCells = errCells
};
}
else
{
validate = new Verification()
{
IfPass = true,
ErrCells = errCells
};
}
return validate;
}
验证数据有效性
这个模块相对复杂,看不懂的小伙伴可以多看几遍理解消化吸收下。
首先调用LinqToExcel的WorksheetNoHeader方法获得除了标题的集合数据
然后得到当前标题行和Excel列的映射关系
调用GetErrCellByParameter方法进行验证
GetErrCellByParameter说明
得到所有列名称集合,得到所有列名称索引
遍历行数据,调用RowValidate的静态方法RowValidate.Validate<T>
传递的参数是,行索引,列名称集合,列索引集合,行数据集合
private List<ErrCell> GetErrCellByParameter<T>(List<RowNoHeader> rows, int startRowIndex)
{
List<string> colNames = _propertyCollection.Values.Select(u => u.ColName).ToList();
List<int> colIndexs = _propertyCollection.Values.Select(u => u.ColIndex).ToList();
List<ErrCell> errCells = new List<ErrCell>();
for (int rowIndex = startRowIndex; rowIndex < rows.Count; rowIndex++)
{
List<string> rowValues = rows[rowIndex].Where((u, index) => colIndexs.Any(p => p == index)).Select(u => u.ToString()).ToList();
errCells.AddRange(RowValidate.Validate<T>(rowIndex, colNames, colIndexs, rowValues));
}
return errCells;
}
private List<ErrCell> ValidateParameter<T>(int startRowIndex)
{
//第一步 得到集合
var excel = new ExcelQueryFactory(FilePath);
var rows = (from c in excel.WorksheetNoHeader(SheetIndex)
select c).ToList();
//第二步 获得标题行和Excel列的映射关系
方法体省略
//第二步 调用验证方法
return GetErrCellByParameter<T>(rows, startRowIndex);
}