Хорошо, теперь я разработал подход. Я поделюсь этим здесь.
Дело в том, что вы можете либо денормализовать стол, ака. сделать сущности со всеми вложенными объектами и перечислить свойства в 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