linqtocsv文件有不太好的地方就是:無法設置標題的行數,預設首行就是標題,這不是很尷尬嗎? 並不是所有的csv文件嚴格寫的首行是標題,下麵全是數據,我接受的任務就是讀取很多.csv報表數據,裡面就有很多前幾行是說明性內容,下麵才是標題和數據。為了更好的解決這個問題,自己寫吧... 本博客沒有照 ...
linqtocsv文件有不太好的地方就是:無法設置標題的行數,預設首行就是標題,這不是很尷尬嗎? 並不是所有的csv文件嚴格寫的首行是標題,下麵全是數據,我接受的任務就是讀取很多.csv報表數據,裡面就有很多前幾行是說明性內容,下麵才是標題和數據。為了更好的解決這個問題,自己寫吧...
本博客沒有照搬linqtocsv全部源碼,保留了主要功能,並對其優化,為我所用,哈哈...
下麵是主要代碼:
1-主文件CsvHelper:
這裡在獨自解析數據的時候,遇到了很多坑:
a-遇到數據含有分隔符的問題的解決辦法,代碼已經包含了
b-遇到瞭解析源文檔數據時,未指定字元編碼時,部分數據丟失導致csv文件個別行數據解析異常的問題,針對該問題,就是老老實實把讀取文件時加了字元編碼的參數進去,預設UTF-8。
using Microsoft.Extensions.Logging; using PaymentAccountAPI.Helper; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; namespace PaymentAccountAPI.CSV { public class CsvHelper { /// <summary> /// 日誌 /// </summary> private ILogger _Logger { get; set; } public CsvHelper(ILogger<CsvHelper> logger) { this._Logger = logger; } public List<T> Read<T>(string filePath, CsvFileDescription fileDescription) where T : class, new() { List<T> tList = new List<T>(50 * 10000); T t = null; int currentRawIndex = 1; if (File.Exists(filePath)) { using (StreamReader streamReader = new StreamReader(filePath, fileDescription.Encoding)) { Dictionary<int, FieldMapper> fieldMapperDic = FieldMapper.GetModelFieldMapper<T>().ToDictionary(m => m.CSVTitleIndex); string rawValue = null; string[] rawValueArray = null; PropertyInfo propertyInfo = null; string propertyValue = null; bool rawReadEnd = false; bool isExistSplitChart = false; do { rawValue = streamReader.ReadLine(); //標題行 if (currentRawIndex > fileDescription.TitleRawIndex) { if (!string.IsNullOrEmpty(rawValue)) { //替換字元串含有分隔符為{分隔符},最後再替換回來 if (rawValue.Contains("\"")) { isExistSplitChart = true; int yhBeginIndex = 0; int yhEndIndex = 0; string yhText = null; do { yhBeginIndex = StringHelper.GetIndexOfStr(rawValue, "\"", 1); yhEndIndex = StringHelper.GetIndexOfStr(rawValue, "\"", 2); yhText = rawValue.Substring(yhBeginIndex, (yhEndIndex - yhBeginIndex + 1)); string newYHText = yhText.Replace("\"", "").Replace(fileDescription.SeparatorChar.ToString(), "{分隔符}"); rawValue = rawValue.Replace(yhText, newYHText); } while (rawValue.Contains("\"")); } rawValueArray = rawValue.Split(fileDescription.SeparatorChar); t = new T(); foreach (var fieldMapper in fieldMapperDic) { propertyInfo = fieldMapper.Value.PropertyInfo; propertyValue = rawValueArray[fieldMapper.Key - 1]; if (!string.IsNullOrEmpty(propertyValue)) { try { if (isExistSplitChart && propertyValue.Contains("{分隔符}")) { propertyValue = propertyValue.Replace("{分隔符}", fileDescription.SeparatorChar.ToString()); } TypeHelper.SetPropertyValue(t, propertyInfo.Name, propertyValue); } catch (Exception e) { this._Logger.LogWarning(e, $"第{currentRawIndex + 1}行數據{propertyValue}轉換屬性{propertyInfo.Name}-{propertyInfo.PropertyType.Name}失敗!"); continue; } } } tList.Add(t); } else { rawReadEnd = true; } } currentRawIndex++; } while (rawReadEnd == false); } } return tList; } public void WriteFile<T>(string path, List<T> tList, CsvFileDescription fileDescription) where T : class, new() { if (!string.IsNullOrEmpty(path)) { string fileDirectoryPath = null; if (path.Contains("\\")) { fileDirectoryPath = path.Substring(0, path.LastIndexOf('\\')); } else { fileDirectoryPath = path.Substring(0, path.LastIndexOf('/')); } if (!Directory.Exists(fileDirectoryPath)) { Directory.CreateDirectory(fileDirectoryPath); } int dataCount = tList.Count; Dictionary<int, FieldMapper> fieldMapperDic = FieldMapper.GetModelFieldMapper<T>().ToDictionary(m => m.CSVTitleIndex); int titleCount = fieldMapperDic.Keys.Max(); string[] rawValueArray = new string[titleCount]; StringBuilder rawValueBuilder = new StringBuilder(); string rawValue = null; T t = null; PropertyInfo propertyInfo = null; int currentRawIndex = 1; int tIndex = 0; using (StreamWriter streamWriter = new StreamWriter(path, false, fileDescription.Encoding)) { do { try { rawValue = ""; #if DEBUG if (currentRawIndex % 10000 == 0) { this._Logger.LogInformation($"已寫入文件:{path},數據量:{currentRawIndex}"); } #endif if (currentRawIndex >= fileDescription.TitleRawIndex) { //清空數組數據 for (int i = 0; i < titleCount; i++) { rawValueArray[i] = ""; } if (currentRawIndex > fileDescription.TitleRawIndex) { t = tList[tIndex]; tIndex++; } foreach (var fieldMapperItem in fieldMapperDic) { //寫入標題行 if (currentRawIndex == fileDescription.TitleRawIndex) { rawValueArray[fieldMapperItem.Key - 1] = fieldMapperItem.Value.CSVTitle; } //真正的數據從標題行下一行開始寫 else { propertyInfo = fieldMapperItem.Value.PropertyInfo; object propertyValue = propertyInfo.GetValue(t); string formatValue = null; if (propertyValue != null) { if (propertyInfo.PropertyType is IFormattable && !string.IsNullOrEmpty(fieldMapperItem.Value.OutputFormat)) { formatValue = ((IFormattable)propertyValue).ToString(fieldMapperItem.Value.OutputFormat, null); } else { formatValue = propertyValue.ToString(); } //如果屬性值含有分隔符,則使用雙引號包裹 if (formatValue.Contains(fileDescription.SeparatorChar.ToString())) { formatValue = $"\"{formatValue}\""; } rawValueArray[fieldMapperItem.Key - 1] = formatValue; } } } rawValue = string.Join(fileDescription.SeparatorChar, rawValueArray); } rawValueBuilder.Append(rawValue + "\r\n"); } catch (Exception e) { this._Logger.LogWarning(e, $"(異常)Excel第{currentRawIndex}行,數據列表第{tIndex + 1}個數據寫入失敗!rawValue:{rawValue}"); throw; } currentRawIndex++; } while (tIndex < dataCount); streamWriter.Write(rawValueBuilder.ToString()); streamWriter.Close(); streamWriter.Dispose(); } } } } }
2-CSV映射類特性:
using System; namespace PaymentAccountAPI.CSV { /// <summary> /// Csv文件類特性標記 /// </summary> [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = false)] public class CsvColumnAttribute : System.Attribute { internal const int defaultTitleIndex = Int32.MaxValue; /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 標題位置(從1開始) /// </summary> public int TitleIndex { get; set; } /// <summary> /// 字元輸出格式(數字和日期類型需要) /// </summary> public string OutputFormat { get; set; } public CsvColumnAttribute() { Title = ""; TitleIndex = defaultTitleIndex; OutputFormat = ""; } public CsvColumnAttribute(string title, int titleIndex, string outputFormat) { Title = title; TitleIndex = titleIndex; OutputFormat = outputFormat; } } }
3-CSV文件描述信息類:
using System.Text; namespace PaymentAccountAPI.CSV { public class CsvFileDescription { public CsvFileDescription() : this(1) { } public CsvFileDescription(int titleRawIndex) : this(',', titleRawIndex, Encoding.UTF8) { } public CsvFileDescription(char separatorChar, int titleRawIndex, Encoding encoding) { this.SeparatorChar = separatorChar; this.TitleRawIndex = titleRawIndex; this.Encoding = encoding; } /// <summary> /// CSV文件字元編碼 /// </summary> public Encoding Encoding { get; set; } /// <summary> /// 分隔符(預設為(,),也可以是其他分隔符如(\t)) /// </summary> public char SeparatorChar { get; set; } /// <summary> /// 標題所在行位置(預設為1,沒有標題填0) /// </summary> public int TitleRawIndex { get; set; } } }
4-映射類獲取關係幫助類:
using System.Collections.Generic; using System.Linq; using System.Reflection; namespace PaymentAccountAPI.CSV { /// <summary> /// 欄位映射類 /// </summary> public class FieldMapper { /// <summary> /// 屬性信息 /// </summary> public PropertyInfo PropertyInfo { get; set; } /// <summary> /// 標題 /// </summary> public string CSVTitle { get; set; } /// <summary> /// 標題下標位置 /// </summary> public int CSVTitleIndex { get; set; } /// <summary> /// 字元輸出格式(數字和日期類型需要) /// </summary> public string OutputFormat { get; set; } public static List<FieldMapper> GetModelFieldMapper<T>() { List<FieldMapper> fieldMapperList = new List<FieldMapper>(100); List<PropertyInfo> tPropertyInfoList = typeof(T).GetProperties().ToList(); CsvColumnAttribute csvColumnAttribute = null; foreach (var tPropertyInfo in tPropertyInfoList) { csvColumnAttribute = (CsvColumnAttribute)tPropertyInfo.GetCustomAttribute(typeof(CsvColumnAttribute)); if (csvColumnAttribute != null) { fieldMapperList.Add(new FieldMapper { PropertyInfo = tPropertyInfo, CSVTitle = csvColumnAttribute.Title, CSVTitleIndex = csvColumnAttribute.TitleIndex, OutputFormat = csvColumnAttribute.OutputFormat }); } } return fieldMapperList; } } }
5-其他擴展類:
namespace PaymentAccountAPI.Helper { public class StringHelper { /// <summary> /// 獲取字元串中第strPosition個位置的str的下標 /// </summary> /// <param name="text"></param> /// <param name="str"></param> /// <param name="strPosition"></param> /// <returns></returns> public static int GetIndexOfStr(string text, string str, int strPosition) { int strIndex = -1; int currentPosition = 0; if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(str) && strPosition >= 1) { do { currentPosition++; if (strIndex == -1) { strIndex = text.IndexOf(str); } else { strIndex = text.IndexOf(str, strIndex + 1); } } while (currentPosition < strPosition); } return strIndex; } } }
最後就是將CsvHelper註入到單例中,就可以使用了...