newbie_86
newbie_86

Reputation: 4610

DocumentDb CreateDocumentQuery<T> returns items not of type T

I'm trying to get a list of documents from documentdb of specific object type -

 _client.CreateDocumentQuery<RuleSetGroup>(_collectionLink)
            .Where(f => f.SourceSystemId == sourceSystemId).AsEnumerable().ToList();

This returns objects of types other than RuleSetGroup, as long as they have a property SourceSystemId matching what I pass in. I understand this is how documentdb works, is there a way to enforce the type T so only those objects are returned?

I am using Auto Type Handling:

 JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
                {
                    TypeNameHandling = TypeNameHandling.Auto
                };

Upvotes: 1

Views: 1408

Answers (3)

Granlund
Granlund

Reputation: 126

My repository might be a little too much for you, the short answer is that you can return .AsDocumentQuery() instead of .ToList()

public async Task<IEnumerable<T>> GetDocumentsAsync<T>(Expression<Func<T, bool>> predicate, int maxReturnedDocuments = -1,
        bool enableCrossPartitionQuery = true, int maxDegreeOfParallellism = -1, int maxBufferedItemCount = -1)
    {
        //MaxDegreeofParallelism default = 0, add -1 to let SDK handle it instead of a fixed 1 network connection
        var feedOptions = new FeedOptions
        {
            MaxItemCount = maxReturnedDocuments,
            EnableCrossPartitionQuery = enableCrossPartitionQuery,
            MaxDegreeOfParallelism = maxDegreeOfParallellism,
            MaxBufferedItemCount = maxBufferedItemCount
        };

        IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
        UriFactory.CreateDocumentCollectionUri(_databaseName, _collectionName), feedOptions)
        .Where(predicate)
        .AsDocumentQuery();

        List<T> results = new List<T>();
        while (query.HasMoreResults)
        {
            var res = await query.ExecuteNextAsync<T>();
            results.AddRange(res);
        }
        return results;
    }

You can call the above method like this:

var ecsterConfigs = await repoBO.GetDocumentsAsync<EcsterPaymentConfig>(c => c.ValidTo == null && c.Type == type);

And then I have a wrapper around it sometimes when I "might" do an update of document, to keep track of the _Etag which will change if there is another update on the document before I write it down again.

public class DocumentWrapper<DocumentType>
{
    public DocumentWrapper(Document document)
    {
        Value = (DocumentType)(dynamic)document;
        ETag = document.ETag;
        TimeStamp = document.Timestamp;
    }
    public DocumentType Value { get; set; }
    public string ETag { get; set; }
    public DateTime TimeStamp { get; set; }
}

Upvotes: 1

David Hardin
David Hardin

Reputation: 167

@Granlund how do you make GetDocumentsAsync return DocumentWrapper instances while still allowing the predicate to query on the properties of the Value?

Here is what I came up with but maybe you have a better way:

    [TestMethod]
    [TestCategory("CosmosDB.IntegrationTest")]
    public async Task AddAndReadDocumentWrapperViaQueryAsync()
    {
        var document = new Foo { Count = 1, Name = "David" };
        var response = await client.CreateDocumentAsync(documentCollectionUri, document);

        var id = response.Resource.Id;

        var queryResult = await GetWrappedDocumentsAsync<Foo>(f => f.Where(a => a.Name == "David"));

        foreach (var doc in queryResult)
        {
            Assert.AreEqual("David", doc.Value.Name);
        }
    }

    public class Foo
    {
        public int Count { get; set; }

        public string Name { get; set; }
    }

    public class DocumentWrapper<DocumentType>
    {
        public DocumentWrapper(Document document)
        {
            Value = (DocumentType)(dynamic)document;
            ETag = document.ETag;
            TimeStamp = document.Timestamp;
        }

        public DocumentType Value { get; set; }

        public string ETag { get; set; }

        public DateTime TimeStamp { get; set; }
    }

    public async Task<IEnumerable<DocumentWrapper<T>>> GetWrappedDocumentsAsync<T>(
        Func<IQueryable<T>, IQueryable<T>> query,
        int maxReturnedDocuments = -1,
        bool enableCrossPartitionQuery = true,
        int maxDegreeOfParallellism = -1,
        int maxBufferedItemCount = -1)
    {
        //MaxDegreeofParallelism default = 0, add -1 to let SDK handle it instead of a fixed 1 network connection
        var feedOptions = new FeedOptions
        {
            MaxItemCount = maxReturnedDocuments,
            EnableCrossPartitionQuery = enableCrossPartitionQuery,
            MaxDegreeOfParallelism = maxDegreeOfParallellism,
            MaxBufferedItemCount = maxBufferedItemCount
        };

        IDocumentQuery<T> documentQuery =
            query(client.CreateDocumentQuery<T>(documentCollectionUri, feedOptions)).AsDocumentQuery();

        var results = new List<DocumentWrapper<T>>();
        while (documentQuery.HasMoreResults)
        {
            var res = await documentQuery.ExecuteNextAsync<Document>();

            results.AddRange(res.Select(d => new DocumentWrapper<T>(d)));
        }

        return results;
    }

Upvotes: 0

Matias Quaranta
Matias Quaranta

Reputation: 15583

You will get different document types unless you implement a Type Pattern (adding a Type attribute to each Class) and use it as extra filter.

The reason is because you are storing NoSQL documents, which can obviously have different schema. DocumentDB treats them all equally, they are all documents; when you query, it's your responsability (because only you know the difference) to separate the different document types.

If you document Types all have an attribute "Client" (for example, Orders and Invoices) and you create a query with that attribute but mapped to one Type (Orders), you will get both Orders and Invoices that match the filter because they are documents that match the query. The deserialization logic is on your end, not within DocDB.

Here is an article regarding that Type Pattern when storing different document Types on DocDB (check the Base Type Pattern section).

Something like this might solve it:

public abstract class Entity
{
    public Entity(string type)
    {
        this.Type = type;
    }
    /// <summary>
    /// Object unique identifier
    /// </summary>
    [Key]
    [JsonProperty("id")]
    public string Id { get; set; }
    /// <summary>
    /// Object type
    /// </summary>
    public string Type { get; private set; }
}

public class RuleSetGroup : Entity
{
    public RuleSetGroup():base("rulesetgroup")

}

public class OtherType : Entity
{
    public OtherType():base("othertype")

}

 _client.CreateDocumentQuery<RuleSetGroup>(_collectionLink).Where(f => f.Type == "rulesetgroup" && f.SourceSystemId == sourceSystemId).AsEnumerable().ToList();

You can wrap queries on helpers that set the type as a Where clause before applying your other filters (in LINQ you can chain Wheres without problem).

Upvotes: 1

Related Questions