Skip to content

Commit a6f9e36

Browse files
authored
Implement DynamoDbFlatten annotation (#3833)
1 parent d4ac138 commit a6f9e36

File tree

6 files changed

+481
-97
lines changed

6 files changed

+481
-97
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "DynamoDBv2",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Introduce support for the [DynamoDbFlatten] attribute in the DynamoDB Object Persistence Model`"
8+
]
9+
}
10+
]
11+
}

sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,35 @@ public DynamoDBPolymorphicTypeAttribute(string typeDiscriminator,
181181
}
182182
}
183183

184+
/// <summary>
185+
/// Indicates that the properties of the decorated field or property type should be "flattened"
186+
/// into the parent object's attribute structure in DynamoDB. When applied, all public properties
187+
/// of the referenced type are serialized as individual top-level attributes of the parent item,
188+
/// rather than as a nested object or map.
189+
/// <para>
190+
/// Example:
191+
/// <code>
192+
/// public class Address
193+
/// {
194+
/// public string Street { get; set; }
195+
/// public string City { get; set; }
196+
/// }
197+
///
198+
/// public class Person
199+
/// {
200+
/// public string Name { get; set; }
201+
/// [DynamoDBFlatten]
202+
/// public Address Address { get; set; }
203+
/// }
204+
/// </code>
205+
/// In this example, the <c>Person</c> table will have top-level attributes for <c>Name</c>, <c>Street</c>, and <c>City</c>.
206+
/// </para>
207+
/// </summary>
208+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
209+
public sealed class DynamoDBFlattenAttribute : DynamoDBAttribute
210+
{
211+
}
212+
184213
/// <summary>
185214
/// DynamoDB attribute that directs the specified attribute not to
186215
/// be included when saving or loading objects.

sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -444,26 +444,52 @@ private void PopulateInstance(ItemStorage storage, object instance, DynamoDBFlat
444444
{
445445
foreach (PropertyStorage propertyStorage in storageConfig.AllPropertyStorage)
446446
{
447+
if(propertyStorage.IsFlattened) continue;
447448
string attributeName = propertyStorage.AttributeName;
448-
449-
DynamoDBEntry entry;
450-
if (document.TryGetValue(attributeName, out entry))
449+
if (propertyStorage.ShouldFlattenChildProperties)
451450
{
452-
if (ShouldSave(entry, true))
451+
//create instance of the flatten property
452+
var targetType = propertyStorage.MemberType;
453+
object flattenedPropertyInstance = Utils.InstantiateConverter(targetType, this);
454+
455+
//populate the flatten properties
456+
foreach (var flattenPropertyStorage in propertyStorage.FlattenProperties)
453457
{
454-
object value = FromDynamoDBEntry(propertyStorage, entry, flatConfig);
458+
string flattenedAttributeName = flattenPropertyStorage.AttributeName;
455459

456-
if (!TrySetValue(instance, propertyStorage.Member, value))
457-
{
458-
throw new InvalidOperationException("Unable to retrieve value from " + attributeName);
459-
}
460+
PopulateProperty(storage, flatConfig, document, flattenedAttributeName, flattenPropertyStorage, flattenedPropertyInstance);
461+
}
462+
if (!TrySetValue(instance, propertyStorage.Member, flattenedPropertyInstance))
463+
{
464+
throw new InvalidOperationException("Unable to retrieve value from " + attributeName);
460465
}
461-
462-
if (propertyStorage.IsVersion)
463-
storage.CurrentVersion = entry as Primitive;
464466
}
467+
else
468+
{
469+
PopulateProperty(storage, flatConfig, document, attributeName, propertyStorage, instance);
470+
}
471+
}
472+
}
473+
}
474+
475+
private void PopulateProperty(ItemStorage storage, DynamoDBFlatConfig flatConfig, Document document,
476+
string attributeName, PropertyStorage propertyStorage, object instance)
477+
{
478+
DynamoDBEntry entry;
479+
if (!document.TryGetValue(attributeName, out entry)) return;
480+
481+
if (ShouldSave(entry, true))
482+
{
483+
object value = FromDynamoDBEntry(propertyStorage, entry, flatConfig);
484+
485+
if (!TrySetValue(instance, propertyStorage.Member, value))
486+
{
487+
throw new InvalidOperationException("Unable to retrieve value from " + attributeName);
465488
}
466489
}
490+
491+
if (propertyStorage.IsVersion)
492+
storage.CurrentVersion = entry as Primitive;
467493
}
468494

469495
/// <summary>
@@ -521,30 +547,46 @@ private void PopulateItemStorage(object toStore, ItemStorage storage, DynamoDBFl
521547
if (keysOnly && !propertyStorage.IsHashKey && !propertyStorage.IsRangeKey &&
522548
!propertyStorage.IsVersion && !propertyStorage.IsCounter) continue;
523549

550+
if (propertyStorage.IsFlattened) continue;
551+
524552
string propertyName = propertyStorage.PropertyName;
525553
string attributeName = propertyStorage.AttributeName;
526554

527555
object value;
528556
if (TryGetValue(toStore, propertyStorage.Member, out value))
529557
{
530-
DynamoDBEntry dbe = ToDynamoDBEntry(propertyStorage, value, flatConfig);
558+
DynamoDBEntry dbe = ToDynamoDBEntry(propertyStorage, value, flatConfig, propertyStorage.ShouldFlattenChildProperties);
531559

532560
if (ShouldSave(dbe, ignoreNullValues))
533561
{
534-
Primitive dbePrimitive = dbe as Primitive;
535-
if (propertyStorage.IsHashKey || propertyStorage.IsRangeKey ||
536-
propertyStorage.IsVersion || propertyStorage.IsLSIRangeKey ||
537-
propertyStorage.IsCounter)
562+
563+
if (propertyStorage.ShouldFlattenChildProperties)
538564
{
539-
if (dbe != null && dbePrimitive == null)
540-
throw new InvalidOperationException("Property " + propertyName +
541-
" is a hash key, range key, atomic counter or version property and must be Primitive");
542-
}
565+
if (dbe == null) continue;
543566

544-
document[attributeName] = dbe;
567+
if (dbe is not Document innerDocument) continue;
545568

546-
if (propertyStorage.IsVersion)
547-
storage.CurrentVersion = dbePrimitive;
569+
foreach (var pair in innerDocument)
570+
{
571+
document[pair.Key] = pair.Value;
572+
}
573+
}
574+
else
575+
{
576+
Primitive dbePrimitive = dbe as Primitive;
577+
if (propertyStorage.IsHashKey || propertyStorage.IsRangeKey ||
578+
propertyStorage.IsVersion || propertyStorage.IsLSIRangeKey)
579+
{
580+
if (dbe != null && dbePrimitive == null)
581+
throw new InvalidOperationException("Property " + propertyName +
582+
" is a hash key, range key or version property and must be Primitive");
583+
}
584+
585+
document[attributeName] = dbe;
586+
587+
if (propertyStorage.IsVersion)
588+
storage.CurrentVersion = dbePrimitive;
589+
}
548590
}
549591
}
550592
else
@@ -724,7 +766,8 @@ internal DynamoDBEntry ToDynamoDBEntry(SimplePropertyStorage propertyStorage, ob
724766

725767
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072",
726768
Justification = "The user's type has been annotated with InternalConstants.DataModelModeledType with the public API into the library. At this point the type will not be trimmed.")]
727-
private DynamoDBEntry ToDynamoDBEntry(SimplePropertyStorage propertyStorage, object value, DynamoDBFlatConfig flatConfig, bool canReturnScalarInsteadOfList)
769+
private DynamoDBEntry ToDynamoDBEntry(SimplePropertyStorage propertyStorage, object value,
770+
DynamoDBFlatConfig flatConfig, bool canReturnScalarInsteadOfList)
728771
{
729772
if (value == null)
730773
return null;
@@ -803,7 +846,7 @@ private bool TryToMap(object value, [DynamicallyAccessedMembers(InternalConstant
803846
if (item == null)
804847
entry = DynamoDBNull.Null;
805848
else
806-
entry = ToDynamoDBEntry(propertyStorage, item, flatConfig);
849+
entry = ToDynamoDBEntry(propertyStorage, item, flatConfig, false);
807850

808851
output[key] = entry;
809852
}
@@ -839,7 +882,7 @@ private bool TryToList(object value, [DynamicallyAccessedMembers(DynamicallyAcce
839882
entry = DynamoDBNull.Null;
840883
else
841884
{
842-
entry = ToDynamoDBEntry(propertyStorage, item, flatConfig);
885+
entry = ToDynamoDBEntry(propertyStorage, item, flatConfig, false);
843886
}
844887

845888
output.Add(entry);
@@ -1187,7 +1230,7 @@ private static List<QueryCondition> CreateQueryConditions(DynamoDBFlatConfig fla
11871230
// Key creation
11881231
private DynamoDBEntry ValueToDynamoDBEntry(PropertyStorage propertyStorage, object value, DynamoDBFlatConfig flatConfig)
11891232
{
1190-
var entry = ToDynamoDBEntry(propertyStorage, value, flatConfig);
1233+
var entry = ToDynamoDBEntry(propertyStorage, value, flatConfig, false);
11911234
return entry;
11921235
}
11931236
private static void ValidateKey(Key key, ItemStorageConfig storageConfig)

0 commit comments

Comments
 (0)