2013-03-25 4 views
2

컨트롤러 작업 중 하나에서 0 개 이상의 Key : Value 쌍이있는 JSON 문자열을 사용하여 개체 컬렉션을 검색하는 ASP.NET Web API 서비스에서 작업하고 있습니다. 이 때문에 나는 콜렉션을 필터링하는 요청에 어떤 필드 이름이 올지 알지 못합니다.필드 이름을 알 수없는 동적 LINQ 식

이제 제공된 데이터를 기반으로 WHERE 표현식을 연결하여 동적 쿼리를 작성하는 코드가 있습니다. 이 경우 문제는 필터링해야 할 필드 이름을 알 수있을뿐만 아니라 필드 목록과 해당 값이 각 개체 내의 컬렉션에 저장되며 해당 컬렉션의 개체에는 두 가지 속성 만 있습니다. 이름과 값.

데이터가 제어 할 수없는 일련의 XML 파일에서 데이터를 deserialize하고 있으므로 (서식을 변경할 수 없음) 각 파일에 포함 된 필드 목록이 다를 수 있습니다. (아래의 클래스 정의 참조).

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] 
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)] 
public class Bug 
{ 
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    public int ID { get; set; } 

    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    public DateTime ChangedDate { get; set; } 

    [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    [System.Xml.Serialization.XmlArrayItemAttribute("Field", typeof(BugField), Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] 
    public List<BugField> Fields {get; set;} 
} 

나는 다음과 같은 쿼리 모든 것이 잘 작동 실행하는 경우 - 나는 내가 JSON 요청에 기반을 찾고 결과를 얻을 수 - 해당 요청은 하나 개의 필드와 하나 개의 값에서 검색됩니다 그러나, 그리고 쿼리가 인덱스가 하드 올바른 필드의) (참고 - itemList에 아무 필터링 이전에 만든 모음)

itemList = (List<Bug>)itemList.Where(x => x.Fields[15].Value.ToString() == field.Value.ToString()).ToList(); 

내가 (제공되는 검색 필드를 기반으로 WHERE 식) 체인 동적 LINQ 쿼리를 만들 수있는 장소에 코드를 할 수있다 JSON 요청 (이 코드는 너무 길기 때문에 여기에 포함시키지 않아도됩니다. 물론 완전히 관련이 있습니다 ...). 그러나식이 구문 분석되는 방식에 따라 검색 할 속성의 이름을 참조 할 수 있어야합니다. 이름 속성의 값이므로 물론 알 수 없습니다.

이렇게 - 쿼리 매개 변수를 결정하는 필드 이름을 미리 고려하여 쿼리를 수정하는 방법은 무엇입니까?

편집 : 다음 코드 블록은 내가 사용하고자하는 (또는 동적 쿼리 작성기에서 작동하는) 것을 보여줍니다. 첫 번째 줄은 클래스의 필드 이름이 JSON 문자열에 제공된 필드 이름과 동일하게 정의 된 경우 잘 작동하는 코드입니다. 두 번째는 내부 컬렉션 필드 이름 속성에 접근하려고 시도한 시도 중 하나입니다.

foreach (KeyValuePair<string, object> field in queryFields) 
{ 
    itemList = itemList.Where<Bug>(field.Key, field.Value, (FilterOperation)StringEnum.Parse(typeof(FilterOperation), "eq")); 
    itemList = itemList.Where<Bug>(x => x.Fields.Any(y => y.Name == field.Key && y.Value == field.Value)); 
} 
+0

이 cs 파일 맨 위에'using System.Xml.Serialization'을 추가하지 않은 이유가 있습니까? 그것은 당신의 코드를 읽기가 어렵게 만든다. 이'using '문을 추가하면'System.Xml'을 제거 할 수 있습니다.속성 선언에서 '직렬화'를 선택합니다. 예 :'[System.Xml.Serialization.XmlArrayAttribute (..)'대신 [[XmlArrayAttribute (..)]. – Odys

+0

@odyodyodys : 동의 함. 속성의 경우 C#에서 암시 하듯이 "Attribute"라는 꼬리표를 삭제할 수도 있습니다. – recursive

+0

사실 - 아니, 이유가 없습니다. 일반적으로 나는 물건을 더 깔끔하게하기 위해 정확히 그것을한다. :) –

답변

0

이 솔루션을 다른 곳으로 크게 변경하지 않고이 솔루션을 얻으려는 시도에 거의 2 일을 소비 한 후,이 작업을 쉽게하기 위해 모델을 약간 변경하기로 결정했습니다.

어떻게 해결 했습니까? 사용자 정의 개체 목록을 사용하는 대신 모델에 직접 각 필드를 추가하는 것이 었습니다. 이로 인해 나는 XML Deserialization을 제거하고 XDocument를 사용하여 Node 내의 모든 요소들을 루프 처리했다. 그리고 "Name"속성의 값을 비교하여 어떤 필드인지 판단하고 "Value"속성의 값을 할당했다. 모델의 해당 Property로 이동합니다.

약간의 추가 코드 및 할당을 처리 할 수 ​​22가지 경우에 좋은 작은 스위치 문 ...

나는 그것을 할 싶어하지 방법, 그러나 적어도 나는 아직도 시간에 서비스를 제공 할 수 있습니다 다음

: 역 직렬화 방법으로 무엇을 사용

public class Bug 
{ 
    public int ID { get; set; } 

