Skip to content

Creating Query Strategies

There are several ways to create query strategies depending on your needs. This guide covers the main approaches.

Single Entity Query

The most common strategy is querying a single entity and for that purpose the interface IEntityQueryStrategy<TSource, TResult> and its abstract class implementation EntityQueryStrategy<TSource, TResult> was created.

Here's an example of a entity query strategy that filters a list of entities based on a set of conditions:

csharp
public class GetProductNameQueryStrategy : EntityQueryStrategy<Product, ProductName>
{
    protected override IQueryable<ProductName> ExecuteCore(IQueryable<Product> source)
    {
        return source
            .Where(ProductSpecification.IsInStock)
            .OrderBy(e => e.Name)
            .Select(e => new ProductName { Id = e.Id, Name = e.Name });
    }
}
csharp
public class GetProductNameQueryStrategy : EntityQueryStrategy<Product, ProductName>
{
    protected override IQueryable<ProductName> ExecuteCore(IQueryable<Product> source)
    {
        return from p in source.Where(ProductSpecification.IsInStock)
            orderby p.Name
            select new ProductName { Id = p.Id, Name = p.Name };
    }
}

or, you can define only the preconditions:

csharp
public class GetProductInStockQueryStrategy : EntityQueryStrategy<Product>
{
    protected override IEnumerable<Specification<Product>> GetPreconditions()
    {
        yield new ProductIsInStock();
    }
}
csharp
public class GetProductInStockQueryStrategy : EntityQueryStrategy<Product>
{
    protected override IEnumerable<Specification<Product>> GetPreconditions()
    {
        yield Specification.Create<Product>(p => p.AvailableQuantity > 0);
    }
}

or yet, you can create a static class as a provider of query strategies:

csharp
public static class ProductQueryStrategy
{
    public static IEntityQueryStrategy<Product, ProductName> GetName() =>
        QueryStrategy.CreateForEntity(
            (IQueryable<Product> source) => source
                .Where(ProductSpecification.IsInStock)
                .OrderBy(e => e.Name)
                .Select(e => new ProductName { Id = e.Id, Name = e.Name });
}
csharp
public static class ProductQueryStrategy
{
    public static IEntityQueryStrategy<Product, ProductName> GetName() =>
        QueryStrategy.CreateForEntity(
            (IQueryable<Product> source) =>
                from p in source.Where(ProductSpecification.IsInStock)
                orderby p.Name
                select new ProductName { Id = p.Id, Name = p.Name });
}

Multiple Entities Query

For the cases where multiple entities need to be queried, the interface IQueryStrategy<TResult> was created.

You can implement the interface directly, as the example below:

csharp
public class GetProductNameOfOpenStoreQueryStrategy : IQueryStrategy<ProductName>
{
    public IQueryable<TResult> Execute(IQuerySource source) =>
        source => from product in source.GetSetUsing(ProductSpecification.IsInStock)
            join store in source.GetSetUsing(StoreSpecification.IsOpen) on
                product.StoreId equals store.Id
            orderby product.Name
            select new ProductName { Id = e.Id, Name = e.Name };
}

or, can create a static class as a provider of query strategies:

csharp
public static class ProductQueryStrategy
{
    public static IQueryStrategy<ProductName> GetNameOfOpenStore() =>
        QueryStrategy.Create(
            source => from product in source.GetSetUsing(ProductSpecification.IsInStock)
                join store in source.GetSetUsing(StoreSpecification.IsOpen) on
                    product.StoreId equals store.Id
                orderby product.Name
                select new ProductName { Id = e.Id, Name = e.Name });
}

Value Types Results

When your query strategy returns value types (structs) such as int, decimal, DateTime, or other structs, you should use them with the QueryValue() method instead of Query(). Both IEntityQueryStrategy<TSource, TResult> and IQueryStrategy<TResult> support value type results when TResult is constrained to struct.

csharp
public class GetActiveProductCountQueryStrategy : EntityQueryStrategy<Product, int>
{
    protected override IQueryable<int> ExecuteCore(IQueryable<Product> source)
    {
        return source
            .Where(ProductSpecification.IsInStock)
            .Select(p => p.AvailableQuantity);
    }
}
csharp
public static class ProductQueryStrategy
{
    public static IEntityQueryStrategy<Product, decimal> GetAveragePrice() =>
        QueryStrategy.CreateForEntity(
            (IQueryable<Product> source) => source
                .Where(ProductSpecification.IsInStock)
                .Select(p => p.Price));
}
csharp
public static class OrderQueryStrategy
{
    public static IQueryStrategy<decimal> GetTotalRevenue() =>
        QueryStrategy.Create(
            source => from order in source.GetSet<Order>()
                where order.Status == OrderStatus.Completed
                select order.TotalAmount);
}

These query strategies should be used with the QueryValue() method on IDbQuerySession:

csharp
await using (var session = querySessionFactory.Create())
{
    // Using entity query strategy
    IDbQueryValue<int> quantityQuery = session.QueryValue(new GetActiveProductCountQueryStrategy());
    int? totalQuantity = await quantityQuery.FirstOrDefaultAsync();

    // Using factory method
    IDbQueryValue<decimal> priceQuery = session.QueryValue(ProductQueryStrategy.GetAveragePrice());
    decimal? avgPrice = await priceQuery.FirstOrDefaultAsync();

    // Using multi-entity query strategy
    IDbQueryValue<decimal> revenueQuery = session.QueryValue(OrderQueryStrategy.GetTotalRevenue());
    decimal? revenue = await revenueQuery.SingleOrDefaultAsync();
}

Why use QueryValue for value types?

The QueryValue() method returns IDbQueryValue<T> which provides proper nullable semantics for value types. Methods like FirstOrDefaultAsync() and SingleOrDefaultAsync() return T? (nullable value type), allowing you to distinguish between "no result found" (null) and a result with a zero value.

Released under the MIT License.