2008-09-10 4 views
2

У меня есть много уродливого код, который выглядит следующим образом:Как бы вы реорганизовали этот код LINQ?

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>.
Идея заключается в том, что для каждого из этих бесчисленных выпадающих списков и флажков, если выпадающее меню имеет что-то выбранное, пользователь хочет сопоставить этот элемент. Если флажок установлен, пользователю нужны именно те записи, где это поле имеет нулевую или пустую строку. (Пользовательский интерфейс не позволяет одновременно выбирать оба варианта.) Все это добавляет выражение LINQ, которое запускается в конце, после того, как мы добавили все условия.

Это кажется как там должен быть какой-то способ вытащить Expression<Func<MyObject, bool>> или два, так что я могу поставить повторяющиеся части в методе и просто передать в какие изменения. Я сделал это в других местах, но этот набор кода заставил меня замолчать. (Кроме того, я бы хотел избежать «Динамического LINQ», потому что я хочу, чтобы по возможности хранить вещи безопасными по типу.) Любые идеи?

ответ

0
results = results.Where(x => 
    (string.IsNullOrEmpty(ddlFileName.SelectedItem.Text) || x.FileName.Contains(ddlFileName.SelectedValue)) 
    && (!chkFileName.Checked || string.IsNullOrEmpty(x.FileName)) 
    && ...); 
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; 

Это не короче, но я нахожу эту логику более понятной.

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, что заставляет меня жертвовать безопасностью типа.

Любые другие идеи?

0

@Kyralessa,

Вы можете создать метод расширения AddCondition для предикатов, который принимает параметр типа Control плюс лямбда-выражение и возвращает в сочетании выражения. Затем вы можете комбинировать условия, используя свободный интерфейс и повторно использовать предикаты. Чтобы увидеть пример того, как это можно реализовать увидеть мой ответ на этот вопрос:

How do I compose existing Linq Expressions

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, что вам необходимо проверить, и что вы ВГА e использовать отражение.

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 оценка не является немедленной, но происходит каждый раз, когда происходит повторение. Делегаты могут захватывать переменные и использовать их за пределами области действия.

Похоже, вы слишком близко к пользовательскому интерфейсу. Querying - это слой вниз, а linq - это не тот способ, по которому пользовательский интерфейс связывается.

Возможно, вам будет лучше сделать следующее. Развяжите логику поиска из презентации - она ​​более гибкая и многоразовая - основы 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); 

Да, это больше печатает, но вы читаете код примерно в 10 раз больше, чем вы его пишете. Ваш пользовательский интерфейс более ясный, класс параметров поиска заботится о себе и гарантирует, что взаимоисключающие параметры не сталкиваются, а код поиска абстрагируется от любого пользовательского интерфейса и даже не заботится, если вы вообще используете Linq.

0

Поскольку вы хотите повторно уменьшить исходный запрос результатов с помощью бесчисленных фильтров, вы можете использовать Aggregate() (что соответствует сокращению() в функциональных языках).

Фильтры имеют предсказуемую форму, состоящую из двух значений для каждого члена MyObject - в соответствии с информацией, которую я почерпнул из вашего сообщения. Если каждый сравниваемый элемент является строкой, которая может быть нулевой, то я рекомендую использовать метод расширения, который позволяет ссылаться на нулевые ссылки на метод расширения его предполагаемого типа.

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)); 

Я побежал это в консольного приложения с имитацией тестовых значений, и она работала, как ожидалось. Я думаю, что это, по крайней мере, демонстрирует принцип.

0

Одна вещь, которую вы можете рассмотреть, - это упрощение вашего пользовательского интерфейса, устраняя флажки и используя вместо этого «<empty>» или «<null>» в раскрывающемся списке. Это уменьшит количество элементов управления, занимающих место в вашем окне, устранит необходимость в сложной логике «включить X, только если Y не проверена», и позволит создать красивое одно-контрольное поле для каждого запроса.


Переходя к результирующей логике запроса, я хотел бы начать с созданием простого объекта для представления фильтра домена объекта:

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

Вы можете связать соответствующий экземпляр фильтра с каждым из ваши элементы управления пользовательского интерфейса, а затем получить, что, когда пользователь инициирует запрос:

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

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

Вы можете обобщают свой результат фильтрации, просто перечисляя элементы управления и выполнения соответствующий фильтр (спасибо в hurst для агрегированной идеи):

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); 
1

Не используйте LINQ, если это влияет на читаемость. Вычислите отдельные тесты в логические методы, которые могут использоваться как ваше выражение.

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; 
} 
+0

Имейте в виду, что это LINQ to SQL (что я не сказал в вопросе, но это один из тегов). Я хочу, чтобы фильтрация выполнялась на стороне базы данных, а не на стороне клиента. – 2008-09-30 01:42:39

Смежные вопросы