    public DateTime ChangedDate { get; set; } 

    public string State { get; set; } 
    public string AreaPath { get; set; } 
    public string Title { get; set; } 
    public string Status { get; set; } 
    public string SubStatus { get; set; } 
    public string OpenedBy { get; set; } 
    public string ChangedBy { get; set; } 
    public DateTime OpenedDate { get; set; } 
    public DateTime ResolvedDate { get; set; } 
    public string AssignedTo { get; set; } 
    public string IssueType { get; set; } 
    public string IssueSubtype { get; set; } 
    public string Priority { get; set; } 
    public string Severity { get; set; } 
    public string Keywords { get; set; } 
    public string ScreenID { get; set; } 
    public string ResolvedBy { get; set; } 
    public string Resolution { get; set; } 
    public string ReproSteps { get; set; } 
    public string HowFound { get; set; } 
    public string FullHistory { get; set; } 
    public string EverChangedBy { get; set; } 


} 

과 : 여기에 완성도를 위해서

내가 변경된 것입니다 ... 나중에이 작업을 수행하는 더 나은 방법이 있는지 확인하려고

foreach (KeyValuePair<string, object> field in queryFields) 
     { 
      itemList = itemList.Where<Bug>(field.Key, field.Value, (FilterOperation)StringEnum.Parse(typeof(FilterOperation), "eq"));     
     } 

건배 : 나를 LINQ 쿼리를 구축하기 위해 다음 전화를 사용할 수

internal static Bug Deserialize(string filePath) 
    { 
     XDocument doc = XDocument.Load(filePath); 
     Bug bug = new Bug(); 

     bug.ID = int.Parse(doc.Root.Element("ID").Value); 
     bug.ChangedDate = DateTime.Parse(doc.Root.Element("ChangedDate").Value); 

     foreach (var el in doc.Root.Element("Fields").Elements()) 
     { 
      XAttribute fieldName = el.Attributes("Name").Single(); 
      XAttribute fieldValue = el.Attributes("Value").Single(); 

      switch (fieldName.Value.ToString()) 
      { 
       case "State": 
        bug.State = fieldValue.Value.ToString(); 
        break; 
       case "Area Path": 
        bug.AreaPath = fieldValue.Value.ToString(); 
        break; 
       case "Title": 
        bug.Title = fieldValue.Value.ToString(); 
        break; 
       case "Status": 
        bug.Status = fieldValue.Value.ToString(); 
        break; 
       case "SubStatus": 
        bug.SubStatus = fieldValue.Value.ToString(); 
        break; 
       case "Opened By": 
        bug.OpenedBy = fieldValue.Value.ToString(); 
        break; 
       case "ChangedBy": 
        bug.ChangedBy = fieldValue.Value.ToString(); 
        break; 
       case "Opened Date": 
        bug.OpenedDate = DateTime.Parse(fieldValue.Value.ToString()); 
        break; 
       case "Resolved Date": 
        bug.ResolvedDate = DateTime.Parse(fieldValue.Value.ToString()); 
        break; 
       case "Assigned To": 
        bug.AssignedTo = fieldValue.Value.ToString(); 
        break; 
       case "Issue Type": 
        bug.IssueType = fieldValue.Value.ToString(); 
        break; 
       case "Issue Subtype": 
        bug.IssueSubtype = fieldValue.Value.ToString(); 
        break; 
       case "Priority": 
        bug.Priority = fieldValue.Value.ToString(); 
        break; 
       case "Severity": 
        bug.Severity = fieldValue.Value.ToString(); 
        break; 
       case "Keywords": 
        bug.Keywords = fieldValue.Value.ToString(); 
        break; 
       case "ScreenID": 
        bug.ScreenID = fieldValue.Value.ToString(); 
        break; 
       case "ResolvedBy": 
        bug.ResolvedBy = fieldValue.Value.ToString(); 
        break; 
       case "Resolution": 
        bug.Resolution = fieldValue.Value.ToString(); 
        break; 
       case "ReproSteps": 
        bug.State = fieldValue.Value.ToString(); 
        break; 
       case "HowFound": 
        bug.State = fieldValue.Value.ToString(); 
        break; 
       case "FullHistory": 
        bug.State = fieldValue.Value.ToString(); 
        break; 
       case "EverChangedBy": 
        bug.State = fieldValue.Value.ToString(); 
        break; 
      } 
     } 

     return bug; 
    } 

!

1) 익명을 IEnumerable이
2를 필요에 따라 ON)을 IEnumerable 또는 IList의 배역, 오는 :

