【问题标题】:Best practice of using TableEntity (Azure table storage) - decoupling of classes使用 TableEntity(Azure 表存储)的最佳实践——类的解耦
【发布时间】:2020-06-07 04:21:12
【问题描述】:

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

我的 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,因此如果我选择继承 TableEntity,它会将一堆 Azure Cosmos dll 接收到浏览器中。

所以我无法决定是否只需要解耦我的 poco 类并摆脱 TableEntity.. 我看到了一些关于使用 TableEntityAdapter 的内容,但找不到任何使用它的示例?

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

基本上是在寻找输入和灵感:)

谢谢

【问题讨论】:

  • “Dto 'duplicate' classes of my POCO classes”被称为 DAO(数据访问对象)。这将是要走的路。
  • 好的,但我已经在使用存储库模式(来自示例 - 检查 PersonService.cs)),而且 DAO 似乎也包含 Get、Update、Create 方法,所以看起来一样 -相同的。例如。 PersonService(IAzureTableStorage repository) 其中 Person 是需要从 TableEntity 继承的 POCO 类(我想摆脱)
  • 不,DAO 通常不包含任何方法或逻辑(可能除了一些用于转换/格式化的方法)。 CRUD 方法属于存储库。
  • Ok 找到了这篇博文,他在其中创建了一个动态 dto(猜想它相当于 DAO),它可以即时将 Poco 转换为 dto。虽然看起来工作量很大,但我希望 TableEntity 适配器可以帮助bretthargreaves.wordpress.com/2014/01/11/…
  • 好吧,好像我只是将 TableEntityAdapter(myEntitity> 传递到继承 TableEntity 的存储库。我现在将继续这样做。更多信息:stackoverflow.com/questions/47044207/…

标签: c# asp.net-core design-patterns azure-storage blazor


【解决方案1】:

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

问题是你可以去规范化表格,也就是。将实体及其所有嵌套对象和列表属性放入 1 tableentity => 1 行。这意味着复杂的属性或类类型属性需要序列化为字符串(最有可能是 json)。

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

但是如果你真的想同时做这两个,比如说有不同的行,但是每个表实体也有 IEnumerable 属性,比如列表和集合,如果将它们分成不同的行也将是多余的。

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

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

在这里,您可以轻松地将 POCO 实体转换为 TableEntity,反之亦然。非规范化。

我将这 2 个通用方法添加到库中:

/// <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&lt;Environment&gt; Environment 属性相关的环境

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-05
    • 2011-06-24
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多