web-dev-qa-db-ja.com

適切に設計されたクエリコマンドや仕様

典型的なリポジトリパターン(特殊なクエリのメソッドのリストの増加など)によって提示される問題の優れた解決策をかなり長い間探していました。参照: http://ayende.com/blog/ 3955/repository-is-the-new-singleton )。

特に仕様パターンを使用することで、コマンドクエリを使用するというアイデアが本当に気に入っています。ただし、仕様に関する私の問題は、単純な選択(基本的にはwhere句)の基準のみに関連し、結合、グループ化、サブセット選択または投影などのクエリの他の問題を処理しないことです。基本的に、多くのクエリが正しいデータセットを取得するために通過する必要があるすべての余分なフープ。

(注:クエリオブジェクトとしても知られるコマンドパターンのように「コマンド」という用語を使用します。クエリとコマンド(更新、削除、インサート))

したがって、クエリ全体をカプセル化する代替手段を探していますが、コマンドクラスの爆発のためにスパゲッティリポジトリを交換するだけでは十分に柔軟です。

たとえば、Linqspecsを使用しましたが、選択基準に意味のある名前を割り当てることができるという点である程度の価値があるとは思いますが、それだけでは不十分です。おそらく、私は複数のアプローチを組み合わせた混合ソリューションを求めています。

この問題に対処するため、または別の問題に対処するためにこれらの要件をまだ満たすために他者が開発したソリューションを探しています。リンクされた記事では、AyendeはnHibernateコンテキストを直接使用することを提案していますが、クエリ情報を含める必要があるため、ビジネスレイヤーが大幅に複雑になっていると感じています。

待機期間が経過するとすぐに、これに関する報奨金を提供します。そのため、適切な説明を加えて、ソリューションを賞金に値するものにしてください。最適なソリューションを選択し、次点に賛成します。

注:ORMベースの何かを探しています。明示的にEFまたはnHibernateである必要はありませんが、これらは最も一般的であり、最適です。他のORMに簡単に適用できる場合は、ボーナスになります。 Linq互換もニースで​​す。

更新:ここには良い提案があまりないことに本当に驚いています。人々は完全にCQRSであるか、完全にリポジトリキャンプにいるようです。私のアプリのほとんどは、CQRSを保証するほど複雑ではありません(CQRSの支持者のほとんどは、このアプリを使用すべきではないと言っています)。

更新:ここで少し混乱しているようです。新しいデータアクセステクノロジーを探しているのではなく、ビジネスとデータの間の合理的に設計されたインターフェイスを探しています。

理想的には、私が探しているのは、Queryオブジェクト、仕様パターン、およびリポジトリの間のある種のクロスです。前述したように、仕様パターンはwhere句の側面のみを扱い、クエリの他の側面(結合、副選択など)は扱いません。リポジトリはクエリ全体を扱いますが、しばらくすると手に負えなくなります。クエリオブジェクトもクエリ全体を処理しますが、単純にリポジトリをクエリオブジェクトの爆発で置き換えたくありません。

88

それに対処する私の方法は、実際には単純化されており、ORMにとらわれません。リポジトリの私の見解はこれです:リポジトリの仕事は、コンテキストに必要なモデルをアプリに提供することであるため、アプリはレポジトリにwhatを求めますが、伝えません- how取得する。

リポジトリメソッドにCriteria(はい、DDDスタイル)を指定します。これは、リポジトリがクエリ(または必要なもの-Webサービスリクエストなど)を作成するために使用します。私見の結合とグループは方法の詳細であり、what句を作成するための基礎となるのはwhatおよび基準だけではありません。

モデル=アプリが必要とする最終的なオブジェクトまたはデータ構造。

public class MyCriteria
{
   public Guid Id {get;set;}
   public string Name {get;set;}
    //etc
 }

 public interface Repository
  {
       MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
   }

おそらく、必要に応じてORM基準(Nhibernate)を直接使用できます。リポジトリの実装は、基になるストレージまたはDAOで基準を使用する方法を知っている必要があります。

私はあなたのドメインとモデルの要件を知りませんが、最良の方法がクエリ自体を構築するアプリである場合は奇妙です。モデルが大きく変化するため、安定したものを定義できませんか?

このソリューションには明らかに追加のコードが必要ですが、残りをORMやストレージにアクセスするために使用するものに結合しません。リポジトリはファサードとして機能するためにその役割を果たし、IMOはクリーンであり、「基準の翻訳」コードは再利用可能です

4
MikeSW

これを実行し、これをサポートし、これを元に戻しました。

主な問題はこれです:どのようにそれをしても、追加された抽象化はあなたの独立性を獲得しません。定義によりリークします。本質的には、コードを可愛く見せるためだけにレイヤー全体を発明していますが、メンテナンスを減らしたり、読みやすさを改善したり、モデルにとらわれないタイプを獲得したりするわけではありません。

おもしろいのは、オリビエの回答に答えてあなた自身の質問に答えたことです。「これは基本的に、Linqから得られるすべての利点を持たずにLinqの機能を複製している」ということです。

