【问题标题】:Repository that support query by partition key without change interface支持按分区键查询而无需更改接口的存储库
【发布时间】:2020-08-25 06:32:38
【问题描述】:

我正在开发一个应用程序,它使用IDocumentClient 来执行对 CosmosDB 的查询。我的GenericRepository支持IdPredicate查询。

将数据库从 SqlServer 更改为 CosmosDb 时遇到麻烦,在 CosmosDb 中,我们有 partition key。而且我不知道如何实现支持partition key 查询的存储库,而无需更改接口以将partition key 作为参数传递。

public interface IRepository<T>
{
    //I can handle this one by adding value of partition key to id and split it by ":"
    Task<T> FindByIdAsync(string id);

    // I am stuck here!!!
    Task<T> FindByPredicateAsync(Expression<Func<T, bool>> predicate);
}

我的实现

public class Repository<T> : IRepository<T>
{
    private readonly IDocumentClient _documentClient;

    private readonly string _databaseId;
    private readonly string _collectionId;

    public Repository(IDocumentClient documentClient, string databaseId, string collectionId)
    {
        _documentClient = documentClient;

        _databaseId = databaseId;
        _collectionId = collectionId;
    }

    public async Task<T> FindByIdAsync(string id)
    {
        var documentUri = UriFactory.CreateDocumentUri(_databaseId, _collectionId, id);

        try
        {
            var result = await _documentClient.ReadDocumentAsync<TDocument>(documentUri, new RequestOptions
            {
                PartitionKey = ParsePartitionKey(documentId)
            });

            return result.Document;
        }
        catch (DocumentClientException e)
        {
            if (e.StatusCode == HttpStatusCode.NotFound)
            {
                throw new EntityNotFoundException();
            }

            throw;
        }
    }
    
    public async Task<T> FindByPredicateAsync(Expression<Func<T, bool>> predicate)
    {
         //Need to query CosmosDb with partition key here!
    }

    private PartitionKey ParsePartitionKey(string entityId) => new PartitionKey(entityId.Split(':')[0]);
}

非常感谢任何帮助,谢谢。

【问题讨论】:

  • 可以举出这些函数的例子让我们更好的理解吗?并显示您的 FindByIdAsync 代码的外观。
  • 我已经更新了问题。请再次检查,谢谢。
  • 希望能帮助 CosmosDB 用户理解并给你更好的答案。

标签: c# .net-core repository azure-cosmosdb data-partitioning


【解决方案1】:

我找到了使您的存储库独立于数据库的解决方案(例如,我使用的是 v3 SDK)。只是将当前界面分为两部分:

public interface IRepository<T>
{
    Task<T> FindItemByDocumentIdAsync(string documentId);

    
    Task<IEnumerable<T>> FindItemsBySqlTextAsync(string sqlQuery);

    Task<IEnumerable<T>> FindAll(Expression<Func<T, bool>> predicate = null);
}

public interface IPartitionSetter<T>
{
    string PartititonKeyValue { get; }

    void SetPartitionKey<T>(string partitionKey);
}//using factory method or DI framework to create same instance for IRepository<T> and IPartitionSetter<T> in a http request

实施:

public class Repository<T> : IRepository<T>, IPartitionSetter<T>
{
    //other implementation

    public async Task<IEnumerable<T>> FindAll(Expression<Func<T, bool>> predicate = null)
    {
        var result = new List<T>();
        var queryOptions = new QueryRequestOptions
        {
            MaxConcurrency = -1,
            PartitionKey = ParsePartitionKey()
        };

        IQueryable<T> query = _container.GetItemLinqQueryable<T>(requestOptions: queryOptions);

        if (predicate != null)
        {
            query = query.Where(predicate);
        }

        var setIterator = query.ToFeedIterator();
        while (setIterator.HasMoreResults)
        {
            var executer = await setIterator.ReadNextAsync();

            result.AddRange(executer.Resource);
        }

        return result;
    }

    private string _partitionKey;

    public string PartititonKeyValue => _partitionKey;

