問題:複数のアプリケーションおよび機能のサブセクションにわたって、データストア(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
}
);
}
コードからわかるように、シグネチャを確認してから、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'.
スローします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'
ことを示しているため、文字の位置が疑わしいかどうかはわかりません。
私は完全にちょうどコードの再利用のために弱い型指定のクエリを作るに対して 、あなたを助言します。
コードの再利用は保守性を向上させるためのものですが、間違った方法で使用すると、弱いタイピングによってコードが破壊される可能性があります。クエリをプレーンテキストで記述することにより、クラスを効果的にリファクタリングおよび変更しにくくし、多くのあいまいな依存関係を導入することになります。
Expression
を組み合わせることができるLinqKitをご覧になることをお勧めします。たとえば、クエリをページごとに分割し、さまざまなタイプのプロジェクト全体で使用する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
は比較的単純な選択式(既にfolder
と別のパラメーターに依存しています)を返し、さまざまなメソッドがフィルタリング、順序付け、カウントの適用、パラメーターとして渡されるセレクター式での選択などを使用して、この式を再利用します。 。クエリが作成されると、パラメータが類似している場合、将来の使用のためにキャッシュされます。
それはあなたの質問に対する直接の答えではありません。
あなたが言ったように、あなたは似て見えますが異なるタイプを返すかなりたくさんのコードを持っています。先に進んでこのアプローチの一般的な実装を探す場合、結果にはいくつかのハックがある可能性があります。それでも、不快なSQLを渡すか、オブジェクトのタイプをチェックするか、リフレクションkung-fuを実行できます。あなたはまだこのパスを選択するかもしれません、そして実際に誰かは汚いハックのように見えないだろう賢明な考えを持つことができます。
もう1つのオプションは、一般的なリポジトリパターンと依存性注入( google link )で適切なORMを使用することです。データアクセスレイヤーは、はるかにシンプルで保守しやすくなります。