/// <summary>
        /// Executes a get/query/scan request against the table
        /// </summary>
        internal object LoadEntities(TranslationResult translationResult, Type entityType)
        {
            // cancelling the previous index creation, if there was one
            this.CurrentIndexCreator = null;

            // skipping added and removed entities
            this.ClearModifications();

            // if a HashKey value was explicitly specified
            if (this.HashKeyValue != null)
            {
                // then adding a condition for it
                translationResult.Conditions.AddCondition
                    (
                        this.TableDefinition.HashKeys[0],
                        new SearchCondition(ScanOperator.Equal, this.HashKeyValue)
                    );
            }

#if DEBUG
            this._loadOperationStopwatch = new Stopwatch();
            this._loadOperationStopwatch.Start();
#endif

            return this.InternalLoadEntities(translationResult, entityType);
        }
        public QueryableMethodsVisitor(Type entityType, Type tableEntityType)
        {
            this._tableEntityType = tableEntityType;
            this.EnumerableParameterExp = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(entityType));

            this.TranslationResult = new TranslationResult(tableEntityType.Name);
        }
        private bool TryLoadFromCache(TranslationResult translationResult, Type entityType, out object result)
        {
            result = null;

            var conditions4Cache = translationResult.Conditions;
            // if an explicit HashKey value is specified for table, then
            // we need to remove a condition for it from SearchConditions before passing them to cache
            // implementation. That's because the entity's Type doesn't contain a HashKey property.
            if (this.HashKeyValue != null)
            {
                conditions4Cache = conditions4Cache.ExcludeField(this.TableDefinition.HashKeys[0]);
            }

            // implementing Count()
            if (translationResult.CountRequested)
            {
                int? countFromCache = this.Cache.GetCount(conditions4Cache);
                if (countFromCache.HasValue)
                {
                    result = countFromCache.Value;
                    return true;
                }
            }
            else
            {
                // getting the entities themselves from cache
                var docsFromCache = this.Cache.GetEntities(conditions4Cache, translationResult.AttributesToGet, translationResult.OrderByColumn, translationResult.OrderByDesc);
                if (docsFromCache != null)
                {
                    result = this.CreateDocArrayReader(docsFromCache, entityType, translationResult.ProjectionFunc);
                    return true;
                }
            }

            // If we failed to get from cache, then start filling an index in cache
            // (this should be started before querying DynamoDb and only when table (full) entities are requested)
            this.CurrentIndexCreator =
                (translationResult.ProjectionFunc == null)
                ?
                this.Cache.StartCreatingIndex(conditions4Cache)
                :
                this.Cache.StartCreatingProjectionIndex(conditions4Cache, translationResult.AttributesToGet)
            ;

            return false;
        }
        private bool TryExecuteQuery(TranslationResult translationResult, Type entityType, out object resultingReader)
        {
            resultingReader = null;
            QueryFilter queryFilter;
            string indexName;

            // if we failed to compose a query with table's keys and local secondary indexes
            if (!translationResult.TryGetQueryFilterForTable(this.TableDefinition, out queryFilter, out indexName))
            {
                // then trying to find a suitable Global Secondary Index
                var matchingIndex = this.TableDefinition
                    .GlobalSecondaryIndexes.Values
                    .FirstOrDefault
                    (
                        index => translationResult.TryGetQueryFilterForGlobalSeconaryIndex(index, out queryFilter)
                    );

                if (matchingIndex == null)
                {
                    return false;
                }

                indexName = matchingIndex.IndexName;
            }

            var queryConfig = new QueryOperationConfig
            {
                Filter = queryFilter,
                CollectResults = false,
                ConsistentRead = this._consistentRead,
                IndexName = indexName
            };

            // if a projection is specified - then getting only the required list of fields
            if (translationResult.AttributesToGet != null)
            {
                queryConfig.Select = SelectValues.SpecificAttributes;
                queryConfig.AttributesToGet = translationResult.AttributesToGet;
            }

            var searchResult = this.TableDefinition.Query(queryConfig);

            if (string.IsNullOrEmpty(queryConfig.IndexName))
            {
                this.Log("DynamoDb query: " + translationResult);
            }
            else
            {
                this.Log("DynamoDb index query: " + translationResult + ". Index name: " + queryConfig.IndexName);
            }

            resultingReader = this.CreateReader(searchResult, entityType, translationResult.ProjectionFunc);
            return true;
        }
        private bool TryExecuteGet(TranslationResult translationResult, Type entityType, out object result)
        {
            result = null;

            var entityKey = translationResult.TryGetEntityKeyForTable(this.TableDefinition);
            if (entityKey == null)
            {
                return false;
            }

            Document resultDoc = null;

            // first trying to get entity from cache, but only if it's not a projection
            if (ReferenceEquals(this.TableEntityType, entityType))
            {
                resultDoc = this.Cache.GetSingleEntity(entityKey);
            }

            if (resultDoc != null)
            {
                this.Log("Get from cache: " + translationResult);
            }
            else
            {
                // if the entity is not found in cache - then getting it from DynamoDb
                resultDoc = this.TableDefinition.GetItem
                    (
                        this.EntityKeyGetter.GetKeyDictionary(entityKey),
                        new GetItemOperationConfig
                        {
                            AttributesToGet = translationResult.AttributesToGet,
                            ConsistentRead = this._consistentRead
                        }
                    );

                // putting the entity to cache as well
                this.Cache.PutSingleLoadedEntity(entityKey, resultDoc);

                this.Log("Get from DynamoDb: " + translationResult);
            }

            // creating an enumerator for a single value or an empty enumerator
            result = this.CreateSingleDocReader(resultDoc, entityType, translationResult.ProjectionFunc);

            return true;
        }
        private bool TryExecuteBatchGet(TranslationResult translationResult, Type entityType, out object resultingReader)
        {
            resultingReader = null;

            var batchGet = translationResult.GetBatchGetOperationForTable(this.TableDefinition);
            if (batchGet == null)
            {
                return false;
            }

            // if a projection is specified - then getting only the required list of fields
            if (translationResult.AttributesToGet != null)
            {
                batchGet.AttributesToGet = translationResult.AttributesToGet;
            }

            batchGet.Execute();

            this.Log("DynamoDb batch get: " + translationResult);

            resultingReader = this.CreateDocArrayReader(batchGet.Results, entityType, translationResult.ProjectionFunc);
            return true;
        }
        private object InternalLoadEntities(TranslationResult translationResult, Type entityType)
        {
            // first trying to execute get
            object result;
            if (this.TryExecuteGet(translationResult, entityType, out result))
            {
                return result;
            }

            // now trying to load a query from cache
            if (this.TryLoadFromCache(translationResult, entityType, out result))
            {
                return result;
            }

            // finally requesting data from DynamoDb
            if (!this.TryExecuteBatchGet(translationResult, entityType, out result))
            {
                if (!this.TryExecuteQuery(translationResult, entityType, out result))
                {
                    result = this.ExecuteScan(translationResult, entityType);
                }
            }

            // Implementing Count().
            // Currently Count() causes a full fetch of all matched entities from DynamoDb.
            // Yes, there's an option to request just the count of them from DynamoDb.
            // But it will cost you the same money as a full fetch!
            // So, it might be more efficient to request (and put to cache) all of them right now.
            // TODO: implement an option for this.
            if (translationResult.CountRequested)
            {
                return ((IEnumerable)result).Count(entityType);
            }

            // implementing OrderBy
            if (!string.IsNullOrEmpty(translationResult.OrderByColumn))
            {
                result = ((IEnumerable)result).OrderBy(entityType, translationResult.OrderByColumn, translationResult.OrderByDesc);
            }

            return result;
        }
        private object ExecuteScan(TranslationResult translationResult, Type entityType)
        {
            var scanConfig = new ScanOperationConfig
            {
                Filter = translationResult.GetScanFilterForTable(this.TableDefinition),
                CollectResults = false
            };

            if (translationResult.AttributesToGet != null)
            {
                scanConfig.Select = SelectValues.SpecificAttributes;
                scanConfig.AttributesToGet = translationResult.AttributesToGet;
            }

            var searchResult = this.TableDefinition.Scan(scanConfig);

            this.Log("DynamoDb scan: " + translationResult);

            return this.CreateReader(searchResult, entityType, translationResult.ProjectionFunc);
        }
        /// <summary>
        /// Returns a single entity by it's keys. Very useful in ASP.Net MVC
        /// </summary>
        protected internal object Find(params object[] keyValues)
        {
            if (this.KeyNames.Length != keyValues.Length)
            {
                throw new InvalidOperationException
                (
                    string.Format
                    (
                        "Table {0} has {1} key fields, but {2} key values was provided",
                        this.TableDefinition.TableName,
                        this.KeyNames.Length,
                        keyValues.Length
                    )
                );
            }

            // constructing a GET query
            var tr = new TranslationResult(this.TableEntityType.Name);
            for (int i = 0; i < keyValues.Length; i++)
            {
                var condition = new SearchCondition
                (
                    ScanOperator.Equal,
                    keyValues[i].ToDynamoDbEntry(keyValues[i].GetType())
                );
                tr.Conditions[this.KeyNames[i]] = new List<SearchCondition> { condition };
            }

            return this.LoadEntities(tr, this.TableEntityType);
        }