Лучшая практика использования TableEntity (Azure табличное хранилище) - разделение классов - PullRequest
1 голос
/ 23 февраля 2020

Сейчас я создаю прототип для большого проекта, и мне нужно принять некоторые проектные решения.

Мое решение VS состоит (на данный момент, возможно, позже), из 4 проектов:

  • A. NET Стандартный проект 2.1 со всеми моими сущностями (например, классом Customer), содержащий в основном простые свойства, но некоторые также содержат перечисления и списки. Я хочу, чтобы это было просто без каких-либо зависимостей.
  • Веб-проект, использующий ASP. NET Core 3.1 и Blazor WebAssembly (только на стороне клиента)
  • Проект приложения, содержащий инфраструктуру и сервисы (например, здесь AzureTableStorage<T>)
  • Функциональный уровень Azure, который является «посредником» между сетью и приложением, который зависит от уровня приложения (внедряет 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.

Так что я не могу решить, просто ли я нужно развязать мои классы поко и избавиться TableEntity .. Я видел кое-что об использовании TableEntityAdapter, но не могу найти ни одного примера с его использованием?

Другой подход может заключаться в том, чтобы Dto "дублировал" классы моих классов POCO, которые затем могут наследовать TableEntity. Но тогда мне нужно было бы поддерживать эти классы. Но может понадобиться, так как я не думаю, что методы в Azure Библиотеки хранилищ могут обрабатывать списки, перечисление и т. Д. c из коробки. Но с другой стороны, если я смогу сделать какой-нибудь универсальный c адаптер, который позаботится о более сложных типах, класс Dto мог бы быть избыточным.

В основном требуется ввод и вдохновение:)

Спасибо

1 Ответ

0 голосов
/ 12 марта 2020

Хорошо, теперь я разработал подход. Я поделюсь этим здесь.

Дело в том, что вы можете либо денормализовать стол, ака. сделать сущности со всеми вложенными объектами и перечислить свойства в 1 строку tableentity => 1. Это подразумевает, что сложные свойства или свойства класса должны быть сериализованы в строки (json, наиболее вероятно).

Или вы можете создавать отношения, в которых объекты совместно используют один и тот же ключ разделения. Затем создайте их в пакетном режиме. Например, отдел -> Персоны. Таким образом, у отдела есть partionkey = Department01, а у лица X - partionkey = Department01. 2 строки.

Но что, если вы действительно хотите сделать и то и другое, скажем, иметь разные строки, но каждая таблица также имеет свойства IEnumerable, такие как списки и коллекции, которые было бы излишне разделять на разные строки.

Я нашел эту замечательную библиотеку сообщества, которую расширил, и создал 2 обобщенных c метода. Они не идеальны, но это начало.

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

Здесь вы можете легко преобразовать объект POCO в TableEntity и наоборот, иначе. денормализации.

Я добавил эти 2 обобщенных метода c в библиотеку:

/// <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 или более связанных сред, которые хранятся в свойстве Collection<Environment> Environment в GovernorProject

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...