为业务实体扩展Azure存储表所需属性应采用哪种设计模式?
问题描述
我有多个业务实体,需要为每个实体创建对应的Azure Storage Table实体类,这类实体要额外实现ITableEntity接口的几个必填属性(PartitionKey、RowKey、Timestamp、ETag)。目前的写法是每个业务实体都派生一个AzureTable版本,比如:
// MyData 是包含若干属性的业务实体 public record MyData_AzureTable : MyData, ITableEntity { // Azure Storage Table必填属性 public string PartitionKey { get; set; } = ""; public string RowKey { get; set; } = ""; public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } = new ETag(); }
但这种重复创建类的方式太繁琐,我尝试过用泛型继承的方式,但C#不支持从类型参数继承,写法不合法:
public record AzureTable<T> : T, ITableEntity { public string PartitionKey { get; set; } = ""; public string RowKey { get; set; } = ""; public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } = new ETag(); }
想知道应该用哪种设计模式来避免重复,同时要保证保存到Azure Table Storage的对象是扁平化结构(属性都是表格支持的基础数据类型,无嵌套层次)。
可行的解决方案
1. 基类继承模式(适用于可修改业务实体的场景)
如果业务实体的代码可修改,直接定义一个包含Table属性的抽象基类,让所有业务实体继承它并实现ITableEntity接口:
public abstract record BaseTableEntity : ITableEntity { public string PartitionKey { get; set; } = ""; public string RowKey { get; set; } = ""; public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } = new ETag(); } // 业务实体直接继承基类 public record MyData : BaseTableEntity { // 业务属性 public string ProductName { get; set; } = ""; public decimal Price { get; set; } }
这种方式最直接,完全避免重复代码,且实体结构天然扁平化,符合Azure Table的要求。但前提是你有权修改所有业务实体的继承关系。
2. 映射器+DynamicTableEntity模式(适用于不可修改业务实体的场景)
利用Azure SDK提供的DynamicTableEntity类,配合通用映射工具实现业务实体与Table实体的转换,无需创建额外的实体类:
public static class AzureTableMapper { public static DynamicTableEntity ToTableEntity<T>(T businessEntity, string partitionKey, string rowKey) { var tableEntity = new DynamicTableEntity(partitionKey, rowKey); // 反射遍历业务实体属性,转换为Table支持的EntityProperty foreach (var prop in typeof(T).GetProperties()) { var value = prop.GetValue(businessEntity); tableEntity.Properties[prop.Name] = EntityProperty.CreateEntityPropertyFromObject(value); } return tableEntity; } public static T FromTableEntity<T>(DynamicTableEntity tableEntity) where T : new() { var businessEntity = new T(); foreach (var prop in typeof(T).GetProperties()) { if (tableEntity.Properties.TryGetValue(prop.Name, out var entityProp)) { prop.SetValue(businessEntity, entityProp.PropertyAsObject); } } return businessEntity; } }
使用示例:
// 业务实体转Table实体 var myData = new MyData { ProductName = "Laptop", Price = 999.99m }; var tableEntity = AzureTableMapper.ToTableEntity(myData, "Electronics", "Laptop_001"); await tableClient.AddEntityAsync(tableEntity); // Table实体转业务实体 var retrievedEntity = await tableClient.GetEntityAsync<DynamicTableEntity>("Electronics", "Laptop_001"); var retrievedData = AzureTableMapper.FromTableEntity<MyData>(retrievedEntity.Value);
这种模式完全无需额外创建实体类,通过反射自动完成属性映射,DynamicTableEntity天然是扁平化结构,完全符合Azure Table的要求。
3. 组合+源生成器模式(追求类型安全的场景)
如果既不能修改业务实体,又想要类型安全的Table实体,可以用组合模式配合Roslyn源生成器自动生成包装类:
- 定义一个通用包装类模板,包含Table必填属性和业务实体的引用
- 用源生成器遍历所有业务实体,自动生成对应包装类的属性映射代码
示例模板思路:
// 源生成器自动生成的代码示例 public record MyData_AzureTable : ITableEntity { public string PartitionKey { get; set; } = ""; public string RowKey { get; set; } = ""; public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } = new ETag(); // 自动生成的业务属性映射 public string ProductName { get => _businessEntity.ProductName; set => _businessEntity.ProductName = value; } public decimal Price { get => _businessEntity.Price; set => _businessEntity.Price = value; } private MyData _businessEntity; public MyData_AzureTable(MyData businessEntity) { _businessEntity = businessEntity; } }
这种方式既保证了类型安全,又避免了手动重复编写包装类,生成的实体结构也是扁平化的。
总结
- 能修改业务实体:优先用基类继承模式,简单直接
- 不能修改业务实体且追求灵活性:用映射器+DynamicTableEntity模式
- 追求类型安全且不愿手动重复编码:用组合+源生成器模式
内容的提问来源于stack exchange,提问作者Bruno
相关产品推荐
相关产品推荐

