0

I want to create generic a query handler(and command handler in the future) which could handle each query and after processing, returns query result.

IQueryHandler interfaces:

public interface IQueryHandler
{

}

public interface IQueryHandler<TResult> : IQueryHandler
{
    TResult Execute();
}

public interface IQueryHandler<TQuery, TResult> : IQueryHandler
    where TResult : class 
    where TQuery: class
{
    TResult Execute(TQuery query);
}

IQuery inteface(which is marker interface):

public interface IQuery
{

}

Simple query object:

public class BrowseTitlesQuery : IQuery
{
    public string Title { get; set; }
}

Simple query handler object:

public class BrowseTitlesQueryHandler : IQueryHandler<BrowseTitlesQuery, IEnumerable<string>>
{
    public IEnumerable<string> Execute(BrowseTitlesQuery query)
    {
        throw new System.NotImplementedException();
    }
}

QueryBus

public class QueryBus
{
    public object Resolve<T>(IQuery query) 
        where T: IQueryHandler<IQuery, Object>, IQueryHandler, new()
    {
        return new T().Execute(query);
    }
}

And of course Program.cs class(I'm using console application to test)

class Program
{
    static void Main(string[] args)
    {
        var bus = new QueryBus();
        var query = new BrowseTitlesQuery();

        bus.Resolve<BrowseTitlesQueryHandler>(query);

    }
}

In my opinion it should works but it doesn't. I have following error:

The type 'cqrs.BrowseTitlesQueryHandler' cannot be used as type parameter 'T' in the generic type or method 'QueryBus.Resolve(IQuery)'. There is no implicit reference conversion from 'cqrs.BrowseTitlesQueryHandler' to 'cqrs.IQueryHandler'. [cqrs]

Why is that?

Sefe
  • 12,811
  • 5
  • 36
  • 50
bielu000
  • 1,027
  • 1
  • 12
  • 31

2 Answers2

4

You have a problem with co- and contravariance here.

Let’s take a look at the covariance one first: BrowseTitlesQueryHandler implements IQueryHandler<BrowseTitlesQuery, IEnumerable<string>> so the return value from Execute is a IEnumerable<string>. However, in QueryBus, you are expecting a T of IQueryHandler<IQuery, object> with a return value of object.

In order to allow IQueryHandler<TQuery, TResult> to be cast down to IQueryHandler<TQuery, object>, the TResult parameter needs to be covariant. This is pretty simple here since it actually is a result, so making a it covariant is the right thing to do (note the out):

public interface IQueryHandler<out TResult> : IQueryHandler
{ … }

public interface IQueryHandler<TQuery, out TResult> : IQueryHandler
{ … }

The other problem is a bit more difficult and comes down to the fact that BrowseTitlesQueryHandler requires a BrowseTitlesQuery. But the QueryBus.Resolve will only give you a general IQuery. That’s not specific enough for BrowseTitlesQueryHandler.

Unfortunately, the only way to fix this is to make the query type a generic type argument for Resolve as well:

public object Resolve<T, TQuery>(TQuery query)
    where T : IQueryHandler<TQuery, object>, new()
    where TQuery : class, IQuery
{
    return new T().Execute(query);
}

Now, the BrowseTitlesQueryHandler gets the right query argument and can properly execute. Of course, you need to adjust your call then:

bus.Resolve<BrowseTitlesQueryHandler, BrowseTitlesQuery>(query);
poke
  • 307,619
  • 61
  • 472
  • 533
  • It works, but I wanted to awoid passing query type as second T arg. There is no another way to implement it without second generic arg, isn't it? – bielu000 Sep 22 '17 at 09:57
  • Not if you need your query handler to handle that exact type (instead of just `IQuery`). What you could do (maybe, if your application allows it) is make `QueryBus` generic with the handler as type argument and then have only the query type as an argument for `Resolve`. That way you would do `new QueryBus().Resolve(query)` (the generic query type argument will then be provided by the compiler using type inference). But that would of course restrict your query bus to a single handler type, so it may not be what you need. – poke Sep 22 '17 at 10:24
  • At this moment I creates my query handlers just using 'new' operator, but in my opinion it's not so elegant, so I want to move instance's logic to query bus. If I used generic Query Bus I wolud to have to create instance of QueryBus by new operator, so in my opinion, the first solution which you have proposed is better. – bielu000 Sep 22 '17 at 10:35
0

You have to extend the type parameters on your query bus:

public class QueryBus
{
    public object Resolve<THandler, TQuery, TResult>(TQuery query) 
        where T: IQueryHandler<TQuery, TResult>, IQueryHandler, new()
        where TResult : class 
        where TQuery: class
    {
        return new THandler().Execute(query);
    }
}

And then call it like:

bus.Resolve<BrowseTitlesQueryHandler, BrowseTitlesQuery, IEnumerable<string>>(query);
Sefe
  • 12,811
  • 5
  • 36
  • 50
  • Yours solution works too, but this method seems to be too long. Have you any idea how it could be refactored? – bielu000 Sep 22 '17 at 12:29
  • It is only one type parameter longer. You could however create the THandler instance outside and pass it to the method. In this case the type parameters could be inferred and the call would be `bus.Resolve(handler, query);` – Sefe Sep 22 '17 at 12:49