S8MonitoringRow.cs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. using System.Data;
  2. using System.Globalization;
  3. namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess;
  4. /// <summary>
  5. /// S8 监控数据的 canonical 单行。
  6. ///
  7. /// 设计约束(与迁移前 DataRow 读取语义严格一致,不得"顺手改进"):
  8. /// · 取值器缺列返回 null,不抛异常 —— evaluator 依赖此行为做 canonical→params 回退;
  9. /// · DBNull 与 null 等价,均返回 null;**绝不用字符串 "null" 表示空值**;
  10. /// · GetString 去首尾空白;GetDecimal / GetDateTime / GetLong 支持字符串与强类型两种来源
  11. /// (Legacy SQL 走 DataTable 常给字符串,标准 Provider 常给强类型);
  12. /// · <see cref="Values"/> 保留结果集全部原始列,payload 落盘依赖它。
  13. ///
  14. /// 列名比较一律 OrdinalIgnoreCase,与既有 payload 字典(StringComparer.OrdinalIgnoreCase)保持一致。
  15. /// </summary>
  16. public sealed class S8MonitoringRow
  17. {
  18. private readonly Dictionary<string, object?> _values;
  19. private S8MonitoringRow(Dictionary<string, object?> values) => _values = values;
  20. /// <summary>结果集原始列 → 值。缺失值为 null(不是 DBNull,不是 "null" 字符串)。</summary>
  21. public IReadOnlyDictionary<string, object?> Values => _values;
  22. /// <summary>由列值字典构造一行。标准 Provider 使用此入口。</summary>
  23. public static S8MonitoringRow FromValues(IReadOnlyDictionary<string, object?> values)
  24. {
  25. var copy = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
  26. foreach (var kv in values)
  27. copy[kv.Key] = kv.Value == DBNull.Value ? null : kv.Value;
  28. return new S8MonitoringRow(copy);
  29. }
  30. internal static S8MonitoringRow FromDataRow(DataRow row)
  31. {
  32. var values = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
  33. foreach (DataColumn column in row.Table.Columns)
  34. {
  35. var v = row[column];
  36. values[column.ColumnName] = v == DBNull.Value ? null : v;
  37. }
  38. return new S8MonitoringRow(values);
  39. }
  40. /// <summary>列是否存在于本行。</summary>
  41. public bool HasColumn(string? columnName) =>
  42. !string.IsNullOrWhiteSpace(columnName) && _values.ContainsKey(columnName!);
  43. private bool TryGetRaw(string? columnName, out object? value)
  44. {
  45. value = null;
  46. if (string.IsNullOrWhiteSpace(columnName)) return false;
  47. if (!_values.TryGetValue(columnName!, out var v)) return false;
  48. if (v is null) return false;
  49. value = v;
  50. return true;
  51. }
  52. /// <summary>读字符串并去首尾空白;缺列 / 空值返回 null。</summary>
  53. public string? GetString(string? columnName) =>
  54. TryGetRaw(columnName, out var v) ? Convert.ToString(v)?.Trim() : null;
  55. /// <summary>读时间;支持 DateTime 与可解析字符串。缺列 / 空值 / 无法解析返回 null。</summary>
  56. public DateTime? GetDateTime(string? columnName)
  57. {
  58. if (!TryGetRaw(columnName, out var v)) return null;
  59. if (v is DateTime dt) return dt;
  60. return DateTime.TryParse(Convert.ToString(v, CultureInfo.InvariantCulture), out var parsed) ? parsed : null;
  61. }
  62. /// <summary>读小数;支持数值与可解析字符串。缺列 / 空值 / 无法解析返回 null。</summary>
  63. public decimal? GetDecimal(string? columnName)
  64. {
  65. if (!TryGetRaw(columnName, out var v)) return null;
  66. if (v is decimal d) return d;
  67. return decimal.TryParse(
  68. Convert.ToString(v, CultureInfo.InvariantCulture),
  69. NumberStyles.Any,
  70. CultureInfo.InvariantCulture,
  71. out var parsed)
  72. ? parsed
  73. : null;
  74. }
  75. /// <summary>读长整型。缺列 / 空值 / 无法解析返回 null。</summary>
  76. public long? GetLong(string? columnName)
  77. {
  78. if (!TryGetRaw(columnName, out var v)) return null;
  79. if (v is long l) return l;
  80. return long.TryParse(Convert.ToString(v, CultureInfo.InvariantCulture), out var parsed) ? parsed : null;
  81. }
  82. }