Skip to content

Repository Query data

anton-martyniuk edited this page Oct 26, 2022 · 6 revisions

The following repository interface is used for querying data:

public interface IModernQueryRepository<TEntity, in TId>
    where TEntity : class
    where TId : IEquatable<TId>

TEntity is a type of entity contained in the data store
TId is a type of the entity's identifier (mainly primary key)

IModernQueryRepository<TEntity, TId> has the following methods to get entity by id:

/// Returns an entity from the data store with the given <paramref name="id"/>
/// </summary>
/// <param name="id">The entity id</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided id is null</exception>
/// <exception cref="EntityNotFoundException">Thrown if an entity does is not found in the data store</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Found entity</returns>
Task<TEntity> GetByIdAsync(TId id, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

/// <summary>
/// Tries to return an entity from the data store with the given <paramref name="id"/>; otherwise, <see langword="null"/>
/// </summary>
/// <remarks>
/// Method does not throw exception if entity is not found
/// </remarks>
/// <param name="id">The entity id</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided id is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Found entity</returns>
Task<TEntity?> TryGetByIdAsync(TId id, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

The main difference between them is that GetByIdAsync method throws EntityNotFoundException when entity is not found in the data store, while TryGetByIdAsync returns null when entity is not found.

EntityIncludeQuery<TEntity> is an expression that describes included entities. Refer to a specific repository implementation to see if this expression is supported.

Example:

var entity = await repository.GetByIdAsync(1);
var nullableEntity = await repository.TryGetByIdAsync(1);

var entityWithProduct = await _repository.GetByIdAsync(1, new EntityIncludeQuery(x => x.Product));

IModernQueryRepository<TEntity, TId> has the following method to get all entities:

/// <summary>
/// Returns all entities.<br/>
/// IMPORTANT: there can be performance issues when retrieving large amount of entities from the data store
/// </summary>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>The list of all entities</returns>
Task<IEnumerable<TEntity>> GetAllAsync(EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

Example:

var entities = await repository.GetAllAsync();

IModernQueryRepository<TEntity, TId> has the following methods to get count of entities:

/// <summary>
/// Returns the total count of entities in the data store
/// </summary>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Count of entities</returns>
Task<long> CountAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Returns the total count of entities in the data store that match the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Count of entities</returns>
Task<long> CountAsync(Expression<Func<TEntity, bool>> predicate, EntityIncludeQuery<TEntity>? includeQuery = null,
	CancellationToken cancellationToken = default);

CountAsync can retrieve count of all entites or entities filtered by the given expression.

Example:

// Get count of all entities
var entities = await repository.CountAsync();

// Get count of filtered entities
var entities = await repository.CountAsync(x => x.Price > 500);

IModernQueryRepository<TEntity, TId> has the following method to check entity for existence:

/// <summary>
/// Determines whether the data store contains at least one entity that matches the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns><see langword="true"/> if at least one entity exists; otherwise, <see langword="false"/></returns>
Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

Example:

var exists = await repository.ExistsAsync(x => x.City == "London");

IModernQueryRepository<TEntity, TId> has the following methods to get one entity:

/// <summary>
/// Returns the first entity from the data store that matches the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Entity that matches the given <paramref name="predicate"/> or <see langword="null"/> if entity not found</returns>
Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

/// <summary>
/// Returns the single entity from the data store that matches the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="InvalidOperationException">Thrown if the data store contains more than one entity that matches the condition</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>Entity that matches the given <paramref name="predicate"/> or <see langword="null"/> if entity not found</returns>
Task<TEntity?> SingleOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

Both methods retrieve an entity by the given filtering expression.
The main difference between them is that FirstOrDefaultAsync method retrieves the first entity found in the data store, while SingleOrDefaultAsync checks if items exists twice in the data store to make sure entity is contained only once.

⚠️ SingleOrDefaultAsync performs full scan (or full index scan) of the database table (or collection) to make sure entity is contained only once. While FirstOrDefaultAsync stops scanning the database when a first matching entity is found.

Example:

var firstEntity = await repository.FirstOrDefaultAsync(x => x.City == "London");
var singleEntity = await repository.SingleOrDefaultAsync(x => x.City == "London");

IModernQueryRepository<TEntity, TId> has the following methods to get multiple entities:

/// <summary>
/// Returns all entities from the data store that match the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>A list of entities that match the condition</returns>
Task<IEnumerable<TEntity>> WhereAsync(Expression<Func<TEntity, bool>> predicate, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

/// <summary>
/// Returns certain amount of paged entities from the data store that match the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</param>
/// <param name="pageNumber">Page number. Entities to skip = (pageNumber - 1) * pageSize</param>
/// <param name="pageSize">The total number of items to select</param>
/// <param name="includeQuery">Expression that describes included entities</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="ArgumentNullException">Thrown if provided predicate is null</exception>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>A list of entities that match the condition</returns>
Task<PagedResult<TEntity>> WhereAsync(Expression<Func<TEntity, bool>> predicate, int pageNumber, int pageSize, EntityIncludeQuery<TEntity>? includeQuery = null, CancellationToken cancellationToken = default);

Both methods retrieve multiple entities by the given filtering expression. The second method supports paged filtering: a user must provide a page number and page size in order to retrieve a portion of filtered entites.

Example:

var entities = await repository.WhereAsync(x => x.Country == "USA");
var pagedResult = await repository.WhereAsync(x => x.Country == "USA", 2, 50);

IModernQueryRepository<TEntity, TId> has a special method that returns IQueryable:

/// <summary>
/// Returns <see cref="IQueryable{TEntity}"/> implementation
/// </summary>
/// <remarks>
/// IMPORTANT: The members of the returned <see cref="IQueryable{TEntity}"/> instance can throw implementation specific exceptions.<br/>
/// AsQueryable supports synchronous and asynchronous operations depending on implementation.<br/>
/// Some implementations may not support this operation at all
/// </remarks>
/// <exception cref="RepositoryErrorException">Thrown if an error occurred while retrieving entities from the data store</exception>
/// <returns>The object typed as <see cref="IQueryable{TEntity}"/></returns>
IQueryable<TEntity> AsQueryable();

This method is designed for a possibility to chain a query outside of a Repository.

Example:

var entities = await repository.AsQueryable(x => x.City == "London").OrderBy(x => x.Population).ToListAsync();

Refer to a specific repository implementation to see if this method is supported.

Clone this wiki locally