Skip to content

In Memory Service Query data

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

The following service interface is used for querying data:

public interface IModernInMemoryService<TEntityDto, out TEntityDbo, TId>
    where TEntityDto : class
    where TEntityDbo : class
    where TId : IEquatable<TId>

TEntityDto is a type of entity returned from the service
TEntityDbo is a type of entity contained in the repository
TId is a type of the entity's identifier (mainly primary key)

✅ All methods of in memory service return data from the in-memory cache, not from db!

IModernQueryInMemoryService<TEntityDto, TEntityDbo, TId> has the following methods to get entity by id:

/// <summary>
/// Returns an entity with the given <paramref name="id"/>
/// </summary>
/// <param name="id">The entity id</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</exception>
/// <exception cref="InternalErrorException">If a service internal error occurred</exception>
/// <returns>The entity</returns>
Task<TEntityDto> GetByIdAsync(TId id, CancellationToken cancellationToken = default);

/// <summary>
/// Tries to return an entity 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="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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>The entity</returns>
Task<TEntityDto?> TryGetByIdAsync(TId id, CancellationToken cancellationToken = default);

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

Example:

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

IModernQueryInMemoryService<TEntityDto, TEntityDbo, 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
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>The list of all entities</returns>
Task<List<TEntityDto>> GetAllAsync(CancellationToken cancellationToken = default);

Example:

var entities = await service.GetAllAsync();

IModernQueryInMemoryService<TEntityDto, TEntityDbo, TId> has the following methods to get count of entities:

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

/// <summary>
/// Returns the total count of entities that match the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>Count of entities</returns>
Task<long> CountAsync(Func<TEntityDto, bool> predicate, CancellationToken cancellationToken = default);

CountAsync can retrieve count of all entites or entities filtered by the given expression from the in-memory cache.

Example:

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

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

IModernQueryInMemoryService<TEntityDto, TEntityDbo, 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="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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns><see langword="true"/> if at least one entity exists; otherwise, <see langword="false"/></returns>
Task<bool> ExistsAsync(Func<TEntityDto, bool> predicate, CancellationToken cancellationToken = default);

Example:

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

IModernQueryInMemoryService<TEntityDto, TEntityDbo, TId> has the following methods to get one entity:

/// <summary>
/// Returns the first entity that matches the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>Entity that matches the given <paramref name="predicate"/> or <see langword="null"/> if entity not found</returns>
Task<TEntityDto?> FirstOrDefaultAsync(Func<TEntityDto, bool> predicate, CancellationToken cancellationToken = default);

/// <summary>
/// Returns the single entity that matches the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>Entity that matches the given <paramref name="predicate"/> or <see langword="null"/> if entity not found</returns>
Task<TEntityDto?> SingleOrDefaultAsync(Func<TEntityDto, bool> predicate, 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 in-memory cache, while SingleOrDefaultAsync checks if items exists twice in the in-memory cache to make sure entity is contained only once.

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

Example:

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

IModernQueryInMemoryService<TEntityDto, TEntityDbo, TId> has the following methods to get multiple entities:

/// <summary>
/// Returns all entities that match the given <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each element for condition</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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>A list of entities that match the condition</returns>
Task<List<TEntityDto>> WhereAsync(Func<TEntityDto, bool> predicate, 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="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="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>A list of entities that match the condition</returns>
Task<PagedResult<TEntityDto>> WhereAsync(Func<TEntityDto, bool> predicate, int pageNumber, int pageSize, CancellationToken cancellationToken = default);

Both methods retrieve multiple entities by the given filtering expression from the in-memory cache. 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 service.WhereAsync(x => x.Country == "USA");
var pagedResult = await service.WhereAsync(x => x.Country == "USA", 2, 50);

IModernQueryInMemoryService<TEntityDto, TEntityDbo, TId> has two special methods that return IEnumerable and return IQueryable accordingly:

/// <summary>
/// Returns <see cref="IEnumerable{TEntityDto}"/> implementation with data from the cache
/// </summary>
/// <remarks>
/// IMPORTANT: The members of the returned <see cref="IEnumerable{TEntityDto}"/> instance can throw implementation specific exceptions
/// </remarks>
/// <exception cref="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>The object typed as <see cref="IEnumerable{TEntityDto}"/></returns>
IEnumerable<TEntityDto> AsEnumerable();

/// <summary>
/// Returns <see cref="IQueryable{TEntityDto}"/> implementation with data from the data store
/// </summary>
/// <remarks>
/// IMPORTANT: The members of the returned <see cref="IQueryable{TEntityDto}"/> instance can throw implementation specific exceptions
/// </remarks>
/// <exception cref="InternalErrorException">Thrown if an error occurred while retrieving entities</exception>
/// <returns>The object typed as <see cref="IQueryable{TEntityDto}"/></returns>
IQueryable<TEntityDbo> AsQueryable();

AsEnumerable method is designed for a possibility to chain an Enumerable query from the in-memory cache outside of Service.
AsQueryable method is designed for a possibility to chain a query from the data store outside of a Repository and Service.

Example:

// Returns data from the in-memory cache
var entities = await service.AsEnumerable(x => x.City == "London").OrderBy(x => x.Population).ToListAsync();

// Returns data from the repository (database)
var entities2 = await service.AsQueryable(x => x.City == "London").OrderBy(x => x.Population).ToListAsync();

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

Clone this wiki locally