4

問題:リポジトリパターンを広範囲に使用して、複数のアプリケーションと機能のサブセクションにわたるデータストア(LINQを使用するMS SQL)での読み取り/書き込み操作を容易にします。私たちは、すべてが互いに似たようなことをする一連の方法を持っています。

たとえば、ProcessAndSortXXXXXクラスのメソッドがあります。

private static IEnumerable<ClassErrorEntry> ProcessAndSortClassErrorLog(IQueryable<ClassErrorDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassErrorEntry(l.Id)
            {
                ClassId = l.ClassId,
                Code = l.Code,
                Message = l.Message,
                Severity = l.Severity,
                Target = l.Target
            }
        );
}

...と...

private static IEnumerable<ClassTimerLogEntry> ProcessAndSortClassTimerLog(IQueryable<ClassTimerDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassTimerLogEntry(l.Id)
            {
                ClassName = l.ClassName,
                MethodName = l.MethodName,
                StartTime = l.StartTime,
                EndTime = l.EndTime,
                ParentId = l.ParentId,
                ExecutionOrder = l.ExecutionOrder
            }
        );
}

コードからわかるように、署名を確認してから、ClassErrorEntryとClassTimerLogEntryのインスタンスを構築しているreturnステートメントに到達するまで、これらはすべて非常に似ています。

すべてのリポジトリが継承する基本クラスに追加するユーティリティメソッドを作成したいと思います。

オブジェクトをインスタンス化し、それらを返すIEnumerableにパックするために使用できる引数を渡せるようにしたいと思います。

私はScottGuによるこの投稿を見つけました、そしてそれは私が必要とするもののほとんどを私に与えます。これは次のようになります(ドキュメントのサンプルから):

var query =
    db.Customers.
    Where("City = @0 and Orders.Count >= @1", "London", 10).
    OrderBy("CompanyName").
    Select("new(CompanyName as Name, Phone)");

しかし、ここで私は行き詰まります。動的クエリを構築できるように、LINQテーブルとDataContextを一般的な方法で渡す方法についてのポインターまたは提案が必要です。

疑似コードで署名をモックアップすると、次のようになります。

protected internal IEnumerable ProcessAndSort(IQueryable source, string selectClause, string whereClause, string orderByClause);

これを理解すると、完成した署名が異なって見える可能性があることに気付きました。

ありがとうございました!

アップデート!

これで、匿名型を生成するように機能するコードがありますが、具象型に変換すると失敗します。

public static IEnumerable<TResult> ProcessAndSort<T, TResult>(IQueryable<T> queryable, 
    string selector, Expression<Func<T, bool>> predicate, string sortOrder)
{
    var dynamic = queryable.Where(predicate).AsQueryable();
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    var result= dynamic.Select(selector).Cast<TResult>();

    return result;
}

このメソッドを呼び出すコードは次のとおりです。

[TestMethod]
public void TestAnonymousClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0, 
        "new ( ClassId as ClassId, " +
        "Code as Code, " +
        "Message as Message, " +
        "Severity as Severity, " +
        "Target as Target )", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

最後の行TestContext.WriteLine(result.ToList().Count.ToString());は例外をスローしますSystem.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1' and 'Utilities.Logging.ClassErrorEntry'.

ただし、このコードのチャンクは失敗します。

[TestMethod]
public void TestNamedClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0,
        "new ClassErrorEntry(Id) { ClassId = ClassId, " +
        "Code = Code, " +
        "Message = Message, " +
        "Severity = Severity, " +
        "Target = Target }", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

これは解析エラーで失敗します。Test method eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass threw exception: System.Linq.Dynamic.ParseException: '(' expected, found 'ClassErrorEntry' ('Identifier') at char 19 in 'new ClassErrorEntry(Id) { ChassisAuthId = ChassisAuthId, Code = Code, Message = Message, Severity = Severity, Target = Target }'

19番目の文字位置がa(であり、Validateメソッドに渡されたタイプが4の位置、つまり最初のを示しているため、文字位置が疑わしいかどうかはわかりません'C'

4

2 に答える 2

1

コードを再利用するためだけに、弱い型のクエリを作成ないように完全にアドバイスします。
コードの再利用は保守性を高めるためのものですが、間違った方法で使用すると、弱い型付けによってコードが削除される可能性があります。クエリをプレーンテキストで記述することにより、クラスのリファクタリングと変更が非常に困難になり、多くのあいまいな依存関係が導入されます。

を組み合わせることができるLinqKitをご覧になることをお勧めしますExpression。たとえば、Pagingクエリをページごとに分割し、プロジェクト全体でさまざまなタイプで使用するメソッドを作成しました。

var query = CompiledQuery.Compile(
    BuildFolderExpr( folder, false )
        .Select( msg => selector.Invoke( msg, userId ) ) // re-use selector expression
        .OrderBy( mv => mv.DateCreated, SortDirection.Descending )
        .Paging() // re-use paging expression
        .Expand() // LinqKit method that "injects" referenced expressions
    )

public static Expression<Func<T1, T2, PagingParam, IQueryable<TItem>>> Paging<T1, T2, TItem>(
    this Expression<Func<T1, T2, IQueryable<TItem>>> expr )
{
    return ( T1 v1, T2 v2, PagingParam p ) => expr.Invoke( v1, v2 ).Skip( p.From ).Take( p.Count );
}

私の例でBuildMessageExprは、比較的単純なselect式(すでにと別のパラメーターに依存していfolderます)を返します。さまざまなメソッドが、フィルタリング、順序付け、カウントの取得、さらにパラメーターとして渡されるセレクター式などを使用してこの式を再利用します。クエリが作成されると、パラメータが類似している場合に将来使用するためにキャッシュされます。

于 2011-06-10T22:29:07.217 に答える
0

それはあなたの質問に対する直接の答えではありません。

あなたが言ったように、あなたは似ているが異なるタイプを返す非常に多くのコードを持っています。先に進んでこのアプローチの一般的な実装を探す場合、結果にいくつかのハックが含まれる可能性があります。それでも、不快なSQLを渡すか、オブジェクトのタイプを確認するか、リフレクションカンフーを実行する可能性があります。あなたはまだこのパスを選択するかもしれません、そして実際に誰かが汚いハックのように見えないであろう賢明な考えを持つことができます。

もう1つのオプションは、一般的なリポジトリパターンと依存性注入(google link)で適切なORMを使用することです。データアクセス層は、はるかにシンプルで保守が容易になります。

于 2011-06-04T16:00:32.417 に答える