问题描述
我正在开发一个使用IDocumentClient
对CosmosDB进行查询的应用程序。我的GenericRepository
支持通过Id
和Predicate
进行查询。
将数据库从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]);
}
非常感谢您的帮助。
解决方法
我已经找到了使您的存储库独立于数据库的解决方案(例如,我正在使用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<T>
并调用SetPartitionKey
。
这是您想要的吗?
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; }
}
User.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);
,
似乎您正在尝试使用文档ID的一部分作为FindByIdAsync方法中的分区键。不知道我是否可以遵循该逻辑背后的上下文,或者仅仅是随机尝试。如果您确实没有将实体的其他任何属性用作document ID itself as the partition key,则可以将good partition key用于容器(也称为集合)。
注意:我发现您在上面的示例代码中使用的是较旧的V2 SDK。因此,我在下面的答案中同时提供了V2和较新的V3 SDK示例,以防万一您仍然想继续使用V2。
对于documentClient.ReadDocumentAsync (V2 SDK)调用,不需要分区键,因为您正在按ID进行读取(并且如果分区键本身就是ID)。如果使用V3 SDK container.ReadItemAsync,则可以将id本身作为分区键进行传递,前提是您已如开始时所述选择ID作为分区键。
现在,关于其他方法FindByPredicateAsync,这是一个棘手的情况,因为您的谓词可能是实体任何属性的条件。如果传递分区键,它将仅在同一分区内查询可能与谓词匹配的其他分区中缺少的记录。 Example (V2 SDK)和Example (V3 SDK)。因此,一种选择是通过在V2 SDK的情况下将“请求选项”的EnableCrossPartitionQuery属性设置为true来使用跨分区查询,并且不设置分区键。在V3 SDK中,如果未设置QueryRequestOptions的分区键,它将自动启用交叉分区。 注意:请注意跨分区查询的性能和RU成本。
为便于整体参考,这里是Cosmos DB documentation Map。