    private PartitionKey? ParsePartitionKey()
    {
        if (_partitionKey == null)
            return null;
        else if (_partitionKey == string.Empty)
            return PartitionKey.None;//for query documents with partition key is empty
        else
            return new PartitionKey(_partitionKey);
    }

    public void SetPartitionKey<T>(string partitionKey)
    {
        _partitionKey = partitionKey;
    }
}

您需要在执行查询之前注入IPartitionSetter&lt;T&gt; 并调用SetPartitionKey 以在此处应用分区键。

【讨论】:

    【解决方案2】:

    这是你想要的吗?

    BaseModel.cs(不需要。仅当您使用通用保存/更新时才需要)

    public class BaseModel
    {
         public int Id { get; set; }
         public DateTime? CreatedDate { get; set; }
         public string CreatedBy { get; set; }
         public DateTime? ModifiedDate { get; set; }
         public string ModifiedBy { get; set; } 
    }
    

    用户.cs

    public class User : BaseModel
    {
         public string Name { get; set; }
         public int? Age { get; set; }
    }
    

    YourRepository.cs

    public Task<T> FindByPredicateAsync(Expression<Func<T, bool>> predicate)
    {
         return _context.Set<T>().Where(predicate).FirstOrDefault();
    }
    

    YourController.cs

    string id = "1:2";
    string[] ids = id.Split(":");
    
    Expression<Func<User, bool>> exp = x => ids.Contains(x.Id);
    FindByPredicateAsync<User>(exp);
    

    【讨论】:

    • 你了解 CosmosDb 吗?您的代码似乎在谈论 EF 和 SQL Server
    • 抱歉,我不了解 CosmosDb。请忽略我的帖子。是的,这段代码是关于 EFCore 和 SQL Server 的。
    【解决方案3】:

    您似乎正在尝试在 FindByIdAsync 方法中使用文档 ID 的 一部分 作为分区键。不确定我是否可以遵循该逻辑背后的上下文,或者这只是随机尝试。如果您确实没有实体的任何其他属性成为good partition key,则可以将document ID itself as the partition key 用于您的容器(又名集合)。

    注意:我看到您在上面的示例代码中使用了较旧的 V2 SDK。因此,我在下面的回答中提供了 V2 和较新的 V3 SDK 示例,以防您现在仍想坚持使用 V2。

    对于documentClient.ReadDocumentAsync (V2 SDK) 调用,不需要分区键,因为您是按 ID 读取的(如果您的分区键是 id 本身)。在 V3 SDK container.ReadItemAsync 的情况下,您可以将 id 本身作为分区键传递,假设您选择它作为我在开头提到的分区键。

    现在关于另一种方法 FindByPredicateAsync,这是一个棘手的情况,因为您的谓词可能是实体的任何属性的条件。如果您传递分区键,它将仅在同一分区内查询可能与谓词匹配的其他分区中缺少的记录。 Example (V2 SDK)Example (V3 SDK)。因此,一种选择是在 V2 SDK 的情况下通过将 Request Options 的 EnableCrossPartitionQuery 属性设置为 true 来使用跨分区查询,并且不设置分区键。在 V3 SDK 中,如果不设置 QueryRequestOptions 的分区键,它会自动启用跨分区。 注意:注意跨分区查询的性能和 RU 成本影响。

    为了方便整体参考,这里是Cosmos DB documentation Map

    【讨论】:

    • partition key is not required since you are reading by ID => 不带分区键的查询对RU和性能有影响吗?
    • it's a tricky situation since your predicate might be a condition on any property(ies) of the entity => 我想知道为什么 CosmosDb 工程师在使用where中的分区键执行查询时不支持自动检测分区键
    • 按ID读取,不会影响RU和性能
    • 关于第二个问题,where条件中的查询部分可能有实体和值的任意字段(查询的右边部分)。如果您不传递分区键(作为值,而不是字段名称),如何自动检测到?价值可以是任何东西。
    • 另外,如果您的 id 本身是分区键,则无论如何您都可以在 FindByIdAsync 方法中将其作为分区 id 本身传递以保持干净。
    猜你喜欢
    • 2013-01-21
    • 2019-01-09
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    相关资源
    最近更新 更多