Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use LINQ in CosmosDB SDK v3.0 async query?

I've been following the official documentation here: https://docs.microsoft.com/en-us/azure/cosmos-db/sql-api-get-started#Query

But I can't figure out how to correctly use a LINQ expression instead on an SQL string. I experimented with GetItemLinqQueryable, but I don't know if is the right way to use it. Also is not async.

var db = Client.GetDatabase(databaseId);
var container = db.GetContainer(containerId);

var q = container.GetItemLinqQueryable<Person>();
var result = q.Where(p => p.Name == "Name").ToList();

Is this the right way to use LINQ with Cosmos v3, and how to make it async?

like image 288
Francesco Cristallo Avatar asked Jul 23 '19 08:07

Francesco Cristallo


People also ask

Can you use LINQ with Cosmos DB?

LINQ is a . Net Programming model that gives us an abstraction over querying data. Either you query XML or File or Object you always write a same program. You can create an IQueryable object that directly queries Azure Cosmos DB, which translates the LINQ query into an Azure Cosmos DB query.

How do you implement pagination in Cosmos DB?

Each page's results is generated by a separate query execution. When query results cannot be returned in one single execution, Azure Cosmos DB will automatically split results into multiple pages. You can specify the maximum number of items returned by a query by setting the MaxItemCount .

How do you query in Cosmos database?

In the Azure Cosmos DB blade, locate and select the Data Explorer link on the left side of the blade. In the Data Explorer section, expand the NutritionDatabase database node and then expand the FoodCollection container node. Within the FoodCollection node, select the Items link. View the items within the container.


1 Answers

If your application follows a layered architecture and you'd like to give your domain layer full control over the query then it's possible to wrap cosmos IQueryable<Person> with a custom IQueryProvider that implements IAsyncEnumerable e.g.

By doing that you can hide the implementation details of asynchronously iterating over the result from your domain layer.

Persistence layer

public class PersonRepository
{
    public IQueryable<Person> Persons => _cosmosContainer.GetItemLinqQueryable<Person>().ToCosmosAsyncQueryable();
}

Domain layer

var persons = await _personRepository.Persons
    .Where(p => p.Name == "Name")
    .AsAsyncQueryable()
    .ToListAsync(cancellationToken);
  • ToListAsync is available from System.Linq.Async that can be referenced from your domain layer

Domain layer extensions

public static IAsyncEnumerable<T> AsAsyncQueryable<T>(this IQueryable<T> queryable)
{
    return (IAsyncEnumerable<T>)queryable;
}

Persistence layer extensions

internal static class CosmosAsyncQueryableExtensions
{
    internal static IQueryable<T> ToCosmosAsyncQueryable<T>(this IOrderedQueryable<T> source)
    {
        return new CosmosAsyncQueryable<T>(source);
    }
}

internal class CosmosAsyncQueryable<TResult> : IEnumerable<TResult>, IQueryable<TResult>, IAsyncEnumerable<TResult>
{
    private readonly IQueryable<TResult> _queryable;

    public CosmosAsyncQueryable(IQueryable<TResult> queryable)
    {
        _queryable = queryable;
        Provider = new CosmosAsyncQueryableProvider(queryable.Provider);
    }

    public Type ElementType => typeof(TResult);

    public Expression Expression => _queryable.Expression;

    public IQueryProvider Provider { get; }

    public IEnumerator<TResult> GetEnumerator() => _queryable.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => _queryable.GetEnumerator();

    public async IAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
    {
        var iterator = _queryable.ToFeedIterator();

        while (iterator.HasMoreResults)
        {
            foreach (var item in await iterator.ReadNextAsync(cancellationToken))
            {
                yield return item;
            }
        }
    }
}

internal class CosmosAsyncQueryableProvider : IQueryProvider
{
    private readonly IQueryProvider _provider;

    public CosmosAsyncQueryableProvider(IQueryProvider provider) => _provider = provider;

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression) =>
        new CosmosAsyncQueryable<TElement>(_provider.CreateQuery<TElement>(expression));

    public IQueryable CreateQuery(Expression expression) => CreateQuery<object>(expression);

    public object Execute(Expression expression) => _provider.Execute(expression);

    public TResult Execute<TResult>(Expression expression) => _provider.Execute<TResult>(expression);
}
like image 127
wassim-k Avatar answered Sep 25 '22 04:09

wassim-k