0

나는 방법의 몇이를 해결하기 위해 참조 :

1), 필터링에 사용할 수있는 모든 속성의 목록을 가지고 속성을 일치하고 LINQ 쿼리를 동적으로 추가 할 수 있습니다.

작동 방법은 json 개체를 수락하고 값과 함께 전달 된 속성을 반복하는 것입니다. 그래서, 당신은 컨트롤러에서 얻을 말 : 컨트롤러에서

{ 
Prop[0].Name = "Name" 
Prop[0].Value = "Justin" 
Prop[1].Name = "Email" 
Prop[1].Value = "[email protected]" 
} 

, 예를 들어, 각 키 값과 dynamicall 체인이 반복됩니다. 일부 의사 코드 :

foreach(var keyValue in Filters){ 
    var prop = keyValue.Name; 
    var val = keyValue.Value; 
    myLinqQuery.AddWhere(c => c.GetType().GetProperty().Name == prop && c.GetValue() == Value); 
} 

이 apprach는 정상적으로 작동하지만 하나의 큰 단점은 리플렉션을 통해 요청의 각 속성을 시도하고 필터링한다는 것입니다. 이 경우 제어를 잃어 버리게됩니다.

2) 여러 개의 필터 만 허용하고 각 필터 조건을 설정하십시오. 그 옵션에서는 필터 목록 만 허용하고 각 필터에 대해 어떤 종류의 조치가 있습니다. 다음은 간단한 예입니다 : 당신은 허용 각 필터에 대해 별도의 클래스를 생성하고 필터 동작을 노출함으로써보다 OOP 및 SOLID을 할 수있는 디자인을 촉진 할 수

public ActionResult Filter(string name, string email){ 
//name and email are the filter values 
var query = (from c in (List<Bug>)itemList select c); 
if(!string.IsNullOrEmpty(name)) 
    query.AddWhere(c => c.Name == name); 

if(!string.IsNullOrEmpty(email)) 
    query.AddWhere(c => c.Email == email); 


} 

: 컨트롤러가 취하는 것을 말한다.

도움이되는지 알려주세요.

+0

두 번째 접근법은 효과가 있지만 내가하고자하는 것은 아닙니다. 검색 대상 필드가 상당히 많으며 다른 조합에 대해 별도의 동작을 올바르게 수행하지 않을 것입니다. 각 항목에 대한 여러 조건부 : 첫 번째 접근 방법을 조사하고 있습니다. 감사합니다 ... –

+0

.AddWhere() 메서드는 어디에 있습니까? 나는 당신의 첫 번째 제안 작업을 할 수 없었습니다. 왜냐하면 c.GetType(). GetProperty() 함수가 해결되지 않았습니다 ... –

+0

안녕하세요, AddWhere 메서드가 의사 코드입니다. 당신은 내가 여기서 언급 한 문장이 어디에 동적으로 chaning하는 방법이 있다고 언급했습니다. 또한, 반사 방법은 유사 코드입니다. 당신은 그 질문에 그 사람을 찾아 볼 수 있습니다 : http://stackoverflow.com/questions/1196991/get-property-value-from-string-using-reflection-in-c-sharp – sTodorov

0

나는이 오래지만, 여기에 내가 모든 가능한 이름을 입력 할 필요없이 그것을 어떻게 알고 첫 번째 요소는,

var property = element.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) 

3) 속성의 Foreach 객체는 당신의 데이터 테이블에 열을 추가 :

table.Columns.Add(property.Name, typeof(property.GetValue(element, null)); 

4)를 처음부터 컬렉션을 생이 : foreach는 :

이 익명 유형을 얻는다 (A IDictionary 완전히 상호 교환되는)를 ExpandoObject로 추출
var prop = item.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); 

dynamic i = new ExpandoObject(); 
IDictionary<string, object> d = (IDictionary<string, object>)i; 
foreach (var p in prop) 
{ 
    d.Add(p.Name, p.GetValue(item)); 
} 
list.Rows.Add(((IDictionary<string, object>)d).Values.ToArray()); 

은 다음 배열로 IDictionary 동적 객체를 캐스팅하여 행을 추가 .

모든 유형의 가늠자를 사용할 수 있으며, 구성 할 때 데이터를 조작 할 수 있습니다.