2

次のような醜いコードがたくさんあります。

if (!string.IsNullOrEmpty(ddlFileName.SelectedItem.Text))
    results = results.Where(x => x.FileName.Contains(ddlFileName.SelectedValue));
if (chkFileName.Checked)
    results = results.Where(x => x.FileName == null);

if (!string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text))
    results = results.Where(x => x.IpAddress.Contains(ddlIPAddress.SelectedValue));
if (chkIPAddress.Checked)
    results = results.Where(x => x.IpAddress == null);

...etc.

resultsですIQueryable<MyObject>
これらの無数のドロップダウンとチェックボックスのそれぞれについて、ドロップダウンで何かが選択されている場合、ユーザーはその項目を一致させたいという考えです。チェックボックスがチェックされている場合、ユーザーはそのフィールドが null または空の文字列であるレコードを特に必要としています。(UI では、両方を同時に選択することはできません。) これはすべて、すべての条件を追加した後、最後に実行される LINQ 式に追加されます。

繰り返し部分をメソッドに入れ、変更内容を渡すことができるように、1 つまたは 2 つの方法を引き出す必要があるようです。Expression<Func<MyObject, bool>>私は他の場所でこれを行いましたが、このコードのセットは私を悩ませています。(また、可能であればタイプセーフを維持したいので、「動的LINQ」は避けたいと思います。)何かアイデアはありますか?

4

10 に答える 10

5

私はそれを単一のLinqステートメントに変換します:

var results =
    //get your inital results
    from x in GetInitialResults()
    //either we don't need to check, or the check passes
    where string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
       x.FileName.Contains(ddlFileName.SelectedValue)
    where !chkFileName.Checked ||
       string.IsNullOrEmpty(x.FileName)
    where string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text) ||
       x.FileName.Contains(ddlIPAddress.SelectedValue)
    where !chkIPAddress.Checked ||
       string.IsNullOrEmpty(x. IpAddress)
    select x;

短くはありませんが、この論理はより明確だと思います。

于 2008-09-10T19:13:48.627 に答える
5

その場合:

//list of predicate functions to check
var conditions = new List<Predicate<MyClass>> 
{
    x => string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
         x.FileName.Contains(ddlFileName.SelectedValue),
    x => !chkFileName.Checked ||
         string.IsNullOrEmpty(x.FileName),
    x => string.IsNullOrEmpty(ddlIPAddress.SelectedItem.Text) ||
         x.IpAddress.Contains(ddlIPAddress.SelectedValue),
    x => !chkIPAddress.Checked ||
         string.IsNullOrEmpty(x.IpAddress)
}

//now get results
var results =
    from x in GetInitialResults()
    //all the condition functions need checking against x
    where conditions.All( cond => cond(x) )
    select x;

述語リストを明示的に宣言しましたが、これらは次のように生成できます。

ListBoxControl lbc;
CheckBoxControl cbc;
foreach( Control c in this.Controls)
    if( (lbc = c as ListBoxControl ) != null )
         conditions.Add( ... );
    else if ( (cbc = c as CheckBoxControl ) != null )
         conditions.Add( ... );

チェックする必要がある MyClass のプロパティをチェックする方法が必要であり、そのためにはリフレクションを使用する必要があります。

于 2008-09-11T07:35:45.240 に答える
1

読みやすさに影響する場合は、LINQを使用しないでください。where式として使用できるブールメソッドに個々のテストを因数分解します。

IQueryable<MyObject> results = ...;

results = results
    .Where(TestFileNameText)
    .Where(TestFileNameChecked)
    .Where(TestIPAddressText)
    .Where(TestIPAddressChecked);

したがって、個々のテストはクラスの単純なメソッドです。それらは個別にユニットテスト可能です。

bool TestFileNameText(MyObject x)
{
    return string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
           x.FileName.Contains(ddlFileName.SelectedValue);
}

bool TestIPAddressChecked(MyObject x)
{
    return !chkIPAddress.Checked ||
        x.IpAddress == null;
}
于 2008-09-27T01:18:27.320 に答える
1

LINQKitを見たことがありますか? AsExpandable はあなたが求めているもののように聞こえます (ただし、詳細については、TomasP.NET のLINQ クエリで関数を呼び出すという投稿を読みたいと思うかもしれません)。

于 2008-09-11T08:20:44.400 に答える
0

<empty>検討する可能性のあることの1つは、チェックボックスを削除し、代わりにドロップダウンリストの「 」または「<null>」項目を使用してUIを簡素化することです。これにより、ウィンドウのスペースを占めるコントロールの数が減り、複雑な「Yがチェックされていない場合にのみXを有効にする」ロジックが不要になり、クエリフィールドごとに1つのコントロールが有効になります。


結果クエリロジックに移り、ドメインオブジェクトのフィルターを表す単純なオブジェクトを作成することから始めます。

interface IDomainObjectFilter {
  bool ShouldInclude( DomainObject o, string target );
}

フィルタの適切なインスタンスを各UIコントロールに関連付け、ユーザーがクエリを開始したときにそれを取得できます。

sealed class FileNameFilter : IDomainObjectFilter {
  public bool ShouldInclude( DomainObject o, string target ) {
    return string.IsNullOrEmpty( target )
        || o.FileName.Contains( target );
  }
}

...
ddlFileName.Tag = new FileNameFilter( );

次に、コントロールを列挙し、関連するフィルターを実行するだけで、結果のフィルター処理を一般化できます(Aggregateのアイデアに感謝します

var finalResults = ddlControls.Aggregate( initialResults, ( c, r ) => {
  var filter = c.Tag as IDomainObjectFilter;
  var target = c.SelectedValue;
  return r.Where( o => filter.ShouldInclude( o, target ) );
} );


クエリは非常に規則的であるため、メンバーセレクターを使用する単一のフィルタークラスを使用することで、実装をさらに簡素化できる場合があります。

sealed class DomainObjectFilter {
  private readonly Func<DomainObject,string> memberSelector_;
  public DomainObjectFilter( Func<DomainObject,string> memberSelector ) {
    this.memberSelector_ = memberSelector;
  }

  public bool ShouldInclude( DomainObject o, string target ) {
    string member = this.memberSelector_( o );
    return string.IsNullOrEmpty( target )
        || member.Contains( target );
  }
}

...
ddlFileName.Tag = new DomainObjectFilter( o => o.FileName );
于 2008-09-27T00:37:55.420 に答える
0
results = results.Where(x => 
    (string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) || x.FileName.Contains(ddlFileName.SelectedValue))
    && (!chkFileName.Checked || string.IsNullOrEmpty(x.FileName))
    && ...);
于 2008-09-10T18:41:32.450 に答える
0

これまでのところ、これらの答えはどちらも私が探しているものではありません。私が目指していることの例を示すために(これも完全な答えとは見なしません)、上記のコードを使用して、いくつかの拡張メソッドを作成しました。

static public IQueryable<Activity> AddCondition(
    this IQueryable<Activity> results,
    DropDownList ddl, 
    Expression<Func<Activity, bool>> containsCondition)
{
    if (!string.IsNullOrEmpty(ddl.SelectedItem.Text))
        results = results.Where(containsCondition);
    return results;
}
static public IQueryable<Activity> AddCondition(
    this IQueryable<Activity> results,
    CheckBox chk, 
    Expression<Func<Activity, bool>> emptyCondition)
{
    if (chk.Checked)
        results = results.Where(emptyCondition);
    return results;
}

これにより、上記のコードを次のようにリファクタリングすることができました。

results = results.AddCondition(ddlFileName, x => x.FileName.Contains(ddlFileName.SelectedValue));
results = results.AddCondition(chkFileName, x => x.FileName == null || x.FileName.Equals(string.Empty));

results = results.AddCondition(ddlIPAddress, x => x.IpAddress.Contains(ddlIPAddress.SelectedValue));
results = results.AddCondition(chkIPAddress, x => x.IpAddress == null || x.IpAddress.Equals(string.Empty));

これはそれほど醜いものではありませんが、それでも私が望むよりも長いです。各セットのラムダ式のペアは明らかに非常に似ていますが、それらをさらに凝縮する方法を理解することはできません...少なくとも動的LINQに頼らなければ、型の安全性が犠牲になります。

他のアイデアはありますか?

于 2008-09-10T22:37:06.580 に答える
0

私は次の形式の解決策に注意するでしょう:

// from Keith
from x in GetInitialResults()
    //either we don't need to check, or the check passes
    where string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) ||
       x.FileName.Contains(ddlFileName.SelectedValue)

私の推論は変数キャプチャです。一度だけ実行すると、おそらく違いに気付かないでしょう。ただし、linqでは、評価は即時ではなく、繰り返されるたびに行われます。デリゲートは変数をキャプチャして、意図した範囲外で使用できます。

UIに近すぎてクエリを実行しているように感じます。クエリはレイヤーダウンであり、linqはUIがダウンコミュニケーションする方法ではありません。

次のことを行う方がよい場合があります。検索ロジックをプレゼンテーションから切り離します-より柔軟で再利用可能です-OOの基本。

// my search parameters encapsulate all valid ways of searching.
public class MySearchParameter
{
    public string FileName { get; private set; }
    public bool FindNullFileNames { get; private set; }
    public void ConditionallySearchFileName(bool getNullFileNames, string fileName)
    {
        FindNullFileNames = getNullFileNames;
        FileName = null;

        // enforce either/or and disallow empty string
        if(!getNullFileNames && !string.IsNullOrEmpty(fileName) )
        {
            FileName = fileName;
        }
    }
    // ...
}

// search method in a business logic layer.
public IQueryable<MyClass> Search(MySearchParameter searchParameter)
{
    IQueryable<MyClass> result = ...; // something to get the initial list.

    // search on Filename.
    if (searchParameter.FindNullFileNames)
    {
        result = result.Where(o => o.FileName == null);
    }
    else if( searchParameter.FileName != null )
    {   // intermixing a different style, just to show an alternative.
        result = from o in result
                 where o.FileName.Contains(searchParameter.FileName)
                 select o;
    }
    // search on other stuff...

    return result;
}

// code in the UI ... 
MySearchParameter searchParameter = new MySearchParameter();
searchParameter.ConditionallySearchFileName(chkFileNames.Checked, drpFileNames.SelectedItem.Text);
searchParameter.ConditionallySearchIPAddress(chkIPAddress.Checked, drpIPAddress.SelectedItem.Text);

IQueryable<MyClass> result = Search(searchParameter);

// inform control to display results.
searchResults.Display( result );

Yes it's more typing, but you read code around 10x more than you write it. Your UI is clearer, the search parameters class takes care of itself and ensures mutually exclusive options don't collide, and the search code is abstracted away from any UI and doesn't even care if you use Linq at all.

于 2008-09-25T00:27:47.217 に答える
0

@キラレッサ、

Control 型のパラメーターとラムダ式を受け取り、組み合わせた式を返す述語用の拡張メソッド AddCondition を作成できます。次に、流暢なインターフェースを使用して条件を組み合わせ、述語を再利用できます。実装方法の例を見るには、この質問に対する私の回答を参照してください。

既存の Linq 式を作成する方法

于 2008-09-10T23:28:44.427 に答える
0

無数のフィルターを使用して元の結果クエリを繰り返し削減したいので、Aggregate()を使用できます(関数型言語の reduce() に相当します)。

投稿から収集した情報によると、フィルターは予測可能な形式であり、MyObject のすべてのメンバーの 2 つの値で構成されます。比較するすべてのメンバーが null の可能性がある文字列である場合は、拡張メソッドを使用することをお勧めします。これにより、null 参照を目的の型の拡張メソッドに関連付けることができます。

public static class MyObjectExtensions
{
    public static bool IsMatchFor(this string property, string ddlText, bool chkValue)
    {
        if(ddlText!=null && ddlText!="")
        {
            return property!=null && property.Contains(ddlText);
        }
        else if(chkValue==true)
        {
            return property==null || property=="";
        }
        // no filtering selected
        return true;
    }
}

多くの反復処理を可能にするために、プロパティ フィルターをコレクションに配置する必要があります。これらは、IQueryable との互換性のために式として表されます。

var filters = new List<Expression<Func<MyObject,bool>>>
{
    x=>x.Filename.IsMatchFor(ddlFileName.SelectedItem.Text,chkFileName.Checked),
    x=>x.IPAddress.IsMatchFor(ddlIPAddress.SelectedItem.Text,chkIPAddress.Checked),
    x=>x.Other.IsMatchFor(ddlOther.SelectedItem.Text,chkOther.Checked),
    // ... innumerable associations
};

ここで、無数のフィルターを最初の結果クエリに集約します。

var filteredResults = filters.Aggregate(results, (r,f) => r.Where(f));

シミュレートされたテスト値を使用してコンソール アプリでこれを実行したところ、期待どおりに機能しました。これは少なくとも原理を示していると思います。

于 2008-09-25T08:40:52.070 に答える