private static string GetAccurateCase(ItemStorageConfig config, string value) { return(config.LowerCamelCaseProperties ? Utils.ToLowerCamelCase(value) : value); }
private static void PopulateConfigFromType(ItemStorageConfig config, ITypeInfo typeInfo) { DynamoDBTableAttribute tableAttribute = Utils.GetTableAttribute(typeInfo); if (tableAttribute == null) { config.TableName = typeInfo.Name; } else { if (string.IsNullOrEmpty(tableAttribute.TableName)) { throw new InvalidOperationException("DynamoDBTableAttribute.Table is empty or null"); } config.TableName = tableAttribute.TableName; config.LowerCamelCaseProperties = tableAttribute.LowerCamelCaseProperties; } string tableAlias; if (AWSConfigs.DynamoDBConfig.Context.TableAliases.TryGetValue(config.TableName, out tableAlias)) { config.TableName = tableAlias; } foreach (var member in typeInfo.GetMembers()) { if (!ItemStorageConfig.IsValidMemberInfo(member)) { continue; } // prepare basic info PropertyStorage propertyStorage = new PropertyStorage(member); propertyStorage.AttributeName = GetAccurateCase(config, member.Name); // run through all DDB attributes List <DynamoDBAttribute> allAttributes = Utils.GetAttributes(member); foreach (var attribute in allAttributes) { // filter out ignored properties if (attribute is DynamoDBIgnoreAttribute) { propertyStorage.IsIgnored = true; } if (attribute is DynamoDBVersionAttribute) { propertyStorage.IsVersion = true; } DynamoDBPropertyAttribute propertyAttribute = attribute as DynamoDBPropertyAttribute; if (propertyAttribute != null) { if (!string.IsNullOrEmpty(propertyAttribute.AttributeName)) { propertyStorage.AttributeName = GetAccurateCase(config, propertyAttribute.AttributeName); } if (propertyAttribute.Converter != null) { propertyStorage.ConverterType = propertyAttribute.Converter; } if (propertyAttribute is DynamoDBHashKeyAttribute) { var gsiHashAttribute = propertyAttribute as DynamoDBGlobalSecondaryIndexHashKeyAttribute; if (gsiHashAttribute != null) { propertyStorage.IsGSIHashKey = true; propertyStorage.AddIndex(gsiHashAttribute); } else { propertyStorage.IsHashKey = true; } } if (propertyAttribute is DynamoDBRangeKeyAttribute) { var gsiRangeAttribute = propertyAttribute as DynamoDBGlobalSecondaryIndexRangeKeyAttribute; if (gsiRangeAttribute != null) { propertyStorage.IsGSIRangeKey = true; propertyStorage.AddIndex(gsiRangeAttribute); } else { propertyStorage.IsRangeKey = true; } } DynamoDBLocalSecondaryIndexRangeKeyAttribute lsiRangeKeyAttribute = propertyAttribute as DynamoDBLocalSecondaryIndexRangeKeyAttribute; if (lsiRangeKeyAttribute != null) { propertyStorage.IsLSIRangeKey = true; propertyStorage.AddIndex(lsiRangeKeyAttribute); } } } config.Properties.Add(propertyStorage); } }
public ItemStorage(ItemStorageConfig storageConfig) { Document = new Document(); Config = storageConfig; }
public ConfigTableCache(ItemStorageConfig baseTypeConfig) { BaseTypeConfig = baseTypeConfig; BaseTableName = BaseTypeConfig.TableName; Cache = new Dictionary <string, ItemStorageConfig>(StringComparer.Ordinal); }
private static void ValidateConfigAgainstTable(ItemStorageConfig config, Table table) { CompareKeys(config, table, table.HashKeys, config.HashKeyPropertyNames, "hash"); CompareKeys(config, table, table.RangeKeys, config.RangeKeyPropertyNames, "range"); }
internal static ItemStorageConfig CreateStorageConfig(Type type) { if (type == null) { throw new ArgumentNullException("type"); } var typeWrapper = TypeFactory.GetTypeInfo(type); ItemStorageConfig config = new ItemStorageConfig(type); DynamoDBTableAttribute tableAttribute = Utils.GetTableAttribute(type); if (tableAttribute == null || string.IsNullOrEmpty(tableAttribute.TableName)) { throw new InvalidOperationException("No DynamoDBTableAttribute on type"); } if (string.IsNullOrEmpty(tableAttribute.TableName)) { throw new InvalidOperationException("DynamoDBTableAttribute.Table is empty or null"); } config.TableName = tableAttribute.TableName; config.LowerCamelCaseProperties = tableAttribute.LowerCamelCaseProperties; foreach (var member in typeWrapper.GetMembers()) { // filter out non-fields and non-properties if (!(member is FieldInfo || member is PropertyInfo)) { continue; } // filter out properties that aren't both read and write if (!Utils.IsReadWrite(member)) { continue; } // prepare basic info Type memberType = Utils.GetType(member); string attributeName = GetAccurateCase(config, member.Name); string propertyName = member.Name; PropertyStorage propertyStorage = new PropertyStorage(member, memberType); // run through all DDB attributes bool ignorePresent = false; var allAttributes = Utils.GetAttributes(member); foreach (var attribute in allAttributes) { // filter out ignored properties if (attribute is DynamoDBIgnoreAttribute) { ignorePresent |= true; } // if ignore attribute is present, ignore other attributes if (ignorePresent) { continue; } if (attribute is DynamoDBVersionAttribute) { Utils.ValidateVersionType(memberType); // no conversion is possible, so type must be a nullable primitive propertyStorage.IsVersion = true; } DynamoDBPropertyAttribute propertyAttribute = attribute as DynamoDBPropertyAttribute; if (propertyAttribute != null) { if (!string.IsNullOrEmpty(propertyAttribute.AttributeName)) { attributeName = GetAccurateCase(config, propertyAttribute.AttributeName); } if (propertyAttribute is DynamoDBHashKeyAttribute) { if (propertyAttribute.Converter == null && !Utils.IsPrimitive(memberType)) { throw new InvalidOperationException("Hash key " + propertyName + " must be of primitive type"); } if (propertyAttribute is DynamoDBGlobalSecondaryIndexHashKeyAttribute) { propertyStorage.IsGSIHashKey = true; var gsiHashAttribute = propertyAttribute as DynamoDBGlobalSecondaryIndexHashKeyAttribute; if (gsiHashAttribute.IndexNames == null || gsiHashAttribute.IndexNames.Length == 0) { throw new InvalidOperationException("Global Secondary Index must not be null or empty"); } propertyStorage.Indexes.AddRange(gsiHashAttribute.IndexNames); AddGSIConfigs(config, gsiHashAttribute.IndexNames, propertyName, true); } else { propertyStorage.IsHashKey = true; } } if (propertyAttribute is DynamoDBRangeKeyAttribute) { if (propertyAttribute.Converter == null && !Utils.IsPrimitive(memberType)) { throw new InvalidOperationException("Range key " + propertyName + " must be of primitive type"); } if (propertyAttribute is DynamoDBGlobalSecondaryIndexRangeKeyAttribute) { propertyStorage.IsGSIRangeKey = true; var gsiRangeAttribute = propertyAttribute as DynamoDBGlobalSecondaryIndexRangeKeyAttribute; if (gsiRangeAttribute.IndexNames == null || gsiRangeAttribute.IndexNames.Length == 0) { throw new InvalidOperationException("Global Secondary Index must not be null or empty"); } propertyStorage.Indexes.AddRange(gsiRangeAttribute.IndexNames); AddGSIConfigs(config, gsiRangeAttribute.IndexNames, propertyName, false); } else { propertyStorage.IsRangeKey = true; } } if (propertyAttribute is DynamoDBLocalSecondaryIndexRangeKeyAttribute) { DynamoDBLocalSecondaryIndexRangeKeyAttribute lsiRangeKeyAttribute = propertyAttribute as DynamoDBLocalSecondaryIndexRangeKeyAttribute; if (lsiRangeKeyAttribute.IndexNames == null || lsiRangeKeyAttribute.IndexNames.Length == 0) { throw new InvalidOperationException("Local Secondary Index must not be null or empty"); } propertyStorage.Indexes.AddRange(lsiRangeKeyAttribute.IndexNames); propertyStorage.IsLSIRangeKey = true; foreach (var index in lsiRangeKeyAttribute.IndexNames) { List <string> properties; if (!config.IndexNameToLSIRangePropertiesMapping.TryGetValue(index, out properties)) { properties = new List <string>(); config.IndexNameToLSIRangePropertiesMapping[index] = properties; } properties.Add(propertyName); } } if (propertyAttribute.Converter != null) { if (!Utils.CanInstantiate(propertyAttribute.Converter) || !Utils.ImplementsInterface(propertyAttribute.Converter, typeof(IPropertyConverter))) { throw new InvalidOperationException("Converter for " + propertyName + " must be instantiable with no parameters and must implement IPropertyConverter"); } propertyStorage.Converter = Utils.Instantiate(propertyAttribute.Converter) as IPropertyConverter; } } } propertyStorage.PropertyName = propertyName; propertyStorage.AttributeName = attributeName; // only add property storage if no ignore attribute was present if (!ignorePresent) { config.AddPropertyStorage(propertyStorage); } } if (config.HashKeyPropertyNames.Count == 0) { throw new InvalidOperationException("No hash key configured for type " + type.FullName); } return(config); }
private static QueryFilter ComposeQueryFilter(object hashKeyValue, IEnumerable <QueryCondition> conditions, ItemStorageConfig storageConfig, out List <string> indexNames) { if (hashKeyValue == null) { throw new ArgumentNullException("hashKeyValue"); } string hashKeyProperty = storageConfig.HashKeyPropertyNames[0]; DynamoDBEntry hashKeyEntry = ValueToDynamoDBEntry(hashKeyProperty, hashKeyValue, storageConfig); if (hashKeyEntry == null) { throw new InvalidOperationException("Unable to convert hash key value for property " + hashKeyProperty); } string hashAttributeName = storageConfig.GetPropertyStorage(hashKeyProperty).AttributeName; Document hashKey = new Document(); hashKey[hashAttributeName] = hashKeyEntry; return(ComposeQueryFilterHelper(hashKey, conditions, storageConfig, out indexNames)); }
private ContextSearch ConvertQueryByValue <T>(object hashKeyValue, IEnumerable <QueryCondition> conditions, DynamoDBOperationConfig operationConfig, ItemStorageConfig storageConfig = null) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, Config); if (storageConfig == null) { storageConfig = StorageConfigCache.GetConfig <T>(flatConfig); } List <string> indexNames; QueryFilter filter = ComposeQueryFilter(flatConfig, hashKeyValue, conditions, storageConfig, out indexNames); return(ConvertQueryHelper <T>(flatConfig, storageConfig, filter, indexNames)); }
internal static ItemStorage ObjectToItemStorage <T>(T toStore, bool keysOnly, bool ignoreNullValues, ItemStorageConfig config) { ItemStorage storage = new ItemStorage(config); PopulateItemStorage(toStore, keysOnly, ignoreNullValues, storage); return(storage); }
// Query/Scan building private static ScanFilter ComposeScanFilter(IEnumerable <ScanCondition> conditions, ItemStorageConfig storageConfig) { ScanFilter filter = new ScanFilter(); foreach (var condition in conditions) { PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(condition.PropertyName); List <AttributeValue> attributeValues = new List <AttributeValue>(); foreach (var value in condition.Values) { var entry = ToDynamoDBEntry(propertyStorage, value, true); if (entry == null) { throw new InvalidOperationException( string.Format("Unable to convert value corresponding to property [{0}] to DynamoDB representation", condition.PropertyName)); } AttributeValue nativeValue = entry.ConvertToAttributeValue(); if (nativeValue != null) { attributeValues.Add(nativeValue); } } filter.AddCondition(propertyStorage.AttributeName, condition.Operator, attributeValues); } return(filter); }
// This method composes the query filter and determines the possible indexes that the filter // may be used against. In the case where the condition property is also a RANGE key on the // table and not just on LSI/GSI, the potential index will be "" (absent). private QueryFilter ComposeQueryFilterHelper( DynamoDBFlatConfig currentConfig, Document hashKey, IEnumerable <QueryCondition> conditions, ItemStorageConfig storageConfig, out List <string> indexNames) { if (hashKey == null) { throw new ArgumentNullException("hashKey"); } if (storageConfig.HashKeyPropertyNames.Count != 1) { throw new InvalidOperationException("Must have one hash key defined for the table " + storageConfig.TableName); } if (storageConfig.RangeKeyPropertyNames.Count != 1 && storageConfig.IndexNameToGSIMapping.Count == 0) { throw new InvalidOperationException("Must have one range key or a GSI index defined for the table " + storageConfig.TableName); } QueryFilter filter = new QueryFilter(); // Configure hash-key equality condition string hashKeyProperty = storageConfig.HashKeyPropertyNames[0]; hashKeyProperty = storageConfig.GetCorrectHashKeyProperty(currentConfig, hashKeyProperty); PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(hashKeyProperty); string attributeName = propertyStorage.AttributeName; DynamoDBEntry hashValue = hashKey[attributeName]; filter.AddCondition(attributeName, QueryOperator.Equal, hashValue); indexNames = new List <string>(); if (conditions != null) { foreach (QueryCondition condition in conditions) { object[] conditionValues = condition.Values; PropertyStorage conditionProperty = storageConfig.GetPropertyStorage(condition.PropertyName); if (conditionProperty.IsLSIRangeKey || conditionProperty.IsGSIKey) { indexNames.AddRange(conditionProperty.IndexNames); } if (conditionProperty.IsRangeKey) { indexNames.Add(NO_INDEX); } List <AttributeValue> attributeValues = ConvertConditionValues(conditionValues, conditionProperty, currentConfig); filter.AddCondition(conditionProperty.AttributeName, condition.Operator, attributeValues); } } if (currentConfig.QueryFilter != null) { foreach (ScanCondition condition in currentConfig.QueryFilter) { object[] conditionValues = condition.Values; PropertyStorage conditionProperty = storageConfig.GetPropertyStorage(condition.PropertyName); List <AttributeValue> attributeValues = ConvertConditionValues(conditionValues, conditionProperty, currentConfig, canReturnScalarInsteadOfList: true); filter.AddCondition(conditionProperty.AttributeName, condition.Operator, attributeValues); } } return(filter); }
private async Task <T> LoadHelperAsync <T>(Key key, DynamoDBFlatConfig flatConfig, ItemStorageConfig storageConfig, CancellationToken cancellationToken) { GetItemOperationConfig getConfig = new GetItemOperationConfig { ConsistentRead = flatConfig.ConsistentRead.Value, AttributesToGet = storageConfig.AttributesToGet }; Table table = GetTargetTable(storageConfig, flatConfig); ItemStorage storage = new ItemStorage(storageConfig); storage.Document = await table.GetItemHelperAsync(key, getConfig, cancellationToken).ConfigureAwait(false); T instance = DocumentToObject <T>(storage, flatConfig); return(instance); }
public ItemStorage(ItemStorageConfig storageConfig) { Document = new Document(); Config = storageConfig; ConvertedObjects = new HashSet <object>(); }
private static void PopulateConfigFromTable(ItemStorageConfig config, Table table) { PropertyStorage property; // keys foreach (var key in table.Keys) { var attributeName = key.Key; var keyDescription = key.Value; if (GetExistingProperty(config, attributeName, false, out property)) { // validate against table if (property.IsKey) { ValidateProperty(property.IsHashKey == keyDescription.IsHash, property.PropertyName, "Property key definition must match table key definition"); } } // populate property if (keyDescription.IsHash) { property.IsHashKey = true; } else { property.IsRangeKey = true; } } foreach (var kvp in table.GlobalSecondaryIndexes) { string indexName = kvp.Key; var gsi = kvp.Value; foreach (var element in gsi.KeySchema) { string attributeName = element.AttributeName; bool isHashKey = element.KeyType == KeyType.HASH; GetExistingProperty(config, attributeName, true, out property); if (property != null) { property.AddGsiIndex(isHashKey, attributeName, indexName); } } } foreach (var kvp in table.LocalSecondaryIndexes) { string indexName = kvp.Key; var lsi = kvp.Value; foreach (var element in lsi.KeySchema) { string attributeName = element.AttributeName; bool isHashKey = element.KeyType == KeyType.HASH; // only add for range keys if (!isHashKey) { GetExistingProperty(config, attributeName, true, out property); if (property != null) { property.AddLsiIndex(attributeName, indexName); } } } } }
private static QueryFilter ComposeQueryFilterHelper(Document hashKey, IEnumerable <QueryCondition> conditions, ItemStorageConfig storageConfig, out List <string> indexNames) { if (hashKey == null) { throw new ArgumentNullException("hashKey"); } if (storageConfig.HashKeyPropertyNames.Count != 1) { throw new InvalidOperationException("Must have one hash key defined for the table " + storageConfig.TableName); } if (storageConfig.RangeKeyPropertyNames.Count != 1) { throw new InvalidOperationException("Must have one range key defined for the table " + storageConfig.TableName); } QueryFilter filter = new QueryFilter(); foreach (string hashKeyProperty in storageConfig.HashKeyPropertyNames) { PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(hashKeyProperty); string attributeName = propertyStorage.AttributeName; DynamoDBEntry hashValue = hashKey[attributeName]; filter.AddCondition(attributeName, QueryOperator.Equal, hashValue); } indexNames = new List <string>(); if (conditions != null) { foreach (QueryCondition condition in conditions) { object[] conditionValues = condition.Values; PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(condition.PropertyName); if (propertyStorage.IsLSIRangeKey) { indexNames.AddRange(propertyStorage.Indexes); } List <AttributeValue> attributeValues = new List <AttributeValue>(); foreach (var conditionValue in conditionValues) { DynamoDBEntry entry = ToDynamoDBEntry(propertyStorage, conditionValue); AttributeValue attributeValue = entry.ConvertToAttributeValue(); attributeValues.Add(attributeValue); } filter.AddCondition(propertyStorage.AttributeName, condition.Operator, attributeValues); } } return(filter); }
private static List <QueryCondition> CreateQueryConditions(DynamoDBFlatConfig flatConfig, QueryOperator op, IEnumerable <object> values, ItemStorageConfig storageConfig) { string rangeKeyPropertyName; string indexName = flatConfig.IndexName; if (string.IsNullOrEmpty(indexName)) { rangeKeyPropertyName = storageConfig.RangeKeyPropertyNames.FirstOrDefault(); } else { rangeKeyPropertyName = storageConfig.GetRangeKeyByIndex(indexName); } List <QueryCondition> conditions = new List <QueryCondition> { new QueryCondition(rangeKeyPropertyName, op, values.ToArray()) }; return(conditions); }
private List <QueryCondition> CreateQueryConditions(DynamoDBOperationConfig config, QueryOperator op, IEnumerable <object> values, ItemStorageConfig storageConfig) { string rangeKeyPropertyName; var flatConfig = new DynamoDBFlatConfig(config, this.config); string indexName = flatConfig.IndexName; if (string.IsNullOrEmpty(indexName)) { rangeKeyPropertyName = storageConfig.RangeKeyPropertyNames.FirstOrDefault(); } else { List <string> rangeProperties; if (!storageConfig.IndexNameToRangePropertiesMapping.TryGetValue(indexName, out rangeProperties)) { rangeProperties = null; } if (rangeProperties == null || rangeProperties.Count != 1) { throw new InvalidOperationException("Unable to determine range key from index name"); } rangeKeyPropertyName = rangeProperties[0]; } List <QueryCondition> conditions = new List <QueryCondition> { new QueryCondition(rangeKeyPropertyName, op, values.ToArray()) }; return(conditions); }
private ContextSearch ConvertQueryHelper <T>(DynamoDBFlatConfig currentConfig, ItemStorageConfig storageConfig, QueryFilter filter, List <string> indexNames) { Table table = GetTargetTable(storageConfig, currentConfig); string indexName = GetQueryIndexName(currentConfig, indexNames); var queryConfig = new QueryOperationConfig { Filter = filter, ConsistentRead = currentConfig.ConsistentRead.Value, BackwardSearch = currentConfig.BackwardQuery.Value, IndexName = indexName, ConditionalOperator = currentConfig.ConditionalOperator }; if (string.IsNullOrEmpty(indexName)) { queryConfig.Select = SelectValues.SpecificAttributes; List <string> attributesToGet = storageConfig.AttributesToGet; queryConfig.AttributesToGet = attributesToGet; } else { queryConfig.Select = SelectValues.AllProjectedAttributes; } Search query = table.Query(queryConfig); return(new ContextSearch(query, currentConfig)); }
// Key creation private static DynamoDBEntry ValueToDynamoDBEntry(string propertyName, object value, ItemStorageConfig storageConfig) { PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(propertyName); var entry = ToDynamoDBEntry(propertyStorage, value); return(entry); }
private QueryFilter ComposeQueryFilter(DynamoDBFlatConfig currentConfig, object hashKeyValue, IEnumerable <QueryCondition> conditions, ItemStorageConfig storageConfig, out List <string> indexNames) { if (hashKeyValue == null) { throw new ArgumentNullException("hashKeyValue"); } // Set hash key property name // In case of index queries, if GSI, different key could be used string hashKeyProperty = storageConfig.HashKeyPropertyNames[0]; hashKeyProperty = storageConfig.GetCorrectHashKeyProperty(currentConfig, hashKeyProperty); PropertyStorage propertyStorage = storageConfig.GetPropertyStorage(hashKeyProperty); string hashAttributeName = propertyStorage.AttributeName; DynamoDBEntry hashKeyEntry = ValueToDynamoDBEntry(propertyStorage, hashKeyValue, currentConfig); if (hashKeyEntry == null) { throw new InvalidOperationException("Unable to convert hash key value for property " + hashKeyProperty); } Document hashKey = new Document(); hashKey[hashAttributeName] = hashKeyEntry; return(ComposeQueryFilterHelper(currentConfig, hashKey, conditions, storageConfig, out indexNames)); }
internal static ItemStorageConfig CreateStorageConfig(Type type) { if (type == null) { throw new ArgumentNullException("type"); } ItemStorageConfig config = new ItemStorageConfig(type); DynamoDBTableAttribute tableAttribute = Utils.GetTableAttribute(type); if (tableAttribute == null || string.IsNullOrEmpty(tableAttribute.TableName)) { throw new InvalidOperationException("No DynamoDBTableAttribute on type"); } if (string.IsNullOrEmpty(tableAttribute.TableName)) { throw new InvalidOperationException("DynamoDBTableAttribute.Table is empty or null"); } config.TableName = tableAttribute.TableName; config.LowerCamelCaseProperties = tableAttribute.LowerCamelCaseProperties; foreach (var member in type.GetMembers()) { // filter out non-fields and non-properties if (!(member is FieldInfo || member is PropertyInfo)) { continue; } // filter out properties that aren't both read and write if (!Utils.IsReadWrite(member)) { continue; } DynamoDBAttribute attribute = Utils.GetAttribute(member); // filter out ignored properties if (attribute is DynamoDBIgnoreAttribute) { continue; } Type memberType = Utils.GetType(member); string attributeName = GetAccurateCase(config, member.Name); string propertyName = member.Name; PropertyStorage propertyStorage = new PropertyStorage { Member = member, MemberType = memberType, }; if (attribute is DynamoDBVersionAttribute) { Utils.ValidateVersionType(memberType); // no conversion is possible, so type must be a nullable primitive propertyStorage.IsVersion = true; } DynamoDBPropertyAttribute propertyAttribute = attribute as DynamoDBPropertyAttribute; if (propertyAttribute != null) { if (!string.IsNullOrEmpty(propertyAttribute.AttributeName)) { attributeName = GetAccurateCase(config, propertyAttribute.AttributeName); } if (propertyAttribute is DynamoDBHashKeyAttribute) { if (propertyAttribute.Converter == null && !Utils.IsPrimitive(memberType)) { throw new InvalidOperationException("Hash key " + propertyName + " must be of primitive type"); } propertyStorage.IsHashKey = true; } if (propertyAttribute is DynamoDBRangeKeyAttribute) { if (propertyAttribute.Converter == null && !Utils.IsPrimitive(memberType)) { throw new InvalidOperationException("Range key " + propertyName + " must be of primitive type"); } propertyStorage.IsRangeKey = true; } if (propertyAttribute is DynamoDBLocalSecondaryIndexRangeKeyAttribute) { DynamoDBLocalSecondaryIndexRangeKeyAttribute lsiRangeKeyAttribute = propertyAttribute as DynamoDBLocalSecondaryIndexRangeKeyAttribute; if (lsiRangeKeyAttribute.IndexNames == null || lsiRangeKeyAttribute.IndexNames.Length == 0) { throw new InvalidOperationException("Local Secondary Index must not be null or empty"); } propertyStorage.Indexes.AddRange(lsiRangeKeyAttribute.IndexNames); } if (propertyAttribute.Converter != null) { if (!Utils.CanInstantiate(propertyAttribute.Converter) || !Utils.ImplementsInterface(propertyAttribute.Converter, typeof(IPropertyConverter))) { throw new InvalidOperationException("Converter for " + propertyName + " must be instantiable with no parameters and must implement IPropertyConverter"); } propertyStorage.Converter = Utils.Instantiate(propertyAttribute.Converter) as IPropertyConverter; } } propertyStorage.PropertyName = propertyName; propertyStorage.AttributeName = attributeName; config.AddPropertyStorage(propertyStorage); } if (config.HashKeyPropertyNames.Count == 0) { throw new InvalidOperationException("No hash key configured for type " + type.FullName); } return(config); }