Exposing IQueryable in a CQRS Query Stack

Sending IQueryable<T> to higher layers can be a touchy subject…

  • “there is nothing more frustrating (or more expensive to maintain) than a data layer leaking up the stack”
  • ”…literally no case where EF + Repository that exposes IQueryable makes sense”
  • “EF-generated anything should never leave the data layer”
  • *“it’s bad for unit testing”*

With CQRS, it is acceptable - and even beneficial to expose IQueryable<T> within the Query Stack.

Query Stack

“When your goal is simply creating a domain model for read-only operations, everything comes easier and classes are simpler overall” - Esposito, Saltarello

In a Query-only (Read-only) Model, we don’t have to worry about designing the Domain Model to be fully CRUD-able.

Instead of centralized Aggregates hidden behind explicit interfaces, it becomes ok for the Domain Model to be anemic and for Aggregates to be decentralized (Esposito, Saltarello).

Before… The Repository

If we were to follow everyone else’s rules about IQueryable, we would write a method in a Repository<T> class that filters and returns the in-memory data we need for a Use Case.

Here’s an example of this canonical IRepository implementation:

public interface IRepository<T> where T : class {
    IEnumerable<T> GetAll(Expression<Func<T, bool>> predicate = null);
    T Get(Expression<Func<T, bool>> predicate);
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
    long Count();
}
public class PartRepository : IRepository<Part> {
    private readonly IPartsCatalogDbContext _dbContext;
    public PartRepository(IPartsCatalogDbContext dbContext) {
        _dbContext = dbContext;
    }
    public IEnumerable<Part> GetAll(Expression<Func<Part, bool>> predicate = null) {
        return _dbContext.Parts.Where(predicate).ToEnumerable();
    }
	public IEnumerable<Part> GetAllParts() {
        return GetAll(x => true);
    }
    // ... Get(), Add(Part part), Update(Part part), Delete(Part part) ...
}

After… The Read Model Facade

We can start refactoring to a Query Stack by restricting DbContext to be read-only with a Read Model Facade:

public class PartsCatalogQueryDbContext : IDisposable {
    private readonly IPartsCatalogDbContext _db;
    public PartsCatalogQueryDbContext() {
        _db = new PartsCatalogDbContext();
    }
    public IQueryable<Part> Parts {
        get { return _db.Parts; }
    }
    public IQueryable<Component> Components {
        get { return _db.Components; }
    }
    public IQueryable<LaborSequence> LaborSequences {
        get { return _db.LaborSequences; }
    }
    public void Dispose() {
        _db.Dispose();
    }
}

Here, we are taking away all the CRUD power of DbContext and only exposing DbSet<T> as IQueryable<T>.

Now that we have a Read Model Facade exposing IQueryable<T>, we can build queries for our specific Use Cases. We no longer need specialized Repository methods beyond our GetAll(Expression<Func<Part, bool>> predicate) method:

We can build these queries in IRequestHandler<Query, Result> with the Mediator Pattern using MediatR, like we did in my last post:

public class Index {
    public class Query : IRequest<CatalogViewModel> {   
    }
    public class QueryHandler : IRequestHandler<Query, CatalogViewModel> {
        private readonly PartsCatalogQueryDbContext _db;
        public QueryHandler(PartsCatalogQueryDbContext db) {
            _db = db;
        }
        public CatalogViewModel Handle(Query message) {
            var catalogViewModel = new CatalogViewModel();
            catalogViewModel.Items.AddRange(
                _db.Parts.Select(p => new CatalogItemViewModel() {
                    PartNumber = p.PartNumber,
                    ExtendedDescription = p.ExtendedDescription
                })
            );
            return catalogViewModel;
        }
    }
}

We create multiple IRequestHandler<Query, Result> for each View, referencing the IMediator instance in the Controller.

public class HomeController : Controller {
    private readonly IMediator _mediator;
    public HomeController(IMediator mediator) {
        _mediator = mediator;
    }
    public ActionResult Index() {
        var catalogViewModel = _mediator.Send(new Index.Query());
        return View(catalogViewModel);
    }
    // ...
}

Request Handlers can use Layered Expression Trees to build more precise queries for other Views as needed.

Summary

We discovered a scenario where a CQRS Query Stack makes it beneficial to use IQueryable<T> for the Presentation Layer’s “currency” (Esposito, Saltarello)

Instead of writing specialized Repository methods for each View, we can use IQueryable to build queries for data that we need at the Presentation Layer. This lets us make change within the layers that actually use the data.

It also makes for clean code that resembles the Ubiquitous Language more closely.

As always, stay tuned for much more on this subject! I also like hearing from you, so sign up for my newsletter, shoot me an email, and/or leave a comment below!

Sample code here


Related Posts:


Sources:

Tweet
comments powered by Disqus