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. 

resultsIQueryable<MyObject>이다.
아이디어는이 무수히 많은 드롭 다운과 체크 박스 각각에 대해, 드롭 다운에 어떤 것이 선택되어 있다면, 사용자는 그 아이템과 일치하기를 원합니다. 이 확인란을 선택하면 사용자는 해당 필드가 null이거나 빈 문자열 인 레코드를 구체적으로 원합니다. (UI는 동시에 두 가지를 모두 선택할 수 없습니다.)이 모든 것이 LINQ Expression에 추가됩니다. LINQ Expression은 모든 조건을 추가 한 다음 끝에 실행됩니다.

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을 만들 수 있습니다. 그런 다음 유창한 인터페이스를 사용하여 조건을 결합하고 술어를 재사용 할 수 있습니다. 이 경우

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; 

난 그냥 명시 적 술어 목록을 선언했습니다,하지만 이러한 수 생성, 무엇인가 : 당신은 당신이 확인하는 데 필요한 MyClass에의 속성을 확인하고, 그것을 위해 당신이 근래 줄 수있는 방법이 필요 것

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

반사를 사용합니다.

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

예는 더 타이핑,하지만 당신은 당신이 그것을 쓰기보다 약 10 배 더 많은 코드를 읽어 보시기 바랍니다. UI가 더 명확 해지고 검색 매개 변수 클래스가 자체적으로 처리하며 상호 배타적 인 옵션이 충돌하지 않도록 보장하며 검색 코드는 모든 UI에서 추상화되어 Linq를 전혀 사용하지 않더라도 상관하지 않습니다.

0

원래 결과 쿼리를 무수히 많은 필터로 반복하여 축소하려는 경우 (기능 언어의 경우 reduce()에 해당) Aggregate()를 사용할 수 있습니다.

필터는 예측 가능한 형태로, 귀하의 게시물에서 수집 한 정보에 따라 MyObject의 모든 구성원에 대해 두 개의 값으로 구성됩니다. 비교할 모든 구성원이 널 (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)); 

나는 모의 테스트 값과 콘솔 응용 프로그램에서이 작업을 실행하고 예상대로 일했다. 나는 이것이 적어도 원리를 입증한다고 생각한다.

0

체크 박스를 제거하고 드롭 다운 목록에서 "<empty>"또는 "<null>"항목을 대신 사용하여 UI를 단순화하는 것이 좋습니다. 이렇게하면 창에서 공간을 차지하는 컨트롤 수가 줄어들고 복잡한 "Y가 선택되지 않은 경우 X 활성화"논리의 필요성이 제거되고 쿼리 - 필드 당 하나의 좋은 컨트롤을 사용할 수있게됩니다.

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

당신은 단순히 (주셔서 감사합니다 컨트롤을 열거하고 관련 필터를 실행하여 결과 필터링을 일반화 할 수 있습니다 다음 UI 컨트롤 및 사용자가 쿼리를 시작할 때 것을 검색 집계 아이디어 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

이 SQL에 LINQ 있음을 알아 두셔야합니다 (I 질문에 말을하지 않았다, 그러나 그것은 태그 중 하나). 나는 필터링이 클라이언트 측이 아니라 데이터베이스 측에서 일어나길 원한다. –

+1

아! 이 접근법에 -1. – loudej

관련 문제