public async Task <Func <Task> > MutateLookupTable(string rowKey, string partitionKey, MemberInfo memberInfo, AzureTableDriverDynamic repository, Func <IEnumerable <KeyValuePair <string, string> >, IEnumerable <KeyValuePair <string, string> > > mutateCollection) { var tableName = GetLookupTableName(memberInfo); return(await repository.UpdateOrCreateAsync <StorageLookupTable, Func <Task> >(rowKey, partitionKey, async (created, lookup, saveAsync) => { // store for rollback var orignalRowAndPartitionKeys = lookup.rowAndPartitionKeys .NullToEmpty() .ToArray(); var updatedRowAndPartitionKeys = mutateCollection(orignalRowAndPartitionKeys) .ToArray(); if (Unmodified(orignalRowAndPartitionKeys, updatedRowAndPartitionKeys)) { return () => true.AsTask(); } lookup.rowAndPartitionKeys = updatedRowAndPartitionKeys; await saveAsync(lookup); Func <Task <bool> > rollback = async() => { var removed = orignalRowAndPartitionKeys.Except(updatedRowAndPartitionKeys, rk => $"{rk.Key}|{rk.Value}"); var added = updatedRowAndPartitionKeys.Except(orignalRowAndPartitionKeys, rk => $"{rk.Key}|{rk.Value}"); var table = repository.TableClient.GetTableReference(tableName); return await repository.UpdateAsync <StorageLookupTable, bool>(rowKey, partitionKey, async(currentDoc, saveRollbackAsync) => { var currentLookups = currentDoc.rowAndPartitionKeys .NullToEmpty() .ToArray(); var rolledBackRowAndPartitionKeys = currentLookups .Concat(removed) .Except(added, rk => $"{rk.Key}|{rk.Value}") .ToArray(); if (Unmodified(rolledBackRowAndPartitionKeys, currentLookups)) { return true; } currentDoc.rowAndPartitionKeys = rolledBackRowAndPartitionKeys; await saveRollbackAsync(currentDoc); return true; }, table: table); }; return rollback; }, tableName : tableName)); bool Unmodified( KeyValuePair <string, string>[] rollbackRowAndPartitionKeys, KeyValuePair <string, string>[] modifiedDocRowAndPartitionKeys) { var modified = modifiedDocRowAndPartitionKeys .Except(rollbackRowAndPartitionKeys, rk => $"{rk.Key}|{rk.Value}") .Any(); var unmodified = !modified; return(unmodified); } }
public async Task <Func <Task> > MutateLookupTable(string rowKey, string partitionKey, MemberInfo memberInfo, AzureTableDriverDynamic repository, Func <IEnumerable <KeyValuePair <string, string> >, IEnumerable <KeyValuePair <string, string> > > mutateCollection) { var tableName = GetLookupTableName(memberInfo); return(await repository.UpdateOrCreateAsync <StorageLookupTable, Func <Task> >(rowKey, partitionKey, async (created, lookup, saveAsync) => { var rollbackRowAndPartitionKeys = lookup.rowAndPartitionKeys; // store for rollback lookup.rowAndPartitionKeys = mutateCollection(rollbackRowAndPartitionKeys) .Distinct(rpKey => rpKey.Key) .ToArray(); await saveAsync(lookup); Func <Task <bool> > rollback = async() => { if (created) { return await repository.DeleteAsync <StorageLookupTable, bool>(rowKey, partitionKey, () => true, () => false, tableName: tableName); } var table = repository.TableClient.GetTableReference(tableName); return await repository.UpdateAsync <StorageLookupTable, bool>(rowKey, partitionKey, async(modifiedDoc, saveRollbackAsync) => { bool Modified() { if (rollbackRowAndPartitionKeys.Length != modifiedDoc.rowAndPartitionKeys.Length) { return true; } var matchKeys = rollbackRowAndPartitionKeys.SelectKeys().AsHashSet(); var matchValues = rollbackRowAndPartitionKeys.SelectKeys().AsHashSet(); var allValuesAccountedFor = modifiedDoc.rowAndPartitionKeys .All( rowAndPartitionKey => { if (!matchKeys.Contains(rowAndPartitionKey.Key)) { return false; } if (!matchValues.Contains(rowAndPartitionKey.Value)) { return false; } return true; }); return !allValuesAccountedFor; } if (!Modified()) { return true; } modifiedDoc.rowAndPartitionKeys = rollbackRowAndPartitionKeys; await saveRollbackAsync(modifiedDoc); return true; }, table: table); }; return rollback; }, tableName : tableName)); }
// Called via reflection public virtual Task <TResult> ExecuteTypedAsync <TEntity, TRefEntity, TResult>(IRef <TRefEntity> entityRef, MemberInfo memberInfo, string rowKeyRef, string partitionKeyRef, TEntity value, IDictionary <string, EntityProperty> dictionary, AzureTableDriverDynamic repository, Func <Func <Task>, TResult> onSuccessWithRollback, Func <TResult> onFailure) where TRefEntity : IReferenceable { var rowKey = entityRef.StorageComputeRowKey(); var partitionKey = entityRef.StorageComputePartitionKey(rowKey); return(repository.UpdateAsync <TRefEntity, TResult>(rowKey, partitionKey, async(entity, saveAsync) => { var referencedEntityType = typeof(TRefEntity); var fieldToModifyFieldInfo = referencedEntityType .GetFields() .Select( field => { return field .GetAttributesInterface <IPersistInAzureStorageTables>() .Where(attr => attr.Name == this.ReferenceProperty) .First( (attr, next) => field, () => default(FieldInfo)); }) .Where(v => !v.IsDefaultOrNull()) .First(); var valueToMutate = fieldToModifyFieldInfo.GetValue(entity); var valueToMutateType = valueToMutate.GetType(); if (valueToMutateType.IsSubClassOfGeneric(typeof(IRefs <>))) { var references = valueToMutate as IReferences; var idsOriginal = references.ids; var rowKeyId = Guid.Parse(rowKeyRef); if (idsOriginal.Contains(rowKeyId)) { return onSuccessWithRollback(() => 1.AsTask()); } var ids = idsOriginal .Append(rowKeyId) .Distinct() .ToArray(); var refsInstantiatable = typeof(Refs <>) .MakeGenericType(valueToMutateType.GenericTypeArguments.First().AsArray()); var valueMutated = Activator.CreateInstance(refsInstantiatable, ids.AsArray()); fieldToModifyFieldInfo.SetValue(ref entity, valueMutated); var result = await saveAsync(entity); Func <Task> rollback = async() => { bool rolled = await repository.UpdateAsync <TRefEntity, bool>(rowKey, partitionKey, async(entityRollback, saveRollbackAsync) => { fieldToModifyFieldInfo.SetValue(ref entityRollback, valueToMutate); await saveRollbackAsync(entityRollback); return true; }, () => false); }; return onSuccessWithRollback(rollback); } return onFailure(); }, onFailure)); }
public async Task <Func <Task> > MutateLookupTable(string rowKey, string partitionKey, MemberInfo memberInfo, AzureTableDriverDynamic repository, Func <IEnumerable <KeyValuePair <string, string> >, IEnumerable <KeyValuePair <string, string> > > mutateCollection) { var tableName = GetLookupTableName(memberInfo); return(await repository.UpdateOrCreateAsync <DateTimeLookupTable, Func <Task> >(rowKey, partitionKey, async (created, lookup, saveAsync) => { // store for rollback var rollbackRowAndPartitionKeys = lookup.rows .NullToEmpty() .Zip(lookup.partitions.NullToEmpty(), (k, v) => k.PairWithValue(v)) .ToArray(); await saveAsync(lookup); Func <Task <bool> > rollback = async() => { if (created) { return await repository.DeleteAsync <DateTimeLookupTable, bool>(rowKey, partitionKey, () => true, () => false, tableName: tableName); } var table = repository.TableClient.GetTableReference(tableName); return await repository.UpdateAsync <DateTimeLookupTable, bool>(rowKey, partitionKey, async(modifiedDoc, saveRollbackAsync) => { bool Modified() { if (rollbackRowAndPartitionKeys.Length != modifiedDoc.rows.Length) { return true; } var matchKeys = rollbackRowAndPartitionKeys.SelectKeys().AsHashSet(); var matchValues = rollbackRowAndPartitionKeys.SelectKeys().AsHashSet(); var allRowAccountedFor = modifiedDoc.rows .All( row => { if (!matchKeys.Contains(row)) { return false; } return true; }); var allPartitionsAccountedFor = modifiedDoc.partitions .All( partition => { if (!matchValues.Contains(partition)) { return false; } return true; }); var allValuesAccountedFor = allRowAccountedFor & allPartitionsAccountedFor; return !allValuesAccountedFor; } if (!Modified()) { return true; } modifiedDoc.rows = rollbackRowAndPartitionKeys.SelectKeys().ToArray(); modifiedDoc.partitions = rollbackRowAndPartitionKeys.SelectValues().ToArray(); await saveRollbackAsync(modifiedDoc); return true; }, table: table); }; return rollback; }, tableName : tableName)); }