public QueryExpression <T> QueryIndex <T>(DynamoGlobalIndex index, Expression <Func <T, bool> > keyExpression)
        {
            var q = new QueryExpression <T>(this)
            {
                IndexName        = index.Name,
                Limit            = PagingLimit,
                ConsistentRead   = false,
                ScanIndexForward = this.ScanIndexForward,
            };

            q.KeyCondition(keyExpression);

            return(q);
        }
        private static DynamoMetadataType ToMetadataTable(Type type)
        {
            var alias = type.FirstAttribute <AliasAttribute>();
            var props = GetTableProperties(type);

            Converters.GetHashAndRangeKeyFields(type, props, out var hash, out var range);

            var provision = type.FirstAttribute <ProvisionedThroughputAttribute>();

            // If its a generic type, the type name will contain illegal characters (not accepted as DynamoDB table name)
            // so, remove the Tilde and make the type name unique to the runtime type of the generic
            string genericTypeNameAlias = null;

            if (type.IsGenericType)
            {
                var indexOfTilde = type.Name.IndexOf("`", StringComparison.Ordinal);
                indexOfTilde         = indexOfTilde < 1 ? type.Name.Length - 1 : indexOfTilde;
                genericTypeNameAlias = type.Name.Substring(0, indexOfTilde) + type.GetGenericArguments().Select(t => t.Name).Join();
            }

            var metadata = new DynamoMetadataType
            {
                Type               = type,
                IsTable            = true,
                Name               = alias != null ? alias.Name : genericTypeNameAlias ?? type.Name,
                ReadCapacityUnits  = provision?.ReadCapacityUnits,
                WriteCapacityUnits = provision?.WriteCapacityUnits,
            };

            metadata.Fields = props.Map(p =>
                                        new DynamoMetadataField
            {
                Parent           = metadata,
                Type             = p.PropertyType,
                Name             = Converters.GetFieldName(p),
                DbType           = Converters.GetFieldType(p.PropertyType),
                IsHashKey        = p == hash,
                IsRangeKey       = p == range,
                ExcludeNullValue = p.HasAttribute <IndexAttribute>() || p.HasAttribute <ExcludeNullValueAttribute>(),
                IsAutoIncrement  = p.HasAttribute <AutoIncrementAttribute>(),
                SetValueFn       = p.CreateSetter(),
                GetValueFn       = p.CreateGetter(),
            }).ToArray();

            metadata.HashKey  = metadata.Fields.FirstOrDefault(x => x.IsHashKey);
            metadata.RangeKey = metadata.Fields.FirstOrDefault(x => x.IsRangeKey);

            if (metadata.HashKey == null)
            {
                throw new ArgumentException($"Could not infer Hash Key in Table '{type.Name}'");
            }

            var    hashField = metadata.HashKey.Name;
            string indexName = null;

            metadata.LocalIndexes = props.Where(x => x.HasAttribute <IndexAttribute>()).Map(x =>
            {
                var indexProjection = x.FirstAttribute <ProjectionTypeAttribute>();
                var projectionType  = indexProjection?.ProjectionType ?? DynamoProjectionType.Include;
                indexName           = metadata.Name.ToIndexName(IndexType.Local, x.Name, metadata.HashKey.Name);
                return(new DynamoLocalIndex
                {
                    Name = indexName,
                    HashKey = metadata.HashKey,
                    RangeKey = metadata.GetField(x.Name),
                    ProjectionType = DynamoProjectionType.All,
                    ProjectedFields = new string[] { "" }
                });
            });

            metadata.GlobalIndexes = props.Where(x => x.HasAttribute <GlobalSecondaryIndexHashKeyAttribute>()).Map(x =>
            {
                var indexProjection = x.FirstAttribute <ProjectionTypeAttribute>();
                var globalSecondaryIndexHashKeyAttribute = x.GetCustomAttribute <GlobalSecondaryIndexHashKeyAttribute>();
                var rangeKey     = globalSecondaryIndexHashKeyAttribute.RangeKey;
                bool hasRangeKey = !string.IsNullOrEmpty(rangeKey);
                //string indexName = (hasRangeKey) ? $"{metadata.Name}-GSI-{x.Name}-{rangeKey}-Index" : $"{metadata.Name}-GSI-{x.Name}-Index";
                indexName       = metadata.Name.ToIndexName(IndexType.Global, x.Name, null, rangeKey);
                var dynamoIndex = new DynamoGlobalIndex
                {
                    Name            = indexName,
                    HashKey         = metadata.GetField(x.Name),
                    RangeKey        = metadata.GetField(rangeKey),
                    ProjectionType  = DynamoProjectionType.All,
                    ProjectedFields = new string[] { "" }
                };
                if (hasRangeKey)
                {
                    dynamoIndex.RangeKey = metadata.GetField(rangeKey);
                }
                return(dynamoIndex);
            });

            var references = type.AllAttributes <ReferencesAttribute>();

            foreach (var attr in references)
            {
                var localIndex = attr.Type.GetTypeWithGenericInterfaceOf(typeof(ILocalIndex <>));
                if (localIndex != null)
                {
                    metadata.LocalIndexes.Add(CreateLocalIndex(type, metadata, hashField, attr.Type));
                }

                var globalIndex = attr.Type.GetTypeWithGenericInterfaceOf(typeof(IGlobalIndex <>));
                if (globalIndex != null)
                {
                    metadata.GlobalIndexes.Add(CreateGlobalIndex(type, metadata, attr.Type));
                }
            }

            return(metadata);
        }