using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using NewLife.Data;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Serialization;
namespace NewLife.IO;
/// <summary>Csv文件轻é‡çº§æ•°æ®åº“</summary>
/// <remarks>
/// 文档 https://newlifex.com/core/csv_db
/// é€‚ç”¨äºŽå¤§é‡æ•°æ®éœ€è¦å¿«é€Ÿè¿½åŠ ã€å¿«é€ŸæŸ¥æ‰¾ï¼Œå¾ˆå°‘ä¿®æ”¹å’Œåˆ é™¤çš„åœºæ™¯ã€‚
/// 在桌é¢å®¢æˆ·ç«¯ä¸ï¼Œå…³ç³»åž‹æ•°æ®åº“SQLiteå¾ˆå®¹æ˜“å› éžæ³•关机而æŸå,本数æ®åº“能跳过æŸå行,自动æ¢å¤ã€‚
///
/// ä¸é—´æ’å…¥å’Œåˆ é™¤æ—¶ï¼Œéœ€è¦ç§»åŠ¨å°¾éƒ¨æ•°æ®ï¼Œæ€§èƒ½è¾ƒå·®ã€‚
///
/// æœ¬è®¾è®¡ä¸æ”¯æŒçº¿ç¨‹å®‰å…¨ï¼ŒåŠ¡å¿…ç¡®ä¿å•线程æ“作。
/// </remarks>
/// <typeparam name="T">实体类型</typeparam>
public class CsvDb<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T> : DisposeBase where T : new()
{
#region 陿€ç¼“å˜ï¼ˆå射开销优化)
// åªå射一次,é™ä½Žè¯»å†™é¢‘çŽ‡è¾ƒé«˜åœºæ™¯çš„æˆæœ¬
// 使用 GetProperties(true) èµ° DefaultReflect 缓å˜ï¼ŒåŒæ—¶è¿‡æ»¤ XmlIgnore/IgnoreDataMember ç‰ç‰¹æ€§
private static readonly IList<PropertyInfo> _properties = typeof(T).GetProperties(true);
// 统一使用åºåˆ—化å(å¯èƒ½æ¥è‡ªç‰¹æ€§ï¼‰ï¼Œä¿®å¤åŽŸå…ˆå¤´éƒ¨å†™å…¥ä½¿ç”¨å±žæ€§å导致与读å–ä¸ä¸€è‡´çš„缺陷
private static readonly String[] _propertyNames = _properties.Select(SerialHelper.GetName).ToArray();
// 属性ååˆ°ç´¢å¼•çš„æ˜ å°„ï¼Œç”¨äºŽå¿«é€ŸæŸ¥æ‰¾
private static readonly Dictionary<String, Int32> _propertyIndexes = BuildPropertyIndexes();
private static Dictionary<String, Int32> BuildPropertyIndexes()
{
var dic = new Dictionary<String, Int32>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < _propertyNames.Length; i++)
{
dic[_propertyNames[i]] = i;
}
return dic;
}
#endregion
#region 属性
/// <summary>文件å。Csvæ•°æ®æ–‡ä»¶è·¯å¾„</summary>
public String? FileName { get; set; }
/// <summary>文件编ç 。默认UTF8</summary>
public Encoding Encoding { get; set; } = Encoding.UTF8;
/// <summary>实体比较器。用于判æ–两个实体是å¦ç›¸ç‰</summary>
public IEqualityComparer<T> Comparer { get; set; }
#endregion
#region æž„é€
/// <summary>实例化Csv文件数æ®åº“</summary>
public CsvDb() => Comparer = EqualityComparer<T>.Default;
/// <summary>使用自定义比较器实例化Csv文件数æ®åº“</summary>
/// <param name="comparer">自定义比较器委托,用于判æ–两实体是å¦ç›¸ç‰</param>
public CsvDb(Func<T?, T?, Boolean> comparer) => Comparer = new MyComparer(comparer);
/// <summary>é”€æ¯æ—¶è‡ªåЍæäº¤æœªæäº¤äº‹åŠ¡</summary>
/// <param name="disposing"></param>
protected override void Dispose(Boolean disposing)
{
base.Dispose(disposing);
// è‹¥ä»æœ‰ç¼“å˜äº‹åŠ¡ï¼Œè‡ªåŠ¨æäº¤ï¼ˆä¿æŒåކå²å…¼å®¹è¡Œä¸ºï¼‰
Commit();
}
#endregion
#region 基础方法(事务)
private List<T>? _cache;
/// <summary>å¼€å¯äº‹åŠ¡ï¼Œä¾¿äºŽæ‰¹é‡å¤„ç†æ•°æ®ã€‚读å–全部数æ®è¿›å…¥å†…å˜ï¼ŒåŽç» Add/Remove/Set ä»…æ“作缓å˜ã€‚</summary>
public void BeginTransaction() => _cache ??= FindAll().ToList();
/// <summary>æäº¤äº‹åŠ¡ï¼ŒæŠŠç¼“å˜æ•°æ®å†™å…¥ç£ç›˜ï¼ˆè¦†ç›–原文件)。æäº¤åŽæ¸…空缓å˜ã€‚</summary>
public void Commit()
{
if (_cache == null) return;
Write(_cache, false);
_cache = null;
}
/// <summary>回滚事务,放弃缓å˜çš„全部修改,ä¸å†™å›žç£ç›˜ã€‚</summary>
public void Rollback() => _cache = null;
#endregion
#region æ·»åˆ æ”¹æŸ¥
/// <summary>批é‡å†™å…¥æ•°æ®ï¼ˆé«˜æ€§èƒ½ï¼‰</summary>
/// <param name="models">è¦å†™å…¥çš„æ•°æ®</param>
/// <param name="append">是å¦é™„åŠ åœ¨å°¾éƒ¨ã€‚ä¸º false 时从头写入,覆盖已有数æ®</param>
public void Write(IEnumerable<T> models, Boolean append)
{
if (append && (models is ICollection<T> collection && collection.Count == 0)) return;
var file = GetFile();
file.EnsureDirectory(true);
using var fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
if (append) fs.Position = fs.Length;
using var csv = new CsvFile(fs, true) { Encoding = Encoding };
// é¦–æ¬¡å†™å…¥æ–‡ä»¶å¤´ã€‚éœ€è¦æ£ç¡®å¤„ç†åå˜é€†å˜é—®é¢˜ï¼Œå…¼å®¹.NET2.0
if (fs.Position == 0) csv.WriteLine(_propertyNames);
// 写入数æ®
foreach (var item in models)
{
if (item is IModel src)
csv.WriteLine(_properties.Select(e => src[e.Name]));
else if (item != null)
csv.WriteLine(_properties.Select(e => item.GetValue(e)));
}
// 截æ–原有多余内容(覆盖写场景)
fs.SetLength(fs.Position);
fs.Flush();
}
/// <summary>尾部æ’入数æ®ï¼Œæ€§èƒ½æžå¥½</summary>
/// <param name="model"></param>
public void Add(T model)
{
if (_cache != null)
_cache.Add(model);
else
Write([model], true);
}
/// <summary>尾部æ’入数æ®ï¼Œæ€§èƒ½æžå¥½</summary>
/// <param name="models"></param>
public void Add(IEnumerable<T> models)
{
if (_cache != null)
_cache.AddRange(models);
else
Write(models, true);
}
/// <summary>åˆ é™¤æ•°æ®ï¼Œæ€§èƒ½å¾ˆå·®ï¼Œå…¨éƒ¨è¯»å–剔除åŽä¿å˜</summary>
/// <param name="model"></param>
/// <returns></returns>
public Int32 Remove(T model) => Remove([model]);
/// <summary>åˆ é™¤æ•°æ®ï¼Œæ€§èƒ½å¾ˆå·®ï¼Œå…¨éƒ¨è¯»å–剔除åŽä¿å˜</summary>
/// <param name="models"></param>
/// <returns></returns>
public Int32 Remove(IEnumerable<T> models)
{
if (models == null) return 0;
if (Comparer == null) throw new ArgumentNullException(nameof(Comparer));
var arr = models as ICollection<T> ?? models.ToList();
if (arr.Count == 0) return 0;
return Remove(x => arr.Any(y => Comparer.Equals(x, y)));
}
/// <summary>åˆ é™¤æ»¡è¶³æ¡ä»¶çš„æ•°æ®ï¼Œæ€§èƒ½å¾ˆå·®ï¼Œå…¨éƒ¨è¯»å–剔除åŽä¿å˜</summary>
/// <param name="predicate"></param>
/// <returns></returns>
public Int32 Remove(Func<T, Boolean> predicate)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
lock (this)
{
if (_cache != null) return _cache.RemoveAll(x => predicate(x));
var list = FindAll();
if (list.Count == 0) return 0;
var count = list.Count;
list = list.Where(x => !predicate(x)).ToList();
// åˆ é™¤æ–‡ä»¶ï¼Œé‡æ–°å†™å›žåŽ»
if (list.Count < count)
{
// 如果没有了数æ®ï¼Œåªå†™å¤´éƒ¨
Write(list, false);
}
return count - list.Count;
}
}
/// <summary>清空数æ®ã€‚åªå†™å¤´éƒ¨</summary>
public void Clear()
{
if (_cache != null)
_cache.Clear();
else
Write([], false);
}
/// <summary>更新指定数æ®è¡Œï¼Œæ€§èƒ½å¾ˆå·®ï¼Œå…¨éƒ¨è¯»å–替æ¢åŽä¿å˜</summary>
/// <param name="model"></param>
/// <returns></returns>
public Boolean Update(T model) => Set(model, false);
/// <summary>è®¾ç½®ï¼ˆæ·»åŠ æˆ–æ›´æ–°ï¼‰æŒ‡å®šæ•°æ®è¡Œï¼Œæ€§èƒ½å¾ˆå·®ï¼Œå…¨éƒ¨è¯»å–替æ¢åŽä¿å˜</summary>
/// <param name="model"></param>
/// <returns></returns>
public Boolean Set(T model) => Set(model, true);
private Boolean Set(T model, Boolean add)
{
if (Comparer == null) throw new ArgumentNullException(nameof(Comparer));
lock (this)
{
var list = _cache ?? FindAll();
if (!add && list.Count == 0) return false;
// æ‰¾åˆ°ç›®æ ‡æ•°æ®è¡Œï¼Œå¹¶æ›¿æ¢
var flag = false;
for (var i = 0; i < list.Count; i++)
{
if (Comparer.Equals(model, list[i]))
{
list[i] = model;
flag = true;
break;
}
}
if (!flag)
{
if (!add) return false;
list.Add(model);
}
// 釿–°å†™å›žåŽ»
if (_cache == null)
{
Write(list, false);
}
return true;
}
}
/// <summary>查找指定数æ®è¡Œ</summary>
/// <param name="model"></param>
/// <returns></returns>
public T? Find(T model)
{
if (Comparer == null) throw new ArgumentNullException(nameof(Comparer));
return Query(e => Comparer.Equals(model, e), 1).FirstOrDefault();
}
/// <summary>èŽ·å–æ»¡è¶³æ¡ä»¶çš„第一行数æ®</summary>
/// <param name="predicate"></param>
/// <returns></returns>
public T? Find(Func<T, Boolean>? predicate) => Query(predicate, 1).FirstOrDefault();
/// <summary>èŽ·å–æ‰€æœ‰æ•°æ®è¡Œ</summary>
/// <returns></returns>
public IList<T> FindAll() => _cache?.ToList() ?? Query(null).ToList();
/// <summary>èŽ·å–æ»¡è¶³æ¡ä»¶çš„æ•°æ®è¡Œï¼Œæ€§èƒ½å¥½ï¼Œé¡ºåºæŸ¥æ‰¾</summary>
/// <param name="predicate"></param>
/// <param name="count">最多返回行数。默认 -1 表示ä¸é™åˆ¶</param>
/// <returns></returns>
public IEnumerable<T> Query(Func<T, Boolean>? predicate, Int32 count = -1)
{
// å¼€å¯äº‹åŠ¡æ—¶ï¼Œç›´æŽ¥è¿”å›žç¼“å˜æ•°æ®
if (_cache != null)
{
foreach (var item in _cache)
{
if (predicate == null || predicate(item))
{
yield return item;
if (--count == 0) break;
}
}
yield break;
}
var file = GetFile();
if (!File.Exists(file)) yield break;
lock (this)
{
using var csv = new CsvFile(file, false) { Encoding = Encoding };
// 文件列ååˆ°ç´¢å¼•çš„æ˜ å°„
var headers = new Dictionary<String, Int32>(StringComparer.OrdinalIgnoreCase);
// æ–‡ä»¶åˆ—ç´¢å¼•åˆ°å±žæ€§ç´¢å¼•çš„æ˜ å°„ï¼Œé¿å…æ¯è¡Œéƒ½æŸ¥å—å…¸
var columnToProperty = (Int32[]?)null;
while (true)
{
var ss = csv.ReadLine();
if (ss == null) break;
// 头部,å称与åºå·å¯¹åº”
if (headers.Count == 0)
{
for (var i = 0; i < ss.Length; i++)
{
// é¿å…é‡å¤é”®å¼‚常;忽略é‡å¤åˆ—
if (!headers.ContainsKey(ss[i])) headers[ss[i]] = i;
}
// æ— æ³•è¯†åˆ«æ‰€æœ‰å—æ®µ
if (headers.Count == 0) break;
// å»ºç«‹æ–‡ä»¶åˆ—åˆ°å±žæ€§çš„æ˜ å°„
columnToProperty = new Int32[ss.Length];
for (var i = 0; i < ss.Length; i++)
{
columnToProperty[i] = _propertyIndexes.TryGetValue(ss[i], out var idx) ? idx : -1;
}
}
else
{
var flag = false;
var model = new T();
try
{
// å射构建对象
var success = 0;
for (var i = 0; i < ss.Length && i < columnToProperty!.Length; i++)
{
var propIdx = columnToProperty[i];
if (propIdx < 0) continue;
var pi = _properties[propIdx];
if (!pi.CanWrite) continue;
var raw = ss[i];
if (raw == null) continue;
// éƒ¨åˆ†åŸºç¡€ç±»åž‹åˆ¤æ–æ•°æ®æœ‰æ•ˆæ€§
if (pi.PropertyType.IsInt() && !Int64.TryParse(raw, out _)) continue;
var code = pi.PropertyType.GetTypeCode();
switch (code)
{
case TypeCode.Single:
case TypeCode.Double:
if (!Double.TryParse(raw, out _)) continue;
break;
case TypeCode.Decimal:
if (!Decimal.TryParse(raw, out _)) continue;
break;
case TypeCode.DateTime:
if (!DateTime.TryParse(raw, out _)) continue;
break;
default:
break;
}
var value = raw.ChangeType(pi.PropertyType);
if (model is IModel dst)
dst[pi.Name] = value;
else
model.SetValue(pi, value);
success++;
}
// æ²¡æœ‰ä»»ä½•å—æ®µæˆåŠŸåŒ¹é…,视为æŸå行
if (success == 0) continue;
if (predicate == null || predicate(model))
{
flag = true;
}
}
catch (Exception ex)
{
// è¯»å–æŸä¸€è¡Œå‡ºé”™ï¼Œæ”¾å¼ƒè¯¥è¡Œ
XTrace.WriteException(ex);
continue;
}
if (!flag) continue;
yield return model;
if (--count == 0) break;
}
}
}
}
/// <summary>èŽ·å–æ•°æ®è¡Œæ•°ï¼Œæ€§èƒ½è¾ƒå¥½ï¼Œç»Ÿè®¡æ–‡ä»¶è¡Œæ•°ï¼ˆé™¤å¤´éƒ¨ï¼‰</summary>
/// <returns></returns>
public Int32 FindCount()
{
if (_cache != null) return _cache.Count;
lock (this)
{
var file = GetFile();
if (!File.Exists(file)) return 0;
// é€è¡Œè¯»å–统计,é¿å…ä¸€æ¬¡æ€§åŠ è½½å…¨éƒ¨å†…å®¹åˆ°å†…å˜
var line = 0;
using var sr = new StreamReader(file, Encoding);
if (sr.ReadLine() == null) return 0; // 跳过头部,ä¸å˜åœ¨åˆ™è¿”回 0
while (sr.ReadLine() != null) line++;
return line;
}
}
#endregion
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#region å¼‚æ¥æ–¹æ³•
/// <summary>异æ¥èŽ·å–æ»¡è¶³æ¡ä»¶çš„æ•°æ®è¡Œ</summary>
/// <param name="predicate">过滤æ¡ä»¶</param>
/// <param name="count">最多返回行数。默认 -1 表示ä¸é™åˆ¶</param>
/// <returns></returns>
public async IAsyncEnumerable<T> QueryAsync(Func<T, Boolean>? predicate, Int32 count = -1)
{
// å¼€å¯äº‹åŠ¡æ—¶ï¼Œç›´æŽ¥è¿”å›žç¼“å˜æ•°æ®
if (_cache != null)
{
foreach (var item in _cache)
{
if (predicate == null || predicate(item))
{
yield return item;
if (--count == 0) break;
}
}
yield break;
}
var file = GetFile();
if (!File.Exists(file)) yield break;
var csv = new CsvFile(file, false) { Encoding = Encoding };
await using (csv.ConfigureAwait(false))
{
// 文件列ååˆ°ç´¢å¼•çš„æ˜ å°„
var headers = new Dictionary<String, Int32>(StringComparer.OrdinalIgnoreCase);
// æ–‡ä»¶åˆ—ç´¢å¼•åˆ°å±žæ€§ç´¢å¼•çš„æ˜ å°„
var columnToProperty = (Int32[]?)null;
await foreach (var ss in csv.ReadAllAsync().ConfigureAwait(false))
{
// 头部,å称与åºå·å¯¹åº”
if (headers.Count == 0)
{
for (var i = 0; i < ss.Length; i++)
{
if (!headers.ContainsKey(ss[i])) headers[ss[i]] = i;
}
if (headers.Count == 0) break;
columnToProperty = new Int32[ss.Length];
for (var i = 0; i < ss.Length; i++)
{
columnToProperty[i] = _propertyIndexes.TryGetValue(ss[i], out var idx) ? idx : -1;
}
continue;
}
var flag = false;
var model = new T();
try
{
var success = 0;
for (var i = 0; i < ss.Length && i < columnToProperty!.Length; i++)
{
var propIdx = columnToProperty[i];
if (propIdx < 0) continue;
var pi = _properties[propIdx];
if (!pi.CanWrite) continue;
var raw = ss[i];
if (raw == null) continue;
if (pi.PropertyType.IsInt() && !Int64.TryParse(raw, out _)) continue;
var code = pi.PropertyType.GetTypeCode();
switch (code)
{
case TypeCode.Single:
case TypeCode.Double:
if (!Double.TryParse(raw, out _)) continue;
break;
case TypeCode.Decimal:
if (!Decimal.TryParse(raw, out _)) continue;
break;
case TypeCode.DateTime:
if (!DateTime.TryParse(raw, out _)) continue;
break;
default:
break;
}
var value = raw.ChangeType(pi.PropertyType);
if (model is IModel dst)
dst[pi.Name] = value;
else
model.SetValue(pi, value);
success++;
}
if (success == 0) continue;
if (predicate == null || predicate(model))
{
flag = true;
}
}
catch (Exception ex)
{
XTrace.WriteException(ex);
continue;
}
if (!flag) continue;
yield return model;
if (--count == 0) break;
}
}
}
/// <summary>异æ¥èŽ·å–æ‰€æœ‰æ•°æ®è¡Œ</summary>
/// <returns></returns>
public async Task<IList<T>> FindAllAsync()
{
if (_cache != null) return _cache.ToList();
var list = new List<T>();
await foreach (var item in QueryAsync(null).ConfigureAwait(false))
{
list.Add(item);
}
return list;
}
#endregion
#endif
#region 辅助
private String GetFile()
{
if (FileName.IsNullOrEmpty()) throw new ArgumentNullException(nameof(FileName));
return FileName.GetFullPath();
}
private class MyComparer(Func<T?, T?, Boolean> comparer) : IEqualityComparer<T>
{
public Func<T?, T?, Boolean> Comparer = comparer;
public Boolean Equals(T? x, T? y) => Comparer(x, y);
public Int32 GetHashCode(T? obj) => obj?.GetHashCode() ?? 0;
}
#endregion
}
|