DocumentDb CreateDocumentQuery<T> returns 项不是 T 类型

DocumentDb CreateDocumentQuery<T> returns items not of type T

我正在尝试从特定对象类型的 documentdb 中获取文档列表 -

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

这个 returns 对象的类型不是 RuleSetGroup,只要它们有一个 属性 SourceSystemId 匹配我传入的内容。我明白这是documentdb 是如何工作的,有没有办法强制类型 T 以便只返回那些对象?

我正在使用自动类型处理:

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

我的存储库对你来说可能有点太多,简短的回答是你可以 return .AsDocumentQuery() 而不是 .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;
    }

你可以这样调用上面的方法:

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

然后,当我 "might" 更新文档时,我有时会对其进行包装,以跟踪 _Etag 如果在我之前对文档进行另一次更新,它将发生变化再写下来。

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; }
}

除非您实施类型模式(向每个 Class 添加类型属性)并将其用作额外过滤器,否则您将获得不同的文档类型。

原因是因为您正在存储 NoSQL 文档,这些文档显然可以有不同的架构。 DocumentDB 对它们一视同仁,它们都是文档;当您查询时,您有责任(因为只有您知道其中的区别)来区分不同的文档类型。

如果您记录所有类型都具有属性 "Client"(例如,订单和发票)并且您使用该属性创建查询但映射到一个类型(订单),您将同时获得订单和发票与过滤器匹配的文档,因为它们是与查询匹配的文档。反序列化逻辑在你这边,不在 DocDB 中。

Here is an article 关于在 DocDB 上存储不同文档类型时的类型模式(检查基本类型模式部分)。

这样的事情可能会解决它:

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();

在应用其他过滤器之前,您可以将查询包装在将类型设置为 Where 子句的帮助器上(在 LINQ 中,您可以毫无问题地链接 Wheres)。

@Granlund 如何使 GetDocumentsAsync return DocumentWrapper 实例同时仍允许谓词查询值的属性?

这是我想出的方法,但也许您有更好的方法:

    [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;
    }