diff --git a/src/LuYao.Common/Data/Attributes/RecordAttribute.cs b/src/LuYao.Common/Data/Attributes/RecordAttribute.cs
deleted file mode 100644
index 3fc9479..0000000
--- a/src/LuYao.Common/Data/Attributes/RecordAttribute.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace LuYao.Data.Attributes;
-
-///
-/// 用于标记 Record 映射行为的特性基类。
-///
-[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public abstract class RecordAttribute : Attribute
-{
-}
diff --git a/src/LuYao.Common/Data/Attributes/RecordColumnNameAttribute.cs b/src/LuYao.Common/Data/Attributes/RecordColumnNameAttribute.cs
deleted file mode 100644
index 8d55ce3..0000000
--- a/src/LuYao.Common/Data/Attributes/RecordColumnNameAttribute.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-
-namespace LuYao.Data.Attributes;
-
-///
-/// 标记属性对应的列名称,优先级高于 。
-/// 对标 。
-///
-[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public sealed class RecordColumnNameAttribute : RecordAttribute
-{
- ///
- /// 初始化 的新实例。
- ///
- /// 列的名称。
- /// 为 null 或空白时抛出。
- public RecordColumnNameAttribute(string name)
- {
- if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
- Name = name;
- }
-
- ///
- /// 列的名称。
- ///
- public string Name { get; }
-}
diff --git a/src/LuYao.Common/Data/Attributes/RecordColumnStorageAttribute.cs b/src/LuYao.Common/Data/Attributes/RecordColumnStorageAttribute.cs
deleted file mode 100644
index c2f2416..0000000
--- a/src/LuYao.Common/Data/Attributes/RecordColumnStorageAttribute.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using LuYao.Data.Mapping;
-using System;
-
-namespace LuYao.Data.Attributes;
-
-///
-/// 声明属性在 中的列存储类型,优先级高于 的全局策略。
-///
-///
-///
-/// 仅在属性类型不是 RecordTable 原生支持的类型时才有意义。对于原生支持的类型,此特性会被忽略。
-///
-///
-/// 使用 或 时,
-/// 必须在 中注册该属性类型对应的 ,否则将在映射时抛出异常。
-///
-///
-///
-///
-/// // 将 Guid 属性以字符串形式存储到 RecordTable
-/// [RecordColumnStorage(RecordColumnStorageTarget.String)]
-/// public Guid Id { get; set; }
-///
-/// // 跳过该属性,不创建对应列
-/// [RecordColumnStorage(RecordColumnStorageTarget.Skip)]
-/// public MyComplexObject Payload { get; set; }
-///
-///
-[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public sealed class RecordColumnStorageAttribute : RecordAttribute
-{
- ///
- /// 初始化 的新实例。
- ///
- /// 指定的存储类型。
- public RecordColumnStorageAttribute(RecordColumnStorageTarget target)
- {
- Target = target;
- }
-
- ///
- /// 属性在 RecordTable 中的列存储类型。
- ///
- public RecordColumnStorageTarget Target { get; }
-}
diff --git a/src/LuYao.Common/Data/Binary/BinaryPayloadHeader.cs b/src/LuYao.Common/Data/Binary/BinaryPayloadHeader.cs
deleted file mode 100644
index f24b188..0000000
--- a/src/LuYao.Common/Data/Binary/BinaryPayloadHeader.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using System.IO;
-
-namespace LuYao.Data.Binary;
-
-internal enum BinaryPayloadType : byte
-{
- RecordTable = 1,
- RecordSet = 2
-}
-
-internal static class BinaryPayloadHeader
-{
- private const byte Marker = 0xFF;
- private const byte Signature1 = (byte)'L';
- private const byte Signature2 = (byte)'Y';
-
- public static void Write(BinaryWriter writer, BinaryPayloadType payloadType)
- {
- writer.Write(Marker);
- writer.Write(Signature1);
- writer.Write(Signature2);
- writer.Write((byte)payloadType);
- }
-
- public static byte ReadHeaderAndVersion(BinaryReader reader, BinaryPayloadType expectedType)
- {
- byte first = reader.ReadByte();
- if (first != Marker)
- {
- return first;
- }
-
- byte sig1 = reader.ReadByte();
- byte sig2 = reader.ReadByte();
- if (sig1 != Signature1 || sig2 != Signature2)
- throw new InvalidOperationException($"无效的二进制文件头签名,期望 'LY',实际 0x{sig1:X2}{sig2:X2}。");
-
- var actualType = (BinaryPayloadType)reader.ReadByte();
- if (actualType != expectedType)
- throw new InvalidOperationException($"二进制数据类型不匹配,期望 {expectedType},实际 {actualType}。");
-
- return reader.ReadByte();
- }
-
- ///
- /// 尝试从二进制数据读取类型头中的 payload 类型。
- ///
- /// 二进制数据。
- /// 成功时返回读取到的 payload 类型。
- /// 当数据包含有效类型头时返回 true;否则返回 false。
- public static bool TryGetPayloadType(byte[] data, out BinaryPayloadType payloadType)
- {
- payloadType = default;
- if (data == null) return false;
- if (data.Length < 4) return false;
- if (data[0] != Marker || data[1] != Signature1 || data[2] != Signature2) return false;
-
- byte rawType = data[3];
- if (rawType != (byte)BinaryPayloadType.RecordTable && rawType != (byte)BinaryPayloadType.RecordSet)
- return false;
-
- payloadType = (BinaryPayloadType)rawType;
- return true;
- }
-}
diff --git a/src/LuYao.Common/Data/Binary/RecordBinaryPayloadCodec.cs b/src/LuYao.Common/Data/Binary/RecordBinaryPayloadCodec.cs
deleted file mode 100644
index 858e644..0000000
--- a/src/LuYao.Common/Data/Binary/RecordBinaryPayloadCodec.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-#if !NET45 && !NET461
-using System;
-using System.IO;
-using System.IO.Compression;
-
-namespace LuYao.Data.Binary;
-
-///
-/// Record 二进制负载编解码器。
-///
-///
-/// 当前格式(版本 1)头部共 6 字节:
-///
-/// 0xFE 'L' 'Y' 'Z' <version=1> <algo>
-///
-///
-/// 其中 algo 对应 :0 = 不压缩,1 = GZip。
-/// 头部中的 0xFE 与 的 marker (0xFF) 以及遗留格式的版本号字节 (1) 均不冲突。
-///
-///
-/// 为防御 zip bomb / DoS 攻击,可通过 和
-/// 属性配置限制。
-///
-///
-public sealed class RecordBinaryPayloadCodec
-{
- // ── Magic ──────────────────────────────────────────────────
- private const byte MagicByte0 = 0xFE;
- private const byte MagicByte1 = (byte)'L';
- private const byte MagicByte2 = (byte)'Y';
- private const byte MagicByte3 = (byte)'Z';
- private const int MagicLength = 4;
-
- // 版本 1 头部:magic(4) + version(1) + algo(1)
- private const byte FormatVersion = 1;
- private const int HeaderLength = MagicLength + 1 + 1; // 6
-
- private const int DecodeBufferSize = 16 * 1024;
- private const int InitialOutputCapacity = 4 * 1024;
-
- // ── 默认 DoS 限制 ──────────────────────────────────────────
- private const int DefaultMaxDecompressedBytes = 64 * 1024 * 1024;
- private const int DefaultMaxCompressedBytes = 16 * 1024 * 1024;
-
- // ── 单例:使用默认配置 ──────────────────────────────────────
- /// 使用默认配置的共享实例(GZip 压缩,默认 DoS 阈值)。
- public static readonly RecordBinaryPayloadCodec Default = new();
-
- // ── 属性 ────────────────────────────────────────────────────
-
- /// 序列化时使用的压缩算法,默认 。
- public RecordPayloadCompression Compression { get; set; } = RecordPayloadCompression.GZip;
-
- ///
- /// 解压后允许的最大字节数,用于防御 zip bomb 等 DoS 攻击。
- /// 仅在使用压缩算法时生效。默认 64 MB。
- ///
- public int MaxDecompressedBytes { get; set; } = DefaultMaxDecompressedBytes;
-
- ///
- /// 压缩负载(Base64 解码后)允许的最大字节数,用于防御过大压缩输入。
- /// 默认 16 MB。
- ///
- public int MaxCompressedBytes { get; set; } = DefaultMaxCompressedBytes;
-
- ///
- /// JSON 字符串中 Base64 文本允许的最大字符数。
- /// 取 对应的 Base64 长度上限并向上取整。
- ///
- public int MaxBase64Length => ((MaxCompressedBytes + 2) / 3) * 4;
-
- // ── Encode ──────────────────────────────────────────────────
-
- /// 按配置的压缩算法编码负载,并写入版本化头部。
- /// 原始负载。
- /// 带头部的编码负载。
- public byte[] Encode(byte[] payload)
- {
- if (payload == null) throw new ArgumentNullException(nameof(payload));
- return Encode(payload, 0, payload.Length);
- }
-
- /// 按配置的压缩算法编码负载,并写入版本化头部。
- /// 原始负载。
- /// 起始偏移。
- /// 长度。
- /// 带头部的编码负载。
- public byte[] Encode(byte[] payload, int offset, int count)
- {
- if (payload == null) throw new ArgumentNullException(nameof(payload));
- if (offset < 0 || offset > payload.Length) throw new ArgumentOutOfRangeException(nameof(offset));
- if (count < 0 || payload.Length - offset < count) throw new ArgumentOutOfRangeException(nameof(count));
-
- using var output = new MemoryStream(count + HeaderLength + 16);
- WriteHeader(output, Compression);
-
- switch (Compression)
- {
- case RecordPayloadCompression.None:
- output.Write(payload, offset, count);
- break;
- case RecordPayloadCompression.GZip:
- using (var gzip = new GZipStream(output, CompressionLevel.Fastest, leaveOpen: true))
- {
- gzip.Write(payload, offset, count);
- }
- break;
- default:
- throw new InvalidOperationException($"不支持的压缩算法:{Compression}。");
- }
-
- return output.ToArray();
- }
-
- // ── Decode ──────────────────────────────────────────────────
-
- ///
- /// 读取负载并自动识别版本与压缩算法进行解码;若负载不带本格式头则原样返回(向后兼容遗留无压缩格式)。
- ///
- /// Base64 解码后的负载。
- /// 解码后的原始负载。
- /// 为 null。
- /// 输入过大、解压结果超出限制或算法不支持。
- public byte[] Decode(byte[] payload)
- {
- if (payload == null) throw new ArgumentNullException(nameof(payload));
-
- if (!TryReadHeader(payload, out byte version, out RecordPayloadCompression algo, out int dataOffset))
- {
- // 无头部:遗留格式,原样返回
- return payload;
- }
-
- if (algo == RecordPayloadCompression.None)
- {
- // 无压缩:直接截取数据部分
- int dataLength = payload.Length - dataOffset;
- var result = new byte[dataLength];
- Buffer.BlockCopy(payload, dataOffset, result, 0, dataLength);
- return result;
- }
-
- // 有压缩:施加输入上限防御 DoS
- if (payload.Length > MaxCompressedBytes)
- throw new InvalidDataException($"压缩负载超过限制:{MaxCompressedBytes} bytes。");
-
- return Decompress(payload, dataOffset, algo);
- }
-
- // ── Private helpers ─────────────────────────────────────────
-
- private static void WriteHeader(Stream stream, RecordPayloadCompression algo)
- {
- stream.WriteByte(MagicByte0);
- stream.WriteByte(MagicByte1);
- stream.WriteByte(MagicByte2);
- stream.WriteByte(MagicByte3);
- stream.WriteByte(FormatVersion);
- stream.WriteByte((byte)algo);
- }
-
- ///
- /// 尝试解析版本 1 头部(6 字节)。
- ///
- private static bool TryReadHeader(byte[] data, out byte version, out RecordPayloadCompression algo, out int dataOffset)
- {
- version = 0;
- algo = RecordPayloadCompression.None;
- dataOffset = 0;
-
- if (data.Length < HeaderLength) return false;
- if (data[0] != MagicByte0 || data[1] != MagicByte1 || data[2] != MagicByte2 || data[3] != MagicByte3)
- return false;
-
- version = data[MagicLength];
- algo = (RecordPayloadCompression)data[MagicLength + 1];
- dataOffset = HeaderLength;
- return true;
- }
-
- private byte[] Decompress(byte[] payload, int dataOffset, RecordPayloadCompression algo)
- {
- int dataLength = payload.Length - dataOffset;
- using var input = new MemoryStream(payload, dataOffset, dataLength, writable: false);
-
- Stream decompressStream = algo switch
- {
- RecordPayloadCompression.GZip => new GZipStream(input, CompressionMode.Decompress, leaveOpen: false),
- _ => throw new InvalidDataException($"不支持的压缩算法标识:0x{(byte)algo:X2}。")
- };
-
- using (decompressStream)
- using (var output = new MemoryStream(InitialOutputCapacity))
- {
- var buffer = new byte[DecodeBufferSize];
- long total = 0;
- while (true)
- {
- int read = decompressStream.Read(buffer, 0, buffer.Length);
- if (read <= 0) break;
-
- if (total + read > MaxDecompressedBytes)
- throw new InvalidDataException($"解压后数据超过限制:{MaxDecompressedBytes} bytes。");
-
- total += read;
- output.Write(buffer, 0, read);
- }
- return output.ToArray();
- }
- }
-}
-#endif
\ No newline at end of file
diff --git a/src/LuYao.Common/Data/Binary/RecordPayloadCompression.cs b/src/LuYao.Common/Data/Binary/RecordPayloadCompression.cs
deleted file mode 100644
index 48fb5c8..0000000
--- a/src/LuYao.Common/Data/Binary/RecordPayloadCompression.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace LuYao.Data.Binary;
-
-///
-/// 指定 序列化时使用的压缩算法。
-///
-public enum RecordPayloadCompression : byte
-{
- /// 不压缩。
- None = 0,
-
- /// 使用 GZip 压缩。
- GZip = 1,
-}
diff --git a/src/LuYao.Common/Data/ConversionFailureHandling.cs b/src/LuYao.Common/Data/ConversionFailureHandling.cs
deleted file mode 100644
index db04900..0000000
--- a/src/LuYao.Common/Data/ConversionFailureHandling.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace LuYao.Data;
-
-///
-/// 在 RecordTable 到 DTO 映射时,列值类型转换失败的处理策略。
-///
-public enum ConversionFailureHandling
-{
- /// 跳过转换失败的列,对应属性保持默认值(默认)。
- Skip = 0,
-
- /// 转换失败时抛出异常。
- Throw = 1,
-}
diff --git a/src/LuYao.Common/Data/Helpers.cs b/src/LuYao.Common/Data/Helpers.cs
index f06fea6..472a89e 100644
--- a/src/LuYao.Common/Data/Helpers.cs
+++ b/src/LuYao.Common/Data/Helpers.cs
@@ -1,8 +1,5 @@
-using LuYao.Data.Meta;
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq.Expressions;
namespace LuYao.Data;
@@ -45,7 +42,7 @@ static Helpers()
}
///
- /// From CLR get the base (excluding Nullable and array info).
+ /// From CLR get its .
///
internal static RecordColumnType GetColumnType(Type type)
{
@@ -55,25 +52,12 @@ internal static RecordColumnType GetColumnType(Type type)
return RecordColumnType.Binary;
}
- var lookup = NormalizeColumnLookupType(type);
-
- if (TypeToColumnType.TryGetValue(lookup, out var ct))
+ if (TypeToColumnType.TryGetValue(type, out var ct))
return ct;
throw new NotSupportedException($"类型 '{type.FullName}' 不是支持的列类型");
}
- ///
- /// 判断 CLR 是否为 Nullable 值类型,或其数组元素类型为 Nullable。
- ///
- internal static bool IsNullableType(Type type)
- {
- return Nullable.GetUnderlyingType(type) != null;
- }
-
- ///
- /// 从基础 、 和 还原 CLR 。
- ///
- internal static Type GetClrType(RecordColumnType columnType, bool isNullable)
+ internal static Type GetClrType(RecordColumnType columnType)
{
if (columnType == RecordColumnType.Binary)
return typeof(byte[]);
@@ -81,8 +65,6 @@ internal static Type GetClrType(RecordColumnType columnType, bool isNullable)
if (!ColumnTypeToType.TryGetValue(columnType, out var baseType))
throw new NotSupportedException($"未知列类型枚举值: {columnType}");
- if (isNullable && baseType.IsValueType)
- return typeof(Nullable<>).MakeGenericType(baseType);
return baseType;
}
@@ -95,75 +77,21 @@ internal static bool IsSupportedColumnType(Type type)
// byte[] is directly supported as Binary
if (type == typeof(byte[])) return true;
- var normalized = NormalizeColumnLookupType(type);
- return TypeToColumnType.ContainsKey(normalized);
+ return TypeToColumnType.ContainsKey(type);
}
- internal static bool IsSupportedForReading(IXProp p)
- {
- if (p != null && p.CanRead && IsSupportedColumnType(p.Type)) return true;
- return false;
- }
- internal static bool IsSupportedForWriting(IXProp p)
- {
- if (p != null && p.CanWrite && IsSupportedColumnType(p.Type)) return true;
- return false;
- }
-
- ///
/// 验证列类型是否在白名单内,不在则抛出异常。
- ///
internal static void ValidateColumnType(Type type)
{
if (!IsSupportedColumnType(type))
{
- throw new NotSupportedException($"类型 '{type.FullName}' 不是支持的列类型。支持的类型包括:bool, 整数类型, 浮点类型, char, string, DateTime, DateTimeOffset, TimeSpan, Guid, byte[]、枚举及其 Nullable 形式,以及上述类型的数组(如 int[], string[,])。");
+ throw new NotSupportedException($"类型 '{type.FullName}' 不是支持的列类型。支持类型由 RecordColumnType 定义;byte[] 作为 Binary 支持,其他数组、Nullable 和枚举不受支持。");
}
}
- ///
- /// 规范化列类型查找目标:先展开 Nullable,再将枚举转换为其基础数值类型。
- ///
- private static Type NormalizeColumnLookupType(Type type)
- {
- var effective = Nullable.GetUnderlyingType(type) ?? type;
- return effective.IsEnum ? Enum.GetUnderlyingType(effective) : effective;
- }
- private static readonly ConcurrentDictionary> _cache = new();
-
- private static Func GetConstructor(Type type)
- {
- return _cache.GetOrAdd(type, static t =>
- {
- // 创建泛型类型
- var genericType = typeof(RecordColumn<>).MakeGenericType(t);
-
- // 获取构造函数
- var ctor = genericType.GetConstructor(new[] {
- typeof(RecordTable),
- typeof(string),
- typeof(Type)
- }) ?? throw new InvalidOperationException($"Constructor not found for {genericType}");
-
- // 定义参数表达式
- var pRecord = Expression.Parameter(typeof(RecordTable), "table");
- var pName = Expression.Parameter(typeof(string), "name");
- var pType = Expression.Parameter(typeof(Type), "type");
-
- // 创建构造函数调用表达式
- var newExpr = Expression.New(ctor, pRecord, pName, pType);
-
- // 编译表达式树为委托
- return Expression.Lambda>(
- newExpr, pRecord, pName, pType).Compile();
- });
- }
-
public static RecordColumn MakeRecordColumn(RecordTable table, string name)
{
- // 使用泛型方法优化,避免运行时查找类型
- var ctor = GetConstructor(typeof(T));
- return ctor.Invoke(table, name, typeof(T));
+ return MakeRecordColumn(table, name, typeof(T));
}
public static RecordColumn MakeRecordColumn(RecordTable table, string name, Type type)
@@ -174,7 +102,36 @@ public static RecordColumn MakeRecordColumn(RecordTable table, string name, Type
ValidateColumnType(type);
- var ctor = GetConstructor(type);
- return ctor.Invoke(table, name, type);
+ return MakeRecordColumn(table, name, GetColumnType(type), type);
+ }
+
+ internal static RecordColumn MakeRecordColumn(RecordTable table, string name, RecordColumnType columnType, Type? clrType = null)
+ {
+ var type = clrType ?? GetClrType(columnType);
+ return columnType switch
+ {
+ RecordColumnType.Boolean => new RecordColumn(table, name, type),
+ RecordColumnType.SByte => new RecordColumn(table, name, type),
+ RecordColumnType.Int16 => new RecordColumn(table, name, type),
+ RecordColumnType.Int32 => new RecordColumn(table, name, type),
+ RecordColumnType.Int64 => new RecordColumn(table, name, type),
+ RecordColumnType.Byte => new RecordColumn(table, name, type),
+ RecordColumnType.UInt16 => new RecordColumn(table, name, type),
+ RecordColumnType.UInt32 => new RecordColumn(table, name, type),
+ RecordColumnType.UInt64 => new RecordColumn(table, name, type),
+ RecordColumnType.Single => new RecordColumn(table, name, type),
+ RecordColumnType.Double => new RecordColumn(table, name, type),
+ RecordColumnType.Decimal => new RecordColumn(table, name, type),
+ RecordColumnType.Char => new RecordColumn(table, name, type),
+ RecordColumnType.String => new RecordColumn(table, name, type),
+ RecordColumnType.DateTime => new RecordColumn(table, name, type),
+ RecordColumnType.DateTimeOffset => new RecordColumn(table, name, type),
+ RecordColumnType.TimeSpan => new RecordColumn(table, name, type),
+ RecordColumnType.Guid => new RecordColumn(table, name, type),
+ RecordColumnType.Binary => new RecordColumn(table, name, type),
+ _ => throw new NotSupportedException($"未知列类型: {columnType}")
+ };
}
}
+
+
diff --git a/src/LuYao.Common/Data/Json/RecordSetJsonConverter.cs b/src/LuYao.Common/Data/Json/RecordSetJsonConverter.cs
deleted file mode 100644
index 574b4a3..0000000
--- a/src/LuYao.Common/Data/Json/RecordSetJsonConverter.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-#if !NET45 && !NET461
-using LuYao.Data.Binary;
-using System;
-using System.IO;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace LuYao.Data.Json;
-
-///
-/// 将 以 Base64 二进制格式进行 JSON 序列化/反序列化。
-///
-public class RecordSetJsonConverter : JsonConverter
-{
- private readonly RecordBinaryPayloadCodec _codec;
-
- /// 使用默认编解码器(GZip 压缩)创建转换器。
- public RecordSetJsonConverter() : this(RecordBinaryPayloadCodec.Default) { }
-
- /// 使用指定编解码器创建转换器。
- /// 编解码器实例。
- public RecordSetJsonConverter(RecordBinaryPayloadCodec codec)
- {
- if (codec == null) throw new ArgumentNullException(nameof(codec));
- _codec = codec;
- }
-
- ///
- public override RecordSet? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.Null) return null;
- if (reader.TokenType == JsonTokenType.String)
- {
- // 在分配字符串/字节数组前先检查 Base64 长度上限,防御超大输入引发的 OOM。
- long base64Length = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;
- if (base64Length > _codec.MaxBase64Length)
- throw new JsonException($"RecordSet Base64 payload exceeds maximum allowed length {_codec.MaxBase64Length}.");
-
- var base64 = reader.GetString()!;
- try
- {
- var bytes = Convert.FromBase64String(base64);
- bytes = _codec.Decode(bytes);
- return RecordSet.FromBytes(bytes);
- }
- catch (FormatException ex)
- {
- throw new JsonException($"Failed to read RecordSet from Base64 JSON string. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (EndOfStreamException ex)
- {
- throw new JsonException($"Failed to read RecordSet from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (InvalidDataException ex)
- {
- throw new JsonException($"Failed to read RecordSet from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (InvalidOperationException ex)
- {
- throw new JsonException($"Failed to read RecordSet from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- }
- throw new JsonException($"Unexpected token type {reader.TokenType} for RecordSet.");
- }
-
- ///
- public override void Write(Utf8JsonWriter writer, RecordSet value, JsonSerializerOptions options)
- {
- if (value == null)
- {
- writer.WriteNullValue();
- return;
- }
- var bytes = _codec.Encode(value.ToBytes());
- writer.WriteStringValue(Convert.ToBase64String(bytes));
- }
-}
-#endif
diff --git a/src/LuYao.Common/Data/Json/RecordTableJsonConverter.cs b/src/LuYao.Common/Data/Json/RecordTableJsonConverter.cs
deleted file mode 100644
index 1f63698..0000000
--- a/src/LuYao.Common/Data/Json/RecordTableJsonConverter.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-#if !NET45 && !NET461
-using LuYao.Data.Binary;
-using System;
-using System.IO;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace LuYao.Data.Json;
-
-///
-/// 将 以 Base64 二进制格式进行 JSON 序列化/反序列化。
-///
-public class RecordTableJsonConverter : JsonConverter
-{
- private readonly RecordBinaryPayloadCodec _codec;
-
- /// 使用默认编解码器(GZip 压缩)创建转换器。
- public RecordTableJsonConverter() : this(RecordBinaryPayloadCodec.Default) { }
-
- /// 使用指定编解码器创建转换器。
- /// 编解码器实例。
- public RecordTableJsonConverter(RecordBinaryPayloadCodec codec)
- {
- if (codec == null) throw new ArgumentNullException(nameof(codec));
- _codec = codec;
- }
-
- ///
- public override RecordTable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.Null) return null;
- if (reader.TokenType == JsonTokenType.String)
- {
- // 在分配字符串/字节数组前先检查 Base64 长度上限,防御超大输入引发的 OOM。
- long base64Length = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;
- if (base64Length > _codec.MaxBase64Length)
- throw new JsonException($"RecordTable Base64 payload exceeds maximum allowed length {_codec.MaxBase64Length}.");
-
- var base64 = reader.GetString()!;
- try
- {
- var bytes = Convert.FromBase64String(base64);
- bytes = _codec.Decode(bytes);
- using var ms = new MemoryStream(bytes, writable: false);
- using var br = new BinaryReader(ms, Encoding.UTF8, leaveOpen: true);
- var record = new RecordTable();
- record.ReadFrom(br);
- return record;
- }
- catch (FormatException ex)
- {
- throw new JsonException($"Failed to read RecordTable from Base64 JSON string. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (EndOfStreamException ex)
- {
- throw new JsonException($"Failed to read RecordTable from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (InvalidDataException ex)
- {
- throw new JsonException($"Failed to read RecordTable from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- catch (InvalidOperationException ex)
- {
- throw new JsonException($"Failed to read RecordTable from binary payload. TokenType={reader.TokenType}, Base64Length={base64.Length}.", ex);
- }
- }
- throw new JsonException($"Unexpected token type {reader.TokenType} for RecordTable.");
- }
-
- ///
- public override void Write(Utf8JsonWriter writer, RecordTable value, JsonSerializerOptions options)
- {
- if (value == null)
- {
- writer.WriteNullValue();
- return;
- }
- using var ms = new MemoryStream();
- using var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
- value.WriteTo(bw);
- bw.Flush();
-
- string base64;
- if (ms.TryGetBuffer(out ArraySegment buffer) && buffer.Array != null)
- base64 = Convert.ToBase64String(_codec.Encode(buffer.Array, buffer.Offset, (int)ms.Length));
- else
- base64 = Convert.ToBase64String(_codec.Encode(ms.ToArray()));
- writer.WriteStringValue(base64);
- }
-}
-#endif
diff --git a/src/LuYao.Common/Data/Mapping/ColumnNameResolver.cs b/src/LuYao.Common/Data/Mapping/ColumnNameResolver.cs
deleted file mode 100644
index 64f0a94..0000000
--- a/src/LuYao.Common/Data/Mapping/ColumnNameResolver.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using LuYao.Data.Attributes;
-using LuYao.Data.Meta;
-using System.Reflection;
-
-namespace LuYao.Data.Mapping;
-
-///
-/// 根据 解析属性对应的列名。
-/// 优先级: > > 属性原名。
-///
-internal static class ColumnNameResolver
-{
- ///
- /// 解析 对应的列名。
- ///
- /// 属性描述符。
- /// 映射选项;为 时使用属性原名。
- /// 解析后的列名。
- internal static string Resolve(XProp prop, RecordMappingOptions? options)
- {
- // 优先级1:RecordColumnNameAttribute
- var attr = prop.GetCustomAttribute();
- if (attr != null) return attr.Name;
-
- // 优先级2:NamingPolicy
- if (options?.NamingPolicy != null) return options.NamingPolicy.ConvertName(prop.Name);
-
- // 优先级3:属性原名
- return prop.Name;
- }
-}
diff --git a/src/LuYao.Common/Data/Mapping/RecordMappingContext.cs b/src/LuYao.Common/Data/Mapping/RecordMappingContext.cs
deleted file mode 100644
index b34fa5f..0000000
--- a/src/LuYao.Common/Data/Mapping/RecordMappingContext.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-using LuYao.Data.Internal;
-using LuYao.Data.Meta;
-using System;
-
-namespace LuYao.Data.Mapping;
-
-///
-/// Encapsulates the execution context for a single mapping operation.
-/// Holds a instance and centralises all
-/// DTO � mapping logic.
-///
-///
-/// All overloads ultimately delegate to this class,
-/// keeping the logic in one place.
-/// The options are frozen via
-/// at construction time.
-///
-internal sealed class RecordMappingContext
-{
- private readonly RecordMappingOptions _options;
-
- ///
- /// Creates a mapping context with the specified options and immediately freezes them.
- ///
- /// Mapping options; must not be null.
- internal RecordMappingContext(RecordMappingOptions options)
- {
- _options = options ?? throw new ArgumentNullException(nameof(options));
- _options.MakeReadOnly();
- }
-
- // ─── DTO â†?RecordRow (map to existing columns, no auto-create) ───────────────
-
- ///
- /// Maps readable properties of on
- /// into the corresponding columns of .
- /// Columns that do not exist are silently skipped; no columns are created.
- ///
- internal void MapDtoToRow(Type type, object data, RecordRow target)
- {
- var cols = target.Table.Columns;
- foreach (var prop in XProp.GetAll(type))
- {
- if (!prop.CanRead) continue;
-
- var colName = ColumnNameResolver.Resolve(prop, _options);
- var col = cols.Find(colName);
- if (col == null) continue;
-
- if (Helpers.IsSupportedForReading(prop))
- {
- col.Set(target, prop.GetValue(data));
- continue;
- }
-
- HandleUnsupportedTypeForWrite(prop);
- }
- }
-
- // ─── DTO â†?RecordRow (auto-create columns) ───────────────────────────────────
-
- ///
- /// Writes readable properties of on
- /// into the corresponding columns of .
- /// Missing columns are created automatically before writing.
- ///
- internal void WriteDtoToRow(Type type, object data, RecordRow target)
- {
- var cols = target.Table.Columns;
- foreach (var prop in XProp.GetAll(type))
- {
- if (!prop.CanRead) continue;
-
- if (Helpers.IsSupportedForReading(prop))
- {
- var colName = ColumnNameResolver.Resolve(prop, _options);
- var col = cols.Find(colName) ?? cols.Add(colName, prop.Type);
- col.Set(target, prop.GetValue(data));
- continue;
- }
-
- HandleUnsupportedTypeForWrite(prop);
- }
- }
-
- // ─── RecordRow â†?DTO ─────────────────────────────────────────────────────────
-
- ///
- /// Maps column values from into the writable properties
- /// of . Columns that do not exist are silently skipped.
- ///
- internal void MapRowToDto(Type type, object data, RecordRow source)
- {
- var cols = source.Table.Columns;
- foreach (var prop in XProp.GetAll(type))
- {
- if (!prop.CanWrite) continue;
-
- var colName = ColumnNameResolver.Resolve(prop, _options);
- var col = cols.Find(colName);
- if (col == null) continue;
-
- var rawValue = col.Get(source);
-
- if (!Helpers.IsSupportedForWriting(prop))
- {
- HandleConversionFailure(
- new NotSupportedException(
- $"Property '{prop.Name}' has type '{prop.Type.FullName}' which is not supported."));
- continue;
- }
-
- if (col.Type == prop.Type)
- {
- TrySetValue(data, prop, rawValue);
- continue;
- }
-
- // Types differ (e.g. column is int, DTO property is decimal).
- // Use Convert.ChangeType as a best-effort numeric/primitive coercion,
- // mirroring the strategy used by Dapper and similar micro-ORMs.
- TrySetValueWithConversion(data, prop, rawValue);
- }
- }
-
- // ─── AddFrom (column definition) ─────────────────────────────────────────────
-
- ///
- /// Appends column definitions to based on the readable
- /// properties of .
- ///
- internal void AddColumnsFrom(RecordColumnCollection columns) where T : class
- => AddColumnsFrom(typeof(T), columns);
-
- ///
- /// Appends column definitions to based on the readable
- /// properties of .
- ///
- internal void AddColumnsFrom(Type type, RecordColumnCollection columns)
- {
- foreach (var prop in XProp.GetAll(type))
- {
- if (!prop.CanRead) continue;
-
- if (Helpers.IsSupportedForReading(prop))
- {
- var colName = ColumnNameResolver.Resolve(prop, _options);
- columns.Add(colName, prop.Type);
- continue;
- }
-
- HandleUnsupportedTypeForWrite(prop);
- }
- }
-
- private void TrySetValue(object data, XProp prop, object? value)
- {
- try
- {
- prop.SetValue(data, value);
- }
- catch (Exception ex) when (_options.ConversionFailureHandling == ConversionFailureHandling.Skip)
- {
- _ = ex;
- }
- }
-
- ///
- /// Attempts to set on after
- /// coercing it to the property's declared type via .
- /// Falls back to direct assignment when the value is already assignable (e.g. null or subtype).
- /// Obeys on failure.
- ///
- private void TrySetValueWithConversion(object data, XProp prop, object? value)
- {
- try
- {
- if (value is null or DBNull)
- {
- prop.SetValue(data, null);
- return;
- }
-
- prop.SetValue(data, DataConvert.ChangeType(value, prop.Type));
- }
- catch (Exception ex)
- {
- HandleConversionFailure(
- new InvalidCastException(
- $"Cannot convert value '{value}' ({value?.GetType().FullName}) to property '{prop.Name}' of type '{prop.Type.FullName}'.",
- ex));
- }
- }
-
- private void HandleUnsupportedTypeForWrite(XProp prop)
- {
- if (_options.UnsupportedTypeHandling == UnsupportedTypeHandling.Throw)
- throw new NotSupportedException(
- $"Property '{prop.Name}' has type '{prop.Type.FullName}' which is not supported and cannot be written to a RecordTable.");
- // Skip: silently ignore
- }
-
- private void HandleConversionFailure(Exception inner)
- {
- if (_options.ConversionFailureHandling == ConversionFailureHandling.Throw)
- throw inner;
- // Skip: silently ignore
- }
-
-
-}
diff --git a/src/LuYao.Common/Data/Mapping/RecordMappingOptions.cs b/src/LuYao.Common/Data/Mapping/RecordMappingOptions.cs
deleted file mode 100644
index 3d3208e..0000000
--- a/src/LuYao.Common/Data/Mapping/RecordMappingOptions.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using LuYao.Data.Attributes;
-using System;
-
-namespace LuYao.Data.Mapping;
-
-///
-/// 控制 与 DTO 之间映射行为的选项类。
-///
-///
-///
-/// 实例首次参与映射任务后将被冻结(只读),之后任何属性赋值或 调用
-/// 均会抛出 。
-///
-///
-/// 可通过 获取全局默认实例;也可自行创建实例并在首次使用前配置。
-///
-///
-public sealed class RecordMappingOptions
-{
- // ─── 静态默认实例 ───────────────────────────────────────────────────────────
-
- /// 全局默认选项实例。
- public static RecordMappingOptions Default { get; } = new RecordMappingOptions();
-
- // ─── 只读控制 ────────────────────────────────────────────────────────────────
-
- private bool _isReadOnly;
-
- ///
- /// 将当前实例标记为只读(冻结)。首次映射任务开始时由框架自动调用,调用后属性不可再更改。
- ///
- internal void MakeReadOnly()
- {
- _isReadOnly = true;
- }
-
- /// 是否已被冻结(只读)。
- public bool IsReadOnly => _isReadOnly;
-
- private void ThrowIfReadOnly()
- {
- if (_isReadOnly)
- throw new InvalidOperationException(
- "RecordMappingOptions 已被冻结,首次参与映射后不可再修改。");
- }
-
- // ─── NamingPolicy ────────────────────────────────────────────────────────────
-
- private RecordNamingPolicy? _namingPolicy;
-
- ///
- /// 列名转换策略。 表示使用属性原名(除非有 标记)。
- ///
- public RecordNamingPolicy? NamingPolicy
- {
- get => _namingPolicy;
- set { ThrowIfReadOnly(); _namingPolicy = value; }
- }
-
- // ─── UnsupportedTypeHandling ─────────────────────────────────────────────────
-
- private UnsupportedTypeHandling _unsupportedTypeHandling = UnsupportedTypeHandling.Skip;
-
- ///
- /// DTO → RecordTable 时遇到不支持的属性类型的处理策略,默认为 。
- ///
- public UnsupportedTypeHandling UnsupportedTypeHandling
- {
- get => _unsupportedTypeHandling;
- set { ThrowIfReadOnly(); _unsupportedTypeHandling = value; }
- }
-
- // ─── ConversionFailureHandling ───────────────────────────────────────────────
-
- private ConversionFailureHandling _conversionFailureHandling = ConversionFailureHandling.Skip;
-
- ///
- /// RecordTable → DTO 时列值类型转换失败的处理策略,默认为 。
- ///
- public ConversionFailureHandling ConversionFailureHandling
- {
- get => _conversionFailureHandling;
- set { ThrowIfReadOnly(); _conversionFailureHandling = value; }
- }
-}
diff --git a/src/LuYao.Common/Data/Mapping/XCopy.cs b/src/LuYao.Common/Data/Mapping/XCopy.cs
deleted file mode 100644
index 1c3e225..0000000
--- a/src/LuYao.Common/Data/Mapping/XCopy.cs
+++ /dev/null
@@ -1,284 +0,0 @@
-using LuYao.Data.Meta;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-
-namespace LuYao.Data.Mapping;
-
-///
-/// Static utility class for bidirectional property mapping between objects and .
-/// Property lists are cached keyed by runtime type, so derived-class properties are handled naturally.
-///
-///
-/// All mapping logic is delegated to .
-/// Overloads without options are equivalent to using .
-///
-public static class XCopy
-{
- private static readonly ConcurrentDictionary> _readableCache
- = new ConcurrentDictionary>();
- private static readonly ConcurrentDictionary> _writableCache
- = new ConcurrentDictionary>();
-
- internal static IReadOnlyList GetReadableProps(Type runtimeType)
- => _readableCache.GetOrAdd(runtimeType, t =>
- {
- var all = XProp.GetAll(t);
- var list = new List(all.Count);
- foreach (var p in all)
- {
- if (Helpers.IsSupportedForReading(p)) list.Add(p);
- }
- return list.AsReadOnly();
- });
-
- internal static IReadOnlyList GetWritableProps(Type runtimeType)
- => _writableCache.GetOrAdd(runtimeType, t =>
- {
- var all = XProp.GetAll(t);
- var list = new List(all.Count);
- foreach (var p in all)
- {
- if (Helpers.IsSupportedForWriting(p)) list.Add(p);
- }
- return list.AsReadOnly();
- });
-
- #region RecordRow
-
- ///
- /// Maps readable properties of into the corresponding columns of
- /// . Uses the runtime type to scan properties.
- /// Columns that do not exist are silently skipped; no columns are created.
- ///
- /// Source object; must not be null.
- /// Target row.
- /// Thrown when is null.
- public static void MapTo(object data, RecordRow target)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapTo(data.GetType(), data, target, RecordMappingOptions.Default);
- }
-
- ///
- /// Maps readable properties of the -declared members on
- /// into the corresponding columns of .
- /// Columns that do not exist are silently skipped; no columns are created.
- ///
- /// Declared type used to scan properties; must not be null.
- /// Source object; must not be null.
- /// Target row.
- /// Thrown when or is null.
- public static void MapTo(Type type, object data, RecordRow target)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapTo(type, data, target, RecordMappingOptions.Default);
- }
-
- ///
- /// Maps readable properties of into the corresponding columns of
- /// using the specified options.
- /// Columns that do not exist are silently skipped; no columns are created.
- ///
- /// Source object; must not be null.
- /// Target row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void MapTo(object data, RecordRow target, RecordMappingOptions options)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapTo(data.GetType(), data, target, options);
- }
-
- ///
- /// Maps readable properties of the -declared members on
- /// into the corresponding columns of using the specified options.
- /// Columns that do not exist are silently skipped; no columns are created.
- ///
- /// Declared type used to scan properties; must not be null.
- /// Source object; must not be null.
- /// Target row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void MapTo(Type type, object data, RecordRow target, RecordMappingOptions options)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (options == null) throw new ArgumentNullException(nameof(options));
- new RecordMappingContext(options).MapDtoToRow(type, data, target);
- }
-
- ///
- /// Maps column values from that correspond to object properties
- /// into . Uses the runtime type to scan properties.
- ///
- /// Target object; must not be null.
- /// Source row.
- /// Thrown when is null.
- public static void MapFrom(object data, RecordRow source)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapFrom(data.GetType(), data, source, RecordMappingOptions.Default);
- }
-
- ///
- /// Maps column values from that correspond to
- /// -declared properties into .
- ///
- /// Declared type used to scan properties; must not be null.
- /// Target object; must not be null.
- /// Source row.
- /// Thrown when or is null.
- public static void MapFrom(Type type, object data, RecordRow source)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapFrom(type, data, source, RecordMappingOptions.Default);
- }
-
- ///
- /// Maps column values from into
- /// using the specified options.
- ///
- /// Target object; must not be null.
- /// Source row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void MapFrom(object data, RecordRow source, RecordMappingOptions options)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- MapFrom(data.GetType(), data, source, options);
- }
-
- ///
- /// Maps column values from that correspond to
- /// -declared properties into
- /// using the specified options (supports custom converters and column-name strategies).
- ///
- /// Declared type used to scan properties; must not be null.
- /// Target object; must not be null.
- /// Source row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void MapFrom(Type type, object data, RecordRow source, RecordMappingOptions options)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (options == null) throw new ArgumentNullException(nameof(options));
- new RecordMappingContext(options).MapRowToDto(type, data, source);
- }
-
- ///
- /// Writes readable properties of into the corresponding columns of
- /// , creating missing columns automatically.
- /// Uses the runtime type to scan properties.
- ///
- /// Source object; must not be null.
- /// Target row.
- /// Thrown when is null.
- public static void WriteTo(object data, RecordRow target)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- WriteTo(data.GetType(), data, target, RecordMappingOptions.Default);
- }
-
- ///
- /// Writes readable properties of the -declared members on
- /// into the corresponding columns of , creating missing columns automatically.
- ///
- /// Declared type used to scan properties; must not be null.
- /// Source object; must not be null.
- /// Target row.
- /// Thrown when or is null.
- public static void WriteTo(Type type, object data, RecordRow target)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- WriteTo(type, data, target, RecordMappingOptions.Default);
- }
-
- ///
- /// Writes readable properties of into the corresponding columns of
- /// (auto-creating columns), using the specified options.
- ///
- /// Source object; must not be null.
- /// Target row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void WriteTo(object data, RecordRow target, RecordMappingOptions options)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- WriteTo(data.GetType(), data, target, options);
- }
-
- ///
- /// Writes readable properties of the -declared members on
- /// into the corresponding columns of (auto-creating columns),
- /// using the specified options.
- ///
- /// Declared type used to scan properties; must not be null.
- /// Source object; must not be null.
- /// Target row.
- /// Mapping options; must not be null.
- /// Thrown when any argument is null.
- public static void WriteTo(Type type, object data, RecordRow target, RecordMappingOptions options)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (options == null) throw new ArgumentNullException(nameof(options));
- new RecordMappingContext(options).WriteDtoToRow(type, data, target);
- }
-
- #endregion
-}
-
-///
-/// Generic thin wrapper over that fixes the scan type to the
-/// compile-time type . All logic is delegated to the explicit
-/// overloads of .
-///
-/// The object type to map; must be a reference type.
-public static class XCopy where T : class
-{
- #region RecordRow
- ///
- /// Maps readable properties of into the corresponding columns of
- /// . Always scans using the compile-time type ;
- /// derived-class properties are not processed even if the runtime type is a subclass.
- ///
- /// Source object.
- /// Target row; columns that do not exist are silently skipped, no columns are created.
- public static void MapTo(T data, RecordRow target) => XCopy.MapTo(typeof(T), data, target);
-
- /// Maps readable properties of into the corresponding columns of using the specified options.
- public static void MapTo(T data, RecordRow target, RecordMappingOptions options) => XCopy.MapTo(typeof(T), data, target, options);
-
- ///
- /// Maps column values from that match object properties
- /// into . Always scans using the compile-time type ;
- /// derived-class properties are not processed even if the runtime type is a subclass.
- ///
- /// Target object.
- /// Source row.
- public static void MapFrom(T data, RecordRow source) => XCopy.MapFrom(typeof(T), data, source);
-
- /// Maps column values from into using the specified options.
- public static void MapFrom(T data, RecordRow source, RecordMappingOptions options) => XCopy.MapFrom(typeof(T), data, source, options);
-
- ///
- /// Writes readable properties of into the corresponding columns of
- /// , creating missing columns automatically.
- /// Always scans using the compile-time type ;
- /// derived-class properties are not processed even if the runtime type is a subclass.
- ///
- /// Source object.
- /// Target row.
- public static void WriteTo(T data, RecordRow target) => XCopy.WriteTo(typeof(T), data, target);
-
- /// Writes readable properties of into the corresponding columns of (auto-creating columns) using the specified options.
- public static void WriteTo(T data, RecordRow target, RecordMappingOptions options) => XCopy.WriteTo(typeof(T), data, target, options);
-
- #endregion
-}
-
diff --git a/src/LuYao.Common/Data/Meta/IXProp.cs b/src/LuYao.Common/Data/Meta/IXProp.cs
deleted file mode 100644
index 9509425..0000000
--- a/src/LuYao.Common/Data/Meta/IXProp.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-
-namespace LuYao.Data.Meta;
-
-///
-/// 表示一个可按对象实例读写的属性描述符。
-///
-///
-/// 由 (基于反射)和 (基于列存储)实现。
-/// GetValue/SetValue 的 instance 参数语义取决于具体实现:
-/// 对于 ,instance 为承载属性的 CLR 对象;
-/// 对于 ,instance 为 (值类型装箱后传入)。
-///
-public interface IXProp
-{
- /// 属性名称。
- string Name { get; }
-
- /// 属性类型。
- Type Type { get; }
-
- /// 是否可读。
- bool CanRead { get; }
-
- /// 是否可写。
- bool CanWrite { get; }
-
- ///
- /// 读取指定实例的属性值。
- ///
- /// 属性所属的对象实例。
- /// 属性当前值。
- /// 属性不可读时抛出。
- object? GetValue(object instance);
-
- ///
- /// 设置指定实例的属性值。
- ///
- /// 属性所属的对象实例。
- /// 要写入的值。
- /// 属性不可写时抛出。
- void SetValue(object instance, object? value);
-}
diff --git a/src/LuYao.Common/Data/Meta/XData.cs b/src/LuYao.Common/Data/Meta/XData.cs
deleted file mode 100644
index d53d740..0000000
--- a/src/LuYao.Common/Data/Meta/XData.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace LuYao.Data.Meta;
-
-///
-/// 提供对强类型对象属性的按名称读写操作,底层使用 加速反射访问。
-///
-/// 目标类型,必须具有无参构造函数。
-public static class XData where T : class
-{
- private static readonly IReadOnlyList _props = XProp.GetAll(typeof(T));
-
- private static XProp Get(string name)
- {
- foreach (var p in _props)
- {
- if (string.Equals(p.Name, name, StringComparison.Ordinal))
- {
- return p;
- }
- }
- throw new ArgumentException($"类型 {typeof(T).FullName} 上未找到属性 '{name}'。", nameof(name));
- }
-
- ///
- /// 按属性名写入值。
- ///
- /// 目标对象实例。
- /// 属性名称(大小写敏感)。
- /// 要写入的值。
- /// 当 为 null 时抛出。
- /// 属性不存在时抛出。
- /// 属性不可写时抛出。
- public static void Set(T data, string name, object? value)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- Get(name).SetValue(data, value);
- }
-
- ///
- /// 按属性名读取值。
- ///
- /// 目标对象实例。
- /// 属性名称(大小写敏感)。
- /// 属性当前值。
- /// 当 为 null 时抛出。
- /// 属性不存在时抛出。
- /// 属性不可读时抛出。
- public static object? Get(T data, string name)
- {
- if (data == null) throw new ArgumentNullException(nameof(data));
- return Get(name).GetValue(data);
- }
-}
diff --git a/src/LuYao.Common/Data/Meta/XProp.cs b/src/LuYao.Common/Data/Meta/XProp.cs
deleted file mode 100644
index 25ca9f6..0000000
--- a/src/LuYao.Common/Data/Meta/XProp.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Reflection;
-
-namespace LuYao.Data.Meta;
-
-
-///
-/// 封装某个类型的单个公共属性,提供快速的反射读写访问(通过编译的委托实现)。
-///
-public class XProp : IXProp
-{
- private static readonly ConcurrentDictionary> _cache =
- new ConcurrentDictionary>();
-
- ///
- /// 扫描指定类型的所有公共实例属性,并以缓存方式返回对应的 列表。
- ///
- /// 要扫描的目标类型。
- /// 该类型所有公共实例属性对应的 只读列表。
- /// 当 为 null 时抛出。
- public static IReadOnlyList GetAll(Type type)
- {
- if (type == null) throw new ArgumentNullException(nameof(type));
- return _cache.GetOrAdd(type, static t =>
- {
- var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
- var list = new List(props.Length);
- foreach (var p in props)
- {
- if (p.GetIndexParameters().Length > 0) continue; // 跳过索引器属性
- list.Add(new XProp(p));
- }
- return list.AsReadOnly();
- });
- }
-
- private readonly Func