private bool TryGetLatestDocument(
            IEnumerable <VersionedDbDocument> documents,
            VersionedDocumentReadOptions options,
            out VersionedDbDocument document,
            out DateTime createdTime,
            out DateTime modifiedTime)
        {
            var ordered = documents.OrderBy(x => x.Version).ToArray();

            if (ordered.Length == 0)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            var first = ordered[0];
            var last  = ordered[ordered.Length - 1];

            if (last.Deleted && !options.IncludeDeleted)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            createdTime  = first.Timestamp;
            modifiedTime = last.Timestamp;
            document     = last;
            return(true);
        }
        private VersionedDocumentReadResult <TDocument> ReadDocumentAsync <TDocument>(
            string id,
            DocumentTypeMapping <TDocument> mapping,
            IList <VersionedDbDocument> documents,
            VersionedDocumentReadOptions readOptions)
        {
            VersionedDbDocument document;
            DateTime            createdTime;
            DateTime            modifiedTime;

            if (!TryGetLatestDocument(documents, readOptions, out document, out createdTime, out modifiedTime))
            {
                return(null);
            }

            var metadata = new VersionedDocumentMetadata(document.Version, document.Deleted, createdTime, modifiedTime, document.Actor);

            TDocument content;
            DocumentReadFailureDetails failure;

            if (!TryGetDocumentContent(document, mapping, out content, out failure))
            {
                return(VersionedDocumentReadResult <TDocument> .CreateFailure(id, metadata, failure));
            }

            return(VersionedDocumentReadResult <TDocument> .CreateOkay(id, metadata, content));
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentQueryResult <TDocument> > GetDocumentsAsync <TDocument>(
            string query,
            IEnumerable <DbParameter> parameters,
            DocumentTypeMapping <TDocument> mapping,
            VersionedDocumentReadOptions options)
        {
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            parameters = parameters ?? new DbParameter[0];

            var builtQuery = QueryClient.CreateQueryByLatest(mapping, query, parameters);
            var documents  = await ExecuteQueryAsync(builtQuery);

            if (documents.Count == 0)
            {
                return(VersionedDocumentQueryResult <TDocument> .Empty);
            }

            options = options ?? new VersionedDocumentReadOptions();

            List <VersionedDocumentReadResult <TDocument> > loaded = new List <VersionedDocumentReadResult <TDocument> >();
            List <VersionedDocumentReadResult <TDocument> > failed = new List <VersionedDocumentReadResult <TDocument> >();

            foreach (var docs in documents.GroupBy(x => x.DocumentId))
            {
                VersionedDbDocument doc;
                DateTime            createdTime;
                DateTime            modifiedTime;

                if (!TryGetLatestDocument(docs, options, out doc, out createdTime, out modifiedTime))
                {
                    // Document exists but the latest version is deleted.
                    continue;
                }

                var metadata = new VersionedDocumentMetadata(doc.Version, doc.Deleted, createdTime, modifiedTime, doc.Actor);

                TDocument content;
                DocumentReadFailureDetails failure;

                if (TryGetDocumentContent(doc, mapping, out content, out failure))
                {
                    loaded.Add(VersionedDocumentReadResult <TDocument> .CreateOkay(doc.DocumentId, metadata, content));
                }
                else
                {
                    failed.Add(VersionedDocumentReadResult <TDocument> .CreateFailure(doc.DocumentId, metadata, failure));
                }
            }

            return(new VersionedDocumentQueryResult <TDocument>(ImmutableList.CreateRange(loaded), ImmutableList.CreateRange(failed)));
        }
        private VersionedDocumentReadResult <TDocument> ReadLatestDocumentAsync <TDocument>(
            string id,
            DocumentTypeMapping <TDocument> mapping,
            IList <VersionedDbDocument> documents,
            VersionedDocumentReadOptions readOptions)
        {
            if (!TryGetLatestDocument(documents, readOptions, out var document, out var createdTime, out var modifiedTime))
            {
                return(null);
            }

            return(CreateReadResult(id, document, mapping, createdTime, modifiedTime));
        }
        private VersionedDbDocument FindLatestDocumentIncludingDeleted(IEnumerable <VersionedDbDocument> documents)
        {
            VersionedDbDocument document;

            var options = new VersionedDocumentReadOptions();

            options.IncludeDeleted = true;

            if (TryGetLatestDocument(documents, options, out document, out _, out _))
            {
                return(document);
            }

            return(null);
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentQueryResult <TDocument> > GetDocumentsAsync <TDocument>(
            string query,
            DocumentTypeMapping <TDocument> mapping,
            VersionedDocumentReadOptions options)
        {
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            return(await GetDocumentsAsync(query, new DbParameter[0], mapping, options));
        }
        private bool TryGetLatestDocument(
            IEnumerable <VersionedDbDocument> documents,
            VersionedDocumentReadOptions options,
            out VersionedDbDocument document,
            out DateTime createdTime,
            out DateTime modifiedTime)
        {
            var ordered = documents.OrderBy(x => x.Version).ToArray();

            if (ordered.Length == 0)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            var first = ordered[0];
            var last  = ordered[ordered.Length - 1];

            if (last.Deleted && !options.IncludeDeleted)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            if (ordered.Length == 1 && !last.Latest)
            {
                // There is only one document returned by the query and that document is not the
                // latest. That means the query did not match the latest version of the document
                // because the first document version is always included in latest queries.
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            createdTime  = first.Timestamp;
            modifiedTime = last.Timestamp;
            document     = last;
            return(true);
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentReadResult <TDocument> > GetDocumentAsync <TDocument>(
            string id,
            DocumentTypeMapping <TDocument> mapping,
            VersionedDocumentReadOptions options)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            options = options ?? new VersionedDocumentReadOptions();

            var query     = QueryClient.CreateQueryById(id, mapping);
            var documents = await ExecuteQueryAsync(query);

            return(ReadLatestDocumentAsync(id, mapping, documents, options));
        }
        public async Task <IList <VersionedDocumentWithMetadata <TDocument> > > GetDocumentsAsync <TDocument>(
            DocumentTypeMapping <TDocument> mapping,
            VersionedDocumentReadOptions options)
        {
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            options = options ?? new VersionedDocumentReadOptions();

            var query     = QueryClient.CreateQueryByLatest(mapping, null);
            var documents = await ExecuteQueryAsync(query);

            List <VersionedDocumentWithMetadata <TDocument> > result = new List <VersionedDocumentWithMetadata <TDocument> >();

            foreach (var docs in documents.GroupBy(x => x.DocumentId))
            {
                VersionedDbDocument doc;
                DateTime            createdTime;
                DateTime            modifiedTime;

                if (!TryGetLatestDocument(docs, options, out doc, out createdTime, out modifiedTime))
                {
                    // Document exists but the latest version is deleted.
                    continue;
                }

                TDocument content;
                if (TryGetDocumentContent(doc, mapping, out content, out _))
                {
                    var metadata = new VersionedDocumentMetadata(doc.Version, doc.Deleted, createdTime, modifiedTime, doc.Actor);
                    result.Add(new VersionedDocumentWithMetadata <TDocument>(metadata, content));
                }
            }

            return(result);
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentReadResult <TDocument> > GetDocumentAsync <TDocument>(
            string id,
            int version,
            DocumentTypeMapping <TDocument> mapping)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            var query     = CreateQueryById(id, version, mapping);
            var documents = await ExecuteQueryAsync(query);

            var readOptions = new VersionedDocumentReadOptions {
                IncludeDeleted = true
            };

            return(ReadDocumentAsync(id, mapping, documents, readOptions));
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentBatchReadResult <TDocument> > GetDocumentsAsync <TDocument>(
            IEnumerable <string> ids,
            DocumentTypeMapping <TDocument> mapping,
            VersionedDocumentReadOptions options)
        {
            if (ids == null)
            {
                throw new ArgumentNullException(nameof(ids));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            var idSet = new HashSet <string>(ids);

            if (idSet.Count == 0)
            {
                return(VersionedDocumentBatchReadResult <TDocument> .Empty);
            }

            options = options ?? new VersionedDocumentReadOptions();

            var query     = QueryClient.CreateQueryByIds(idSet, mapping);
            var documents = await ExecuteQueriesAsync(query);

            List <VersionedDocumentReadResult <TDocument> > loaded = new List <VersionedDocumentReadResult <TDocument> >();
            List <VersionedDocumentReadResult <TDocument> > failed = new List <VersionedDocumentReadResult <TDocument> >();

            foreach (var docs in documents.GroupBy(x => x.DocumentId))
            {
                VersionedDbDocument doc;
                DateTime            createdTime;
                DateTime            modifiedTime;

                if (!TryGetLatestDocument(docs, options, out doc, out createdTime, out modifiedTime))
                {
                    // Document exists but the latest version is deleted.
                    continue;
                }

                var metadata = new VersionedDocumentMetadata(doc.Version, doc.Deleted, createdTime, modifiedTime, doc.Actor);

                TDocument content;
                DocumentReadFailureDetails failure;

                if (TryGetDocumentContent(doc, mapping, out content, out failure))
                {
                    loaded.Add(VersionedDocumentReadResult <TDocument> .CreateOkay(doc.DocumentId, metadata, content));
                }
                else
                {
                    failed.Add(VersionedDocumentReadResult <TDocument> .CreateFailure(doc.DocumentId, metadata, failure));
                }

                idSet.Remove(doc.DocumentId);
            }

            var missing = ImmutableList.CreateRange(idSet);

            return(new VersionedDocumentBatchReadResult <TDocument>(ImmutableList.CreateRange(loaded), missing, ImmutableList.CreateRange(failed)));
        }