5

タイプセーフな方法でLINQを使用してこれを実現する方法についてのヘルプを求めています。

多くの列がある「パフォーマンス」テーブルで検索を実行する必要があります。検索用に指定された基準に基づいて、列を選択し、指定された値でそれらの列を検索する必要があります。

private static IQueryable<Investment> PerformanceSearch(IQueryable<Investment> investments, **??? searchColumn**, double minValue, double maxValue)
{
  var entity = ExtendedEntities.Current;

  investments = from inv in entity.Investments 
                join performance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
                where **performance.searchColumn** >= minValue && **performance.searchColumn** = maxValue
  return investments;
}

今私はあなたの助けを求めています:

  1. 列「searchColumn」をタイプセーフな方法でこのメソッドに渡す方法は?エンティティフレームワークから列名を維持するための何らかの方法に対応するために、辞書オブジェクトを作成することを考えていました。しかし、これを達成する方法がわかりません。

  2. 渡されたcolumnNameを使用し、where句を適用してLINQクエリを実行する方法。

以下のIfElseまたはSwitchの場合は、可能な検索列のリストです。

/*
 * Search Columns can be:
 *      "Return1Month", "Return2Months", "Return3Months", ... almost 10 more and
 *      "Risk1Month", "Risk2Months", "Risk3Months", ... almost 10 more and
 *      "TrackingError1Month", "TrackingError2Months", "TrackingError3Months", ... almost 10 more and
 *      2 more similar set of columns ... 
 */

Stackoverflow、Microsoft、その他のブログに時間を費やし、Dynamic LINQの使用を検討しましたが、タイプセーフではありません。式を使えば実現できるようですが、うまくいきませんでした。

アドバイスをいただければ幸いです。

編集 -

言及すべきもう1つの項目-すべての検索列が「パフォーマンス」テーブルに表示されます。

4

4 に答える 4

4

受け継がれるように、LINQ式は、強く型付けされた方法でLINQクエリを動的に構築するための最良の方法です。動的LINQライブラリを破棄するのは絶対に正しいです!LINQ式は最初は把握するのが難しいですが、最終的な見返りは努力する価値があることをお約束します。

これは、LINQ式を使用して目的を達成するための例です。文字列の列名、switchステートメント、ヘルパークラス、または列挙型が含まれていないことに気付くでしょう。System.Linq.Expressionsこれを機能させるには、名前空間をインポートする必要があります。

編集:この例には、別の要素から要素を選択しながら、1つの結合されたテーブルの列によるフィルタリングが含まれるようになりました。investmentsまた、実際にパラメーターを渡す必要がないため、メソッドからパラメーターを削除しました。メソッド内で直接EFテーブルにアクセスしているだけです(_performanceおよびの代わりに使用し_investmentsます)。

    public static IQueryable<Investment> PerformanceSearch(Expression<Func<Performance, double>> searchColumn, double minValue, double maxValue) {

        // LINQ Expression that represents the column passed in searchColumn
        // x.Return1Month
        MemberExpression columnExpression = searchColumn.Body as MemberExpression;

        // LINQ Expression to represent the parameter of the lambda you pass in
        // x
        ParameterExpression parameterExpression = (ParameterExpression)columnExpression.Expression;

        // Expressions to represent min and max values
        Expression minValueExpression = Expression.Constant(minValue);
        Expression maxValueExpression = Expression.Constant(maxValue);

        // Expressions to represent the boolean operators
        // x.Return1Month >= minValue
        Expression minComparisonExpression = Expression.GreaterThanOrEqual(columnExpression, minValueExpression);

        // x.Return1Month <= maxValue
        Expression maxComparisonExpression = Expression.LessThanOrEqual(columnExpression, maxValueExpression);

        // (x.Return1Month >= minValue) && (x.Return1Month <= maxValue)
        Expression filterExpression = Expression.AndAlso(minComparisonExpression, maxComparisonExpression);

        // x => (x.Return1Month >= minValue) && (x.Return1Month <= maxValue)
        Expression<Func<Performance, bool>> filterLambdaExpression = Expression.Lambda<Func<Performance, bool>>(filterExpression, parameterExpression);

        // use the completed expression to filter your collection
        // This requires that your collection is an IQueryable.
        // I believe that EF tables are already IQueryable, so you can probably
        // drop the .AsQueryable calls and it will still work fine.
        var query = (from i in _investments
                     join p in _performance.AsQueryable().Where(filterLambdaExpression)
                       on i.InvestmentId equals p.InvestmentId
                     select i);

        return query.AsQueryable();

    } 

PerformanceSearchこの単純なコンソールアプリを例として使用して、このように呼び出します。

    private static IList<Investment> _investments;
    private static IList<Performance> _performance;

    static void Main(string[] args) {

        // Simulate your two Entity Framework tables
        BuildMockDataset();

        // Return1Month is on Performance, but I return IQueryable<Investment>;
        var results = PerformanceSearch(x => x.Return1Month, 300, 1000);

    }

この例は、最小値と最大値をとして指定して、doubleからプロパティを渡すことができるように十分に一般的です。PerformancesearchColumndouble

于 2012-08-15T03:06:15.673 に答える
2

Func <TIn、TOut>パラメーター(この場合は式は不要)を使用するだけでこれを実行できるはずです。列のタイプが何であれ、関数をジェネリックにしてタイプセーフにします。これが私が考えていることです...

private static IQueryable<Investment> PerformanceSearch<TMember>(
                              IQueryable<Investment> investments, 
                              Func<Performance,TMember> SearchColumn, 
                              TMember minValue, 
                              TMember maxValue)
{
    var entity = ExtendedEntities.Current;

    investments = from inv in entity.Investments 
        join perfromance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
        where SearchColumn(perfromance) >= minValue && SearchColumn(perfromance) <= maxValue
    return investments;
}

次に、次のように呼び出します。

var results = PerformanceSearch<double>(investments, p => p.Return1Month, 10.0, 20.0);
于 2012-08-15T02:51:37.353 に答える
1
private static IQueryable<Investment> PerformanceSearch(IQueryable<Investment> investments, string searchColumn, double minValue, double maxValue)
{
  var entity = ExtendedEntities.Current;

  investments = from inv in entity.Investments 
                join perfromance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
                where
                (
                    (searchColumn = "Return1Month" && perfromance.Return1Month >= minValue && perfromance.Return1Month <= maxValue) ||
                    (searchColumn = "Return2Months" && perfromance.Return2Months >= minValue && perfromance.Return2Months <= maxValue) ||
                    (searchColumn = "Return3Months" && perfromance.Return3Months >= minValue && perfromance.Return3Months <= maxValue) ||
                    (searchColumn = "Risk1Month" && perfromance.Risk1Month >= minValue && perfromance.Risk1Month <= maxValue)
                    // continue like this for as many columns, unless you want to use reflection
                )
  return investments;
}

もう1つのオプションは、動的レポートシステムで使用した、コードの生成とコンパイルです。

http://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx

于 2012-08-15T02:18:09.777 に答える
1

次のような強い型のwhere句を含む辞書を作成できます。

var wheres = new Dictionary<string, Expression<Func<Performance, bool>>>()
{
    { "Return1Month", p => p.Return1Month >= minValue && p.Return1Month <= minValue },
    { "Return2Months", p => p.Return2Months >= minValue && p.Return2Months <= minValue },
    { "Return3Months", p => p.Return3Months >= minValue && p.Return3Months <= minValue },
    { "Risk1Month", p => p.Risk1Month >= minValue && p.Risk1Month <= minValue },
    { "TrackingError1Month", p => p.TrackingError1Month >= minValue && p.TrackingError1Month <= minValue },
    /* etc */
};

完全なメソッドは次のようになります。

private static IQueryable<Investment> PerformanceSearch(IQueryable<Investment> investments, string searchColumn, double minValue, double maxValue)
{
    var entity = ExtendedEntities.Current;

    var wheres = new Dictionary<string, Expression<Func<Performance, bool>>>()
    {
        { "Return1Month", p => p.Return1Month >= minValue && p.Return1Month <= minValue },
        { "Return2Months", p => p.Return2Months >= minValue && p.Return2Months <= minValue },
        { "Return3Months", p => p.Return3Months >= minValue && p.Return3Months <= minValue },
        { "Risk1Month", p => p.Risk1Month >= minValue && p.Risk1Month <= minValue },
        { "TrackingError1Month", p => p.TrackingError1Month >= minValue && p.TrackingError1Month <= minValue },
        /* etc */
    };

    var investments = (
        from inv in entity.Investments 
        join perfromance in entity.Performances.Where(wheres[searchColumn]) on inv.InvestmentID equals perfromance.InvestmentID
        select inv;

    return investments;
}

各呼び出しの辞書の作成は、実際のデータベース呼び出しに比べて非常に高速なので、あまり心配する必要はありません。心配することにした場合は、辞書を静的なプライベートフィールドにします。

于 2012-08-15T02:27:59.423 に答える