如何让TableEntity序列化非公共属性?Azure Table Storage decimal兼容问题
解决Azure Table Storage序列化非公共成员(及Decimal类型支持)的无反射方案
我之前也踩过Azure Table Storage不支持Decimal类型的坑,同时也遇到过默认序列化只处理公共成员的问题,这里给你两个不用反射的简便解决思路:
方案一:手动实现ITableEntity接口,完全控制序列化逻辑
这是最直接且无反射的方案,通过实现ITableEntity的ReadEntity和WriteEntity方法,你可以手动处理私有成员的读写,同时完美解决Decimal类型的存储问题(因为Table Storage原生不支持Decimal,我们可以转成字符串存储避免精度损失)。
示例代码:
using Azure; using Azure.Data.Tables; using System.Globalization; public class Product : ITableEntity { // 私有成员,用于存储Decimal类型的ListPrice private decimal _listPrice; // 业务用的公共属性,对外暴露Decimal类型 public decimal ListPrice { get => _listPrice; set => _listPrice = value; } // ITableEntity 必备成员 public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } // 从Table Storage读取数据时的自定义逻辑 public void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext) { // 读取并转换存储的字符串为Decimal if (properties.TryGetValue("ListPrice", out var priceProperty)) { _listPrice = decimal.Parse(priceProperty.StringValue, CultureInfo.InvariantCulture); } // 处理其他TableEntity属性 PartitionKey = properties["PartitionKey"].StringValue; RowKey = properties["RowKey"].StringValue; Timestamp = properties["Timestamp"].DateTimeOffsetValue; ETag = properties["ETag"].ETagValue; } // 向Table Storage写入数据时的自定义逻辑 public IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext) { var properties = new Dictionary<string, EntityProperty>(); // 将Decimal转成不变文化的字符串存储,避免精度丢失 properties["ListPrice"] = EntityProperty.GeneratePropertyForString(_listPrice.ToString(CultureInfo.InvariantCulture)); // 写入其他TableEntity属性 properties["PartitionKey"] = EntityProperty.GeneratePropertyForString(PartitionKey); properties["RowKey"] = EntityProperty.GeneratePropertyForString(RowKey); properties["Timestamp"] = EntityProperty.GeneratePropertyForDateTimeOffset(Timestamp ?? DateTimeOffset.UtcNow); properties["ETag"] = EntityProperty.GeneratePropertyForETag(ETag); return properties; } }
这个方案的优势是完全可控,没有反射开销,还能精准处理Decimal的精度问题,适合对数据准确性要求高的场景。
方案二:配置自定义序列化器(新SDK适用)
如果你使用的是Azure.Data.Tables新SDK,可以配置使用Newtonsoft.Json作为序列化器,并设置它支持序列化非公共成员。虽然底层序列化器会用到反射,但你不需要自己写反射代码,属于“无需手动写反射”的简便方案:
示例代码:
using Azure.Data.Tables; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Reflection; // 配置序列化器选项 var serializerOptions = new TableSerializerOptions { Serializer = new NewtonsoftJsonObjectSerializer(new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { // 设置扫描非公共成员 DefaultMembersSearchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic } }) }; // 使用配置好的选项创建TableClient var tableClient = new TableClient( connectionString: "你的连接字符串", tableName: "你的表名", options: new TableClientOptions { Serializer = serializerOptions } );
然后你的实体类可以保留私有成员,同时处理Decimal的话,可以用Newtonsoft.Json的JsonConverter来自动转换Decimal为字符串:
public class Product : ITableEntity { [JsonConverter(typeof(DecimalToStringConverter))] private decimal _listPrice; public decimal ListPrice { get => _listPrice; set => _listPrice = value; } // ITableEntity成员... public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } } // 自定义Json转换器,处理Decimal和字符串的转换 public class DecimalToStringConverter : JsonConverter<decimal> { public override decimal ReadJson(JsonReader reader, Type objectType, decimal existingValue, bool hasExistingValue, JsonSerializer serializer) { return decimal.Parse(reader.Value.ToString(), CultureInfo.InvariantCulture); } public override void WriteJson(JsonWriter writer, decimal value, JsonSerializer serializer) { writer.WriteValue(value.ToString(CultureInfo.InvariantCulture)); } }
总结
如果追求完全无反射且高度可控,优先选方案一;如果想少写代码,且能接受序列化器底层的反射(无需自己实现),方案二更简便。两种方案都能解决你不想暴露私有成员ListPrice_,同时让SDK序列化它的问题,还顺便解决了Decimal类型的存储限制。
内容的提问来源于stack exchange,提问作者UserControl
相关产品推荐
相关产品推荐