自問してみてください:どうしてそうではないのでしょうか?

2
Stu

流fluentなインターフェイスを使用できます。基本的な考え方は、クラスのメソッドは、何らかのアクションを実行した後、まさにこのクラスの現在のインスタンスを返すということです。これにより、メソッド呼び出しを連鎖できます。

適切なクラス階層を作成することにより、アクセス可能なメソッドの論理フローを作成できます。

public class FinalQuery
{
    protected string _table;
    protected string[] _selectFields;
    protected string _where;
    protected string[] _groupBy;
    protected string _having;
    protected string[] _orderByDescending;
    protected string[] _orderBy;

    protected FinalQuery()
    {
    }

    public override string ToString()
    {
        var sb = new StringBuilder("SELECT ");
        AppendFields(sb, _selectFields);
        sb.AppendLine();

        sb.Append("FROM ");
        sb.Append("[").Append(_table).AppendLine("]");

        if (_where != null) {
            sb.Append("WHERE").AppendLine(_where);
        }

        if (_groupBy != null) {
            sb.Append("GROUP BY ");
            AppendFields(sb, _groupBy);
            sb.AppendLine();
        }

        if (_having != null) {
            sb.Append("HAVING").AppendLine(_having);
        }

        if (_orderBy != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderBy);
            sb.AppendLine();
        } else if (_orderByDescending != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderByDescending);
            sb.Append(" DESC").AppendLine();
        }

        return sb.ToString();
    }

    private static void AppendFields(StringBuilder sb, string[] fields)
    {
        foreach (string field in fields) {
            sb.Append(field).Append(", ");
        }
        sb.Length -= 2;
    }
}

public class GroupedQuery : FinalQuery
{
    protected GroupedQuery()
    {
    }

    public GroupedQuery Having(string condition)
    {
        if (_groupBy == null) {
            throw new InvalidOperationException("HAVING clause without GROUP BY clause");
        }
        if (_having == null) {
            _having = " (" + condition + ")";
        } else {
            _having += " AND (" + condition + ")";
        }
        return this;
    }

    public FinalQuery OrderBy(params string[] fields)
    {
        _orderBy = fields;
        return this;
    }

    public FinalQuery OrderByDescending(params string[] fields)
    {
        _orderByDescending = fields;
        return this;
    }
}

public class Query : GroupedQuery
{
    public Query(string table, params string[] selectFields)
    {
        _table = table;
        _selectFields = selectFields;
    }

    public Query Where(string condition)
    {
        if (_where == null) {
            _where = " (" + condition + ")";
        } else {
            _where += " AND (" + condition + ")";
        }
        return this;
    }

    public GroupedQuery GroupBy(params string[] fields)
    {
        _groupBy = fields;
        return this;
    }
}

あなたはこのように呼ぶでしょう

string query = new Query("myTable", "name", "SUM(amount) AS total")
    .Where("name LIKE 'A%'")
    .GroupBy("name")
    .Having("COUNT(*) > 2")
    .OrderBy("name")
    .ToString();

Queryの新しいインスタンスのみを作成できます。他のクラスには、コンストラクターが保護されています。階層のポイントは、メソッドを「無効にする」ことです。たとえば、GroupByメソッドは、GroupedQueryの基本クラスであり、Queryメソッドを持たないWhereを返します(whereメソッドはQuery)。したがって、Whereの後にGroupByを呼び出すことはできません。

しかし、完璧ではありません。このクラス階層を使用すると、メンバーを連続して非表示にできますが、新しいメンバーは表示できません。したがって、Havingは、GroupByの前に呼び出されると例外をスローします。

Whereを複数回呼び出すことができることに注意してください。これにより、ANDを持つ新しい条件が既存の条件に追加されます。これにより、単一の条件からプログラムでフィルターを簡単に構築できます。 Havingでも同じことが可能です。

フィールドリストを受け入れるメソッドには、パラメータparams string[] fieldsがあります。単一のフィールド名または文字列配列を渡すことができます。


流interfacesなインターフェイスは非常に柔軟であり、パラメーターのさまざまな組み合わせでメソッドのオーバーロードを作成する必要はありません。私の例は文字列で動作しますが、アプローチは他のタイプに拡張できます。特別な場合に事前定義されたメソッドや、カスタムタイプを受け入れるメソッドを宣言することもできます。 ExecuteReaderExceuteScalar<T>などのメソッドを追加することもできます。これにより、次のようなクエリを定義できます。

var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
    .Where(new CurrentMonthCondition())
    .Where(new DivisionCondition{ DivisionType = DivisionType.Production})
    .OrderBy(new StandardMonthlyReportSorting())
    .ExecuteReader();

この方法で構築されたSQLコマンドでもコマンドパラメータを使用できるため、SQLインジェクションの問題を回避すると同時に、データベースサーバーがコマンドをキャッシュできます。これはO/R-mapperの代替ではありませんが、そうでなければ単純な文字列連結を使用してコマンドを作成する状況で役立ちます。