使用TableEntity(Azure表存储)的最佳实践-类的解耦



我现在正在为一个更大的项目进行原型设计,需要做出一些设计决策。。

我的VS解决方案由4个项目组成(目前,稍后可能会进一步分离(:

  • 一个.NET Standard 2.1项目,包含我的所有实体(例如一个Customer类(,主要包含简单的属性,但也有一些包含枚举和列表。我希望这是简单的,没有依赖关系
  • 使用ASP.NET Core 3.1和Blazor WebAssembly的Web项目(仅限客户端(
  • 包含基础设施和服务的应用程序项目(例如AzureTableStorage<T>所在的位置(
  • Azure Function层,它是web和应用程序之间的"中介",依赖于应用程序层(注入CustomerService(

对于存储,我使用Azure存储表,我的问题是找到一种优雅的解耦方式来实现它。

我或多或少地使用这个例子:https://www.c-sharpcorner.com/article/single-page-application-in-blazor-with-azure-table-storage/以便对表执行CRUD操作。

但为了使用它,我依赖于继承TableEntity,这很烦人。此外,我的WebUI正在使用Blazor,因此它将一堆Azure Cosmos dll带到浏览器中,如果我选择继承TableEntity,则不需要这些dll。

因此,我无法决定是否只需要对poco类进行解耦并去掉TableEntity。。我看到了一些关于使用TableEntityAdapter的内容,但找不到任何使用它的示例?

另一种方法可能是让Dto"复制"我的POCO类的类,然后它可以继承TableEntity。但是我需要维护这些类。但可能是需要的,因为我不认为Azure存储库中的方法可以开箱即用地处理列表、枚举等。但是,如果我可以制作一些处理更复杂类型的通用适配器,那么Dto类可能是多余的。

寻找输入和灵感基本上:(

感谢

好的,所以我现在已经制定了一种方法。我会在这里分享。

问题是,你可以取消表格的正常化,也就是说。将实体及其所有嵌套对象和列表属性制成1个tableentity=>1行。这意味着复杂属性或类类型属性需要序列化为字符串(很可能是json(。

或者,您可以建立关系,其中实体共享相同的partitionkey。然后批量创建它们。例如部门->人员。因此,一个部门具有partionkey=Department01,而人员X具有partionkey=Department01。2排。

但是,如果你真的想同时做这两件事,比如说有不同的行,但每个表实体也有IEnumerable属性,比如列表和集合,它们也会被过度分割成不同的行。

我发现了这个很棒的社区库,我对它进行了扩展,并制作了2个通用方法。它们并不完美,但这只是一个开始。

https://github.com/giometrix/TableStorage.Abstractions.POCO

在这里,您可以轻松地将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;
}

这使我能够同时建立关系和反规范化行。

用法:

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);
}

在我的案例中,我有一个项目的主要实体,每个项目都有0个或多个相关的环境,这些环境保存在GovernorProject 的Collection<Environment> Environment属性中

最新更新