Building Dynamic LINQ Expressions: Meeting DDD and Specification Pattern in C#
In contemporary software development, efficient and flexible data querying is paramount. As applications become more complex, the demands for data querying also increase. This is where Language Integrated Query (LINQ) comes into play. LINQ provides a robust syntax for querying data from various sources, including databases, arrays, and collections, in a type-safe manner. However, predefined LINQ queries often must be improved when dealing with complex and dynamic query requirements. This article explores the process of creating LINQ expressions dynamically and presents a detailed case study to demonstrate its effective implementation. By the end of this article, you will better understand how to construct dynamic LINQ expressions for improved programming capabilities.

Introduction to Dynamic LINQ Expressions
Sometimes, it can be challenging to determine the query parameters during compilation. In such situations, Dynamic LINQ expressions provide a powerful tool that enables developers to construct queries at runtime. This approach is beneficial in applications that require advanced search functionalities, where users can specify a combination of filters to refine their search results. By utilizing Dynamic LINQ expressions, developers can build intricate queries that are flexible and adaptable to changing requirements.
Advantages of Dynamically Building LINQ Expressions
Reduced Complexity and Increased Maintainability
Constructing complex queries can be effectively simplified by using dynamic LINQ expressions. This approach helps to segregate query construction concerns from business logic, thereby adhering to clean code principles and enhancing code readability and maintainability. By adopting this technique, developers can easily manage complex queries while focusing on building robust and scalable applications.
Improved Performance Optimization Opportunities
Dynamic queries are a powerful tool that allows for the selective application of filters and conditions. This can significantly reduce the amount of retrieved and processed data, resulting in performance optimization. Dynamic queries can significantly improve performance by targeting only the necessary data when only a subset of data needs to be fetched based on user inputs.
Case Study: Dynamic Product Search
Let’s consider a scenario where an application enables users to search for products based on criteria such as name, category, and price range. A flexible querying mechanism that can dynamically adjust the query based on the user’s input is necessary to implement this functionality. This mechanism should handle the complexity of user queries and provide accurate results.
Overview of Components
- SearchProductsQueryHandler: Interacts with the repository to fetch products based on dynamic criteria encapsulated in SearchProductsQuery.
- IProductReadRepository: Defines contracts for reading products from the database.
- ProductReadRepository: Implements the IProductReadRepository, executing database operations.
- ExpressionBuilder: Facilitates the dynamic construction of filter expressions based on specified criteria.
- ProductEntity: Represents the domain model for a product, including methods, specifications, and properties like name, price, and category.
Implementing the Query Handler
The SearchProductsQueryHandler class lies at the core of our dynamic query mechanism. It employs the IProductReadRepository to retrieve products from the database based on a dynamically constructed LINQ expression that matches the search criteria provided by the user.
public class SearchProductsQueryHandler : IRequestHandler<SearchProductsQuery, List<SearchProductsDto>>
{
private readonly IProductReadRepository _productReadRepository;
public SearchProductsQueryHandler(IProductReadRepository productReadRepository)
{
_productReadRepository = productReadRepository;
}
public async Task<List<SearchProductsDto>> Handle(SearchProductsQuery request, CancellationToken cancellationToken)
{
var expression = BuildExpression(request);
var products = await _productReadRepository.GetListAsync(expression, cancellationToken);
return products.Select(x => new SearchProductsDto
{
Name = x.Name,
Description = x.Description,
QuantityInStock = x.QuantityInStock,
Category = x.Category,
Price = x.Price
}).ToList();
}
private static Expression<Func<ProductEntity, bool>> BuildExpression(SearchProductsQuery request)
{
var expression = ExpressionBuilder.New<ProductEntity>(ProductEntity.IsActiveSpecification());
if (!string.IsNullOrEmpty(request.SearchTerm))
{
expression = expression.And(ProductEntity.SearchTermSpecification(request.SearchTerm));
}
if (!string.IsNullOrEmpty(request.Category))
{
expression = expression.And(ProductEntity.CategorySpecification(request.Category));
}
if (request.MinPrice.HasValue || request.MaxPrice.HasValue)
{
expression = expression.And(ProductEntity.CheckPriceSpecification(request.MinPrice, request.MaxPrice));
}
return expression;
}
}The BuildExpression method dynamically constructs a LINQ expression based on the search criteria. It utilizes the ExpressionBuilder class to combine multiple expressions using logical AND operations, enabling the creation of complex query predicates.
Expression Builder Utility
The ExpressionBuilder class provides a fluent API for dynamically constructing and combining expressions. It offers methods such as And and Or to logically combine expressions, making it easier to create complex query scenarios.
public static class ExpressionBuilder
{
public static Expression<Func<T, bool>> New<T>()
{
return x => true;
}
public static Expression<Func<T, bool>> New<T>(Expression<Func<T, bool>> expression)
{
return expression;
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
left.Body,
Expression.Invoke(right, left.Parameters[0])), left.Parameters[0]);
}
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse(
left.Body,
Expression.Invoke(right, left.Parameters[0])), left.Parameters[0]);
}
}Leveraging the Repository Pattern
The ProductReadRepository implements the IProductReadRepository interface, providing the functionality to fetch products from the database based on a given LINQ expression. This design decouples the data access logic from the business logic, adhering to the Single Responsibility Principle (SRP) and making the codebase more maintainable.
public interface IProductReadRepository
{
Task<IList<ProductEntity>> GetListAsync(
Expression<Func<ProductEntity, bool>> predicate,
CancellationToken cancellationToken);
Task<ProductEntity> FirstOrDefaultAsync(
Expression<Func<ProductEntity, bool>> predicate,
CancellationToken cancellationToken);
}public class ProductReadRepository : IProductReadRepository
{
private readonly ApplicationDbContext _dbContext;
public ProductReadRepository(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IList<ProductEntity>> GetListAsync(
Expression<Func<ProductEntity, bool>> predicate,
CancellationToken cancellationToken)
{
var result = await _dbContext.Products
.AsNoTracking()
.Where(predicate)
.ToListAsync(cancellationToken);
return result ?? new List<ProductEntity>();
}
public async Task<ProductEntity> FirstOrDefaultAsync(
Expression<Func<ProductEntity, bool>> predicate,
CancellationToken cancellationToken)
{
var result = await _dbContext.Products
.AsNoTracking()
.FirstOrDefaultAsync(predicate, cancellationToken: cancellationToken);
return result ?? ProductEntity.EmptyProduct();
}
}Meeting Rich Domain Model and Specification Pattern in Product Entity
The ProductEntity class is an exemplary instance of a rich domain model that adheres to Domain-Driven Design (DDD) principles. It functions as an entity that represents database tables while encapsulating business rules and behaviors. This dual capability enables the management of both state and behavior. In contrast to the conventional approach, I opted to leverage the Specification Pattern differently. Instead of utilizing separate classes, the specifications are integrated directly into the entity. This integration facilitates the management of state and behavior alongside the business rules, resulting in a coherent and well-organized domain model. Additionally, the class leverages the “Null Object Pattern”, showcasing its advanced design and integrating various OOP and DDD concepts.
public class ProductEntity : BaseEntity
{
public ProductEntity()
{
}
public ProductEntity(
string name,
decimal price,
int quantityInStock,
bool isActive)
{
Name = name;
Price = price;
QuantityInStock = quantityInStock;
IsActive = isActive;
}
public string Name { get; protected set; }
public string Description { get; protected set; }
public string Category { get; protected set; }
public decimal Price { get; protected set; }
public int QuantityInStock { get; protected set; }
public bool IsActive { get; protected set; }
#region specifications
public static Expression<Func<ProductEntity, bool>> IsActiveSpecification() => x => x.IsActive;
public static Expression<Func<ProductEntity, bool>> SearchTermSpecification(string key) => x =>
x.Name.StartsWith(key, StringComparison.OrdinalIgnoreCase) ||
x.Description.Contains(key, StringComparison.OrdinalIgnoreCase);
public static Expression<Func<ProductEntity, bool>> CategorySpecification(string category) =>
x => x.Category.Equals(category, StringComparison.OrdinalIgnoreCase);
public static Expression<Func<ProductEntity, bool>> CheckPriceSpecification(decimal? min, decimal? max) => x =>
(min.HasValue || x.Price >= min) && (max.HasValue || x.Price <= max);
#endregion
#region behaviors
public void SetDescription(string description)
{
Description = description;
}
public void UpdateStatus(bool isActive)
{
IsActive = isActive;
}
#endregion
// Null object pattern.
public static ProductEntity EmptyProduct()
{
var empty = new ProductEntity(string.Empty, 0, 0, false);
return empty;
}
}Conclusion
Using dynamic LINQ expressions in C# enables the creation of potent and flexible data queries. By leveraging the techniques discussed, developers can implement advanced search functionalities that adjust to user inputs, significantly enhancing the overall user experience. Combining expression trees, the repository pattern, and a fluent API for constructing expressions provides robust tools for addressing complex querying requirements in enterprise-level applications.
Please do not hesitate to contact me if you have any questions.
Peace | Love | Code






