I am developing an application that using IDocumentClient
to perform query to CosmosDB. My GenericRepository
support for query by Id
and Predicate
.
I am in trouble when change Database from SqlServer to CosmosDb, in CosmosDb, we have partition key
. And I have no idea how to implement repository that support query by partition key
without change interface to pass partition key
as a argument.
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);
}
My implementation
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]);
}
Any help is greatly appreciated, thanks.
Cross-partition query Each physical partition has its own index. Therefore, when you run a cross-partition query on a container, you are effectively running one query per physical partition. Azure Cosmos DB will automatically aggregate results across different physical partitions.
You choose a partition key when you create a container in Azure Cosmos DB. Note that you can't change the partition key of a container. If you do need to change it, you need to migrate the container data to a new container with the correct key. A partition key consists of a path, like "/firstname", or "/name/first".
A partition key has two components: partition key path and the partition key value.
The partition key portion of a table's primary key determines the logical partitions in which a table's data is stored. This in turn affects the underlying physical partitions.
I've found out the solution to keep your repository independent of database(I am using v3 SDK for example). Just separated current interface into 2 parts:
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
Implementation:
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;
}
}
You will need to inject IPartitionSetter<T>
and call SetPartitionKey
before execute query to apply partition key here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With