Skip to content

Queries

xhafan edited this page Dec 17, 2018 · 38 revisions

CoreDdd encourages CQRS and application queries to be explicitly implemented as a query and a query handler instead of querying data directly in the code. A CoreDdd query implements IQuery interface, and a query handler implements IQueryHandler<TQuery> interface. CoreDdd provides query handler base classes for SQL queries. Derive a query handler from BaseQueryOverHandler for NHibernate QueryOver SQL queries, or from BaseAdoNetQueryHandler for ADO.NET SQL queries. If you need to query a non-SQL data storage (e.g. NoSQL database, a text file, etc.), you can implement you own query handler by implementing IQueryHandler interface.

CoreDdd queries should return data in a DTO form. The recommended way is to map DTO objects into database views, where the database view contains SQL which queries domain entity tables. The advantage of this approach is that a developer can fine tune the database view SQL code instead of relying on the generated SQL code when using NHibernate QueryOver/Criteria with domain entities and their associations.

Before creating a new query, first persist the entities the query will use. Then create a new query, for instance get ships by name query:

public class GetShipsByNameQuery : IQuery
{
    public string ShipName { get; set; }
}

The query will return ship data in a ShipDto:

public class ShipDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

ShipDto class will be automatically mapped into a database view ShipDto by FluentNHibernate. This database view does not exist, a needs to be created manually by executing the following SQL script, SQLite example:

drop view if exists ShipDto;

create view ShipDto
as
select 
    Id
    , Name
from Ship 

Now add a new query handler:

public class GetShipsByNameQueryHandler : BaseQueryOverHandler<GetShipsByNameQuery>
{
    public GetShipsByNameQueryHandler(NhibernateUnitOfWork unitOfWork) 
        : base(unitOfWork)
    {
    }

    protected override IQueryOver GetQueryOver<TResult>(GetShipsByNameQuery query)
    {
        return Session.QueryOver<ShipDto>()
                      .WhereRestrictionOn(x => x.Name)
                      .IsLike($"%{query.ShipName}%");
    }
}

Execute the query using the following code:

using (var unitOfWork = new NhibernateUnitOfWork(nhibernateConfigurator))
{
    unitOfWork.BeginTransaction();

    var shipRepository = new NhibernateRepository<Ship>(unitOfWork);
    
    try
    {
        var ship = new Ship("lady starlight", tonnage: 10m);
        await shipRepository.SaveAsync(ship);

        unitOfWork.Flush();

        var getShipByNameQuery = new GetShipsByNameQuery {ShipName = "lady"};
        var getShipByNameQueryHandler = new GetShipsByNameQueryHandler(unitOfWork);

        var shipDtos = await getShipByNameQueryHandler.ExecuteAsync<ShipDto>(getShipByNameQuery);

        Console.WriteLine($"Ship by name query was executed. Number of ships queried: {shipDtos.Count()}");

        await unitOfWork.CommitAsync();
    }
    catch
    {
        await unitOfWork.RollbackAsync();
        throw;
    }
}

The code sample above is available here as .NET Core console app.

Query executor

Another way how to execute a query is to use CoreDdd QueryExecutor. Instead of instantiating and executing the query handler directly, QueryExecutor can be used to instantiate and execute the query handler based on the query type:

    var queryExecutor = new QueryExecutor(new FakeQueryHandlerFactory(unitOfWork));
    var getShipByNameQuery = new GetShipsByNameQuery {ShipName = "lady"};
    var shipDtos = await queryExecutor.ExecuteAsync<GetShipsByNameQuery, ShipDto>(getShipByNameQuery);

The code above uses FakeQueryHandlerFactory which is a query handler abstract factory implementation, which creates an instance of GetShipsByNameQueryHandler based on the GetShipsByNameQuery type.

The code sample above is available here.

Query executor with an IoC container

QueryExecutor is not supposed to be used directly, instead IQueryExecutor interface should be used together with an IoC container to execute queries. Most IoC containers (e.g. Castle Windsor or Ninject) are able to provide an abstract factory implementation out of the box, so no manual implementation like FakeQueryHandlerFactory is needed.

To start using QueryExecutor with an IoC container, add an IoC container into the project (e.g. Castle Windsor or Ninject) and register the components into the IoC container, Castle Windsor example:

ioCContainer.AddFacility<TypedFactoryFacility>();

ioCContainer.Register(
    Component.For<IQueryHandlerFactory>().AsFactory(), // register query handler factory (no real factory implementation needed :)
    Component.For<IQueryExecutor>() // register query executor
        .ImplementedBy<QueryExecutor>()
        .LifeStyle.Transient,
    Classes
        .FromAssemblyContaining<GetShipsByNameQuery>() // register all query handlers in this assembly
        .BasedOn(typeof(IQueryHandler<>))
        .WithService.FirstInterface()
        .Configure(x => x.LifestyleTransient()),
    Component.For<INhibernateConfigurator>() // register nhibernate configurator
        .ImplementedBy<CoreDddSampleNhibernateConfigurator>()
        .LifeStyle.Singleton,
    Component.For<NhibernateUnitOfWork>() // register nhibernate unit of work
        .LifeStyle.PerThread
);

And then resolve IQueryExecutor and execute the query:

var queryExecutor = ioCContainer.Resolve<IQueryExecutor>();
var getShipByNameQuery = new GetShipsByNameQuery { ShipName = "lady" };
var shipDtos = await queryExecutor.ExecuteAsync<GetShipsByNameQuery, ShipDto>(getShipByNameQuery);

The code sample above is available here.

Query executor dependency injection

Now getting to a real life usage example. In the real life, an application component (ShipController) will get IQueryExecutor injected into a constructor by an IoC container:

public class ShipController
{
    private readonly IQueryExecutor _queryExecutor;

    public ShipController(IQueryExecutor queryExecutor)
    {
        _queryExecutor = queryExecutor;
    }

    public async Task<IEnumerable<ShipDto>> GetShipsByNameAsync(string shipName)
    {
        var getShipByNameQuery = new GetShipsByNameQuery { ShipName = shipName };
        return await _queryExecutor.ExecuteAsync<GetShipsByNameQuery, ShipDto>(getShipByNameQuery);
    }
}

The code sample above is available here.

NHibernate future queries

NHibernate supports future queries. It basically allows to batch multiple queries into a single database round trip. CoreDdd supports future queries (when the query handler derives from BaseQueryOverHandler), but one needs to pay special attention to make sure multiple queries are sent to the database over a single round trip. First, all queries except the last one have to be executed non-asynchronously, and second, the results must not be enumerated before the last query is executed. Only the last query can be executed asynchronously, in which case it will trigger all queries to be sent to the database in a single round trip, or, when the last query is not executed asynchronously, the first enumeration of any query result will trigger all queries to be sent to the database in a single round trip. Example:

var getShipByNameOneQuery = new GetShipsByNameQuery { ShipName = shipNameOne };
var shipsByNameOne = _queryExecutor.Execute<GetShipsByNameQuery, ShipDto>(getShipByNameOneQuery);

// At this point, getShipByNameOneQuery have not been sent to the DB. 
// The query would be sent to the DB only when the result (shipsByNameOne) is enumerated.

var getShipByNameTwoQuery = new GetShipsByNameQuery { ShipName = shipNameTwo };
var shipsByNameTwo = await _queryExecutor.ExecuteAsync<GetShipsByNameQuery, ShipDto>(getShipByNameTwoQuery);

// getShipByNameTwoQuery async execution was awaited, which sent both queries to the DB in the single round trip.

The code sample above is available here.

You can’t perform that action at this time.