简体   繁体   中英

Query handlers in CQRS architectural pattern

I'm starting with CQRS and I think I'm not quite sure how (or if it's Ok to do it) to add a query that can have filtering and ordering on the result of the final query. For example:

public partial class GetParticipantListQuery : IRequest<IList<ParticipantDto>>
{
    public Expression<Func<ParticipantDto, bool>> Filter { get; set; } = null;
    public Func<IQueryable<ParticipantDto>, IQueryable<ParticipantDto>> OrderBy { get; set; } = null;
}

Then in the handler apply the filtering and ordering to the corresponding result from the DB Is this a good option? How can I achieve this kind of thing in my queries? My goal is to avoid creating one query for each filter I need, like "GetParticipantsByNameQuery", "GetParticipantsByTeamIdQuery" and so on an so forth

You can pass in a filter class which contains the necessary properties to filter the result.

For example:

public class Filter
{
    //Property Name
    public string Key { get; set; }
    //Property Type eg. string, int, bool
    public string Type { get; set; }
    //Value to filter
    public object Value { get; set; }
}

var result = from u in _context.Set<T>() select u;
switch(filter.Type)
{
    case "string":
        result = result.Where(e => EF.Property<string>(e, filter.Key).Equals((string)filter.Value));
}

This is just an example for string type, you can add your own type in the switch block to filter other types.

The way I approach my query side is as follows:-

I have a namespace that represents my query objects in order not to conflict with my domain.

The domain may be something like this:

namespace Product
{
    public class Order
    {
        public Guid Id { get; }
        public DateTime RegisteredDate { get; }

        public Order(Guid id, DateTime registeredDate)
        {
            Id = id;
            RegisteredDate = registeredDate;
        }
    }
}

The read model will look something like this (note the nested Specification class):

namespace Product.DataAccess.Query
{
    public class Order
    {
        public class Specification
        {
            public Guid? Id { get; private set; }
            public DateTime? RegisteredDateStart { get; private set; }
            public DateTime? RegisteredDateEnd { get; private set; }

            public Specification WithId(Guid id)
            {
                Id = id;

                return this;
            }

            public Specification WithRegisteredDateStart(DateTime registeredDateStart)
            {
                RegisteredDateStart = registeredDateStart;

                return this;
            }

            public Specification WithRegisteredDateEnd(DateTime registeredDateEnd)
            {
                RegisteredDateEnd = registeredDateEnd;

                return this;
            }
        }

        public Guid Id { get; set; }
        public DateTime RegisteredDate { get; set; }
    }
}

In my query layer I pass the specification over and the query layer can then construct the query using the specification values:

namespace Product.DataAccess
{
    public interface IOrderQuery
    {
        IEnumerable<Query.Order> Search(Query.Order.Specification specification);
        int Count(Query.Order.Specification specification);
    }
}

In this way you have an explicit requirement passed to your query layer and you refactor your specification class and the query implementation when you need further querying options.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM