2017-09-22 9 views
0

各クエリーを処理し、処理後にクエリー結果を返すジェネリッククエリーハンドラ(および今後のコマンドハンドラ)を作成したいと思います。汎用クエリーハンドラcqrs

IQueryHandlerインターフェース:(マーカーインターフェイスである)

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を:

public interface IQuery 
{ 

} 

単純なクエリオブジェクト:

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

単純なクエリハンドラオブジェクト:

私の意見では3210

QueryBus

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

そしてもちろんのProgram.csのクラス(私はテストへのコンソールアプリケーションを使用しています)

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

     bus.Resolve<BrowseTitlesQueryHandler>(query); 

    } 
} 

それは作品のはずですが、それはしていません。 次のエラーがあります:

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]

なぜですか?

+0

理由あなたは "IQueryHandler"という名前の3つのインターフェースを持っていますか? –

+0

テストのためだけに、私は最終的に上記のリストの3番目になる単一のインターフェイスを持ちたいと思っています。 – bielu000

+0

あなたがリファクタリングして3番目だけを残したら? –

答えて

4

co- and contravarianceに問題があります。

はのは、共分散最初の1を見てみましょう:BrowseTitlesQueryHandlerExecuteからの戻り値がIEnumerable<string>あるのでIQueryHandler<BrowseTitlesQuery, IEnumerable<string>>を実装しています。ただし、QueryBusには、がIQueryHandler<IQuery, object>、戻り値がobjectであると想定されています。

IQueryHandler<TQuery, TResult>IQueryHandler<TQuery, object>にダウンキャストすることができるようにするために、TResultパラメータが共変する必要があります。

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

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

他の問題は少し難しくなり、下に来る:それは実際にがそう(outに注意してください)、それが共変を行うには正しいことで作り、結果であるので、これはここで非常に簡単ですBrowseTitlesQueryHandlerにはBrowseTitlesQueryが必要です。しかし、QueryBus.Resolveはあなたに一般的なIQueryを与えるだけです。それはBrowseTitlesQueryHandlerのために十分ではありません。

は残念ながら、この問題を解決する唯一の方法は、クエリは、同様Resolveのためのジェネリック型引数を入力することです:

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

BrowseTitlesQueryHandlerは、右のクエリ引数を取得し、適切に実行することができます。もちろん、あなたが、あなたの呼び出しを調整する必要があります。

bus.Resolve<BrowseTitlesQueryHandler, BrowseTitlesQuery>(query); 
+0

これはうまくいきましたが、私は2番目のT argとしてクエリ型を渡すことはしたくありませんでした。 2番目のジェネリックargなしで実装する別の方法はありませんか? – bielu000

+0

(IQueryだけではなく)正確な型を扱うためにクエリハンドラが必要な場合はありません。あなたのアプリケーションが許せば(多分、アプリケーションが許せば)、ハンドラを型引数として 'QueryBus'をジェネリックにして、' Resolve'の引数としてクエリ型だけを持つことができます。そうすれば '新しいQueryBus ()。Resolve(query)'(一般的なクエリ型引数は型推論を使用してコンパイラによって提供されます)を行います。しかし、もちろんそれはあなたのクエリバスを単一のハンドラタイプに制限するので、あなたが必要とするものではないかもしれません。 – poke

+0

現時点では、「new」演算子を使用してクエリハンドラを作成しますが、私の意見ではそれほどエレガントではないので、インスタンスのロジックをクエリバスに移動したいと考えています。ジェネラルクエリバスを使用して、新しい演算子でQueryBusのインスタンスを作成する必要がある場合は、私の意見では、提案した最初のソリューションが優れています。 – bielu000

0

あなたのクエリ・バス上の型パラメータを拡張する必要があります。その後、

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); 
    } 
} 

そして呼び出すことが好き:

bus.Resolve<BrowseTitlesQueryHandler, BrowseTitlesQuery, IEnumerable<string>>(query); 
+0

あなたのソリューションも機能しますが、この方法は長すぎるようです。それがどのようにリファクタリングできるか考えてみませんか? – bielu000

+0

これは1つのタイプのパラメータだけです。ただし、THandlerインスタンスを外部に作成してメソッドに渡すことはできます。この場合、型パラメータを推論することができ、呼び出しは 'bus.Resolve(handler、query);' – Sefe