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

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和应用程序之间的“中

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

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

  • 一个.NET标准2.1项目,包含所有my实体(例如客户类),其中大部分包含简单属性,但有些还包含枚举和列表。我希望这是没有依赖性的简单
  • 使用ASP.NET Core 3.1和Blazor WebAssembly(仅客户端)的Web项目
  • 包含基础架构和服务的应用程序项目(例如,
    AzureTableStorage
    所在的位置)
  • Azure功能层,它是web和应用程序之间的“中介”,依赖于应用程序层(注入CustomerService)
对于存储,我使用的是Azure存储表,我的问题是找到一种优雅的解耦方法来实现这一点

我或多或少地使用了这个示例:用于表的CRUD操作

但是为了使用它,我依赖于继承
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);

    }