C# 使用TableEntity(Azure表存储)的最佳实践-类的解耦
我现在正在一个更大的项目中进行原型设计,需要做出一些设计决策 我的VS解决方案由4个项目组成(目前可能会在以后进一步分离):C# 使用TableEntity(Azure表存储)的最佳实践-类的解耦,c#,asp.net-core,design-patterns,azure-storage,blazor,C#,Asp.net Core,Design Patterns,Azure Storage,Blazor,我现在正在一个更大的项目中进行原型设计,需要做出一些设计决策 我的VS解决方案由4个项目组成(目前可能会在以后进一步分离): 一个.NET标准2.1项目,包含所有my实体(例如客户类),其中大部分包含简单属性,但有些还包含枚举和列表。我希望这是没有依赖性的简单 使用ASP.NET Core 3.1和Blazor WebAssembly(仅客户端)的Web项目 包含基础架构和服务的应用程序项目(例如,AzureTableStorage所在的位置) Azure功能层,它是web和应用程序之间的“中
- 一个.NET标准2.1项目,包含所有my实体(例如客户类),其中大部分包含简单属性,但有些还包含枚举和列表。我希望这是没有依赖性的简单
- 使用ASP.NET Core 3.1和Blazor WebAssembly(仅客户端)的Web项目
- 包含基础架构和服务的应用程序项目(例如,
所在的位置)AzureTableStorage
- Azure功能层,它是web和应用程序之间的“中介”,依赖于应用程序层(注入CustomerService)
TableEntity
,这很烦人。此外,我的WebUI使用Blazor,因此如果我选择继承TableEntity
,它会将一堆Azure Cosmos dll引入浏览器,而这是不需要的
因此,我无法决定是否只需要对poco类进行解耦,然后去掉TableEntity
。。我看到了一些关于使用TableEntityAdapter
的内容,但找不到任何使用它的示例
另一种方法可能是让Dto“复制”我的POCO类的类,然后这些类可以继承TableEntity。但是我需要维护这些类。但可能需要这样做,因为我认为Azure存储库中的方法无法处理开箱即用的列表、枚举等。但是,如果我可以制作一些通用适配器来处理更复杂的类型,那么Dto类可能是多余的
基本上是在寻找投入和灵感:)
谢谢好的,我现在已经找到了一种方法。我在这里分享 问题是,你可以把桌子规范化,也就是说。将实体及其所有嵌套对象和列表属性设置为1个tableentity=>1行。这意味着复杂属性或类类型属性需要序列化为字符串(最有可能是json) 或者,您可以建立关系,其中实体共享相同的partitionkey。然后批量创建它们。例如:部门->人员。所以一个部门的partionkey=Department01,而人X的partionkey=Department01。两排 但是如果你真的想同时做这两件事,比如说有不同的行,但是每个tableentity也有IEnumerable属性,比如列表和集合,如果把它们分成不同的行,那就太过分了 我发现了这个很棒的社区库,并对其进行了扩展,创建了两个通用方法。它们并不完美,但这只是一个开始 在这里,您可以轻松地将POCO实体转换为TableEntity,反之亦然。非规范化 我将以下两种通用方法添加到库中:
/// <summary>
/// Adds relationship One To Many between source (one) and related entitiy targets (many). Source and related targets have seperate rows but share the same partition key
/// </summary>
/// <typeparam name="TTarget">Target class, ex. Persons</typeparam>
/// <param name="entitySource">Source entity that only has one entry</param>
/// <param name="relatedEntities">Related entities contained in source entity, this can be 0 to many, ex. e => e.Persons</param>
/// <param name="entityTargetRowKey">Target entity rowkey property, needs to be different than source rowkey</param>
/// <returns></returns>
public async Task InsertBatchOneToMany<TTarget>(T entitySource, Expression<Func<T, IEnumerable<TTarget>>> relatedEntities, Expression<Func<TTarget, string>> entityTargetRowKey) where TTarget : class
{
try
{
//TODO: Put related property on ignorelist for json serializing
//Create the batch operation
TableBatchOperation batchOperation = new TableBatchOperation();
IEnumerable<TTarget> targets = relatedEntities.Compile().Invoke(entitySource);
//Insert source entity to batch
DynamicTableEntity source = CreateEntity(entitySource);
batchOperation.InsertOrMerge(source);
//Insert target entities to batch
foreach (var entityTarget in targets)
{
string trowKey = entityTargetRowKey.Compile().Invoke(entityTarget);
batchOperation.InsertOrMerge(entityTarget.ToTableEntity(source.PartitionKey, trowKey));
}
//Execute batch
IList<TableResult> results = await _table.ExecuteBatchAsync(batchOperation);
}
catch (StorageException ex)
{
throw new StorageException($"Error saving data to Table." +
$"{ System.Environment.NewLine}Error Message: {ex.Message}" +
$"{ System.Environment.NewLine}Error Extended Information: {ex.RequestInformation.ExtendedErrorInformation.ErrorMessage}" +
$"{ System.Environment.NewLine}Error Code: {ex.RequestInformation.ExtendedErrorInformation.ErrorCode}");
}
}
/// <summary>
/// Retrieve source and its related target entities back again to source
/// </summary>
/// <typeparam name="TTarget">Related Entity</typeparam>
/// <param name="partitionKey">Partionkey shared by source and related target entities</param>
/// <param name="relatedEntities">Related entities contained in source entity, ex. e => e.Persons</param>
/// <returns></returns>
public async Task<T> GetBatchOneToMany<TTarget>(string partitionKey, Expression<Func<T, IEnumerable<TTarget>>> relatedEntities) where TTarget : class, new()
{
var dynTableEntities = await _tableStore.GetByPartitionKeyAsync(partitionKey);
T convertSource = new T();
TTarget convertTarget = new TTarget();
var targetObjects = new List<TTarget>();
MemberExpression member = relatedEntities.Body as MemberExpression;
PropertyInfo propInfo = member.Member as PropertyInfo;
IEnumerable<TTarget> targets = relatedEntities.Compile().Invoke(convertSource);
bool sourceFound = false;
foreach (var dynTableEntity in dynTableEntities)
{
//Try convert to source
int nonNullValuesSource = 0;
int nonNullValuesTarget = 0;
if (!sourceFound)
{
convertSource = dynTableEntity.FromTableEntity<T>();
nonNullValuesSource = convertSource.GetType().GetProperties().Select(x => x.GetValue(convertSource)).Count(v => v != null);
}
//Try convert to target
convertTarget = dynTableEntity.FromTableEntity<TTarget>();
nonNullValuesTarget = convertTarget.GetType().GetProperties().Select(x => x.GetValue(convertTarget)).Count(v => v != null);
if (nonNullValuesSource > nonNullValuesTarget)
{
sourceFound = true;
}
else
{
targetObjects.Add(convertTarget);
}
}
propInfo.SetValue(convertSource, targetObjects);
return convertSource;
}
//
///在源(一)和相关实体目标(多)之间添加一对多关系。源和相关目标有单独的行,但共享相同的分区键
///
///目标类别,前个人
///只有一个条目的源实体
///源实体中包含的相关实体,可以是0到多个,例如e=>e.Persons
///目标实体rowkey属性,需要与源rowkey不同
///
公共异步任务InsertBatchOneToMany(T entitySource,Expression relatedEntities,Expression entityTargetRowKey),其中TTarget:class
{
尝试
{
//TODO:将相关属性放在ignorelist上以进行json序列化
//创建批处理操作
TableBatchOperation batchOperation=新建TableBatchOperation();
IEnumerable targets=relatedEntities.Compile().Invoke(entitySource);
//将源实体插入批处理
DynamicTableEntity source=CreateEntity(entitySource);
batchOperation.InsertOrMerge(源代码);
//将目标实体插入批处理
foreach(目标中的var entityTarget)
{
字符串trowKey=entityTargetRowKey.Compile().Invoke(entityTarget);
InsertOrMerge(entityTarget.ToTableEntity(source.PartitionKey,trowKey));
}
//执行批处理
IList results=await_table.ExecuteBatchAsync(批处理操作);
}
捕获(StorageException-ex)
{
抛出新的StorageException($“将数据保存到表时出错。”+
$“{System.Environment.NewLine}错误消息:{ex.Message}”+
$“{System.Environment.NewLine}错误扩展信息:{ex.RequestInformation.ExtendedErrorInformation.ErrorMessage}”+
$“{System.Environment.NewLine}错误代码:{ex.RequestInformation.ExtendedErrorInformation.ErrorCode}”);
}
}
///
///将源及其相关目标实体重新检索回源
///
///相关实体
///源实体和相关目标实体共享的Partionkey
///源实体中包含的相关实体,例如e=>e.人员
///
公共异步任务GetBatchOneToMany(string partitionKey,Expression relatedEntities),其中ttTarget:class,new()
{
var dynTableEntities=await _tableStore.GetByPartitionKeyAsync(partitionKey);
T convertSource=新的T();
TTarget convertTarget=新的TTarget();
var targetObjects=新列表();
MemberExpression成员=relatedEntities.Body作为MemberExpression;
PropertyInfo-propInfo=成员。成员为PropertyInfo;
IEnumerable targets=relatedEntities.Compile().Invoke(convertSource);
bool sourceFound=false;
foreach(var dynTable)dynTable中的实体
public async Task AddProject(GovernorProject project)
{
//Commit to table
await _repository.InsertBatchOneToMany(project, p => p.Environments, e => e.DisplayName);
}
public async Task<GovernorProject> GetProject(string projectId)
{
return await _repository.GetBatchOneToMany(projectId, p => p.Environments);
}