LinqToExcel.Extend 源码分析

C#

浏览数:106

2019-8-30

AD:资源代下载服务

废话不多说,我们直接来分析源码,首先我们查看目录结构

目录结构.png

目录结构功能

  • Extend 通用扩展方法
  • Parameter 公共实体类
  • Parser 解析器
  • Validate 验证工具集

目录结构展开.png

展开目录结构,我们能够更加请详细的分析出每个目录所完成的功能模块。
这里主要讲解工具集中最重要的一个模块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);
        }

作者:HapplyFox