2013-03-25 2 views
2

Я работаю над сервисом ASP.NET Web Api, где один из действий контроллера принимает строку JSON с 0 или более ключами: пары значений для поиска по коллекции объектов. Из-за этого я не знаю, какие имена полей будут в запросе для фильтрации коллекции.Динамическое выражение LINQ, где неизвестно имя поля

Теперь у меня есть код, который будет строить динамический запрос путем объединения выражений WHERE на основе предоставленных данных. Проблема в этом случае заключается в том, что я не только не знаю имя поля, которое нужно отфильтровать, список полей и их значений хранятся в коллекции внутри каждого объекта - и объекты в этой коллекции имеют только два свойства: Имя и стоимость.

Данные десериализуются из множества XML-файлов, которые я не контролирую (поэтому не могу изменить форматирование), а список полей, содержащихся в каждом файле, может быть другим. (см. определение класса ниже).

[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 - однако этот запрос только поиск на одном поле и одно значение, а запрос имеет индекс правильного поле закодированного;) (FYI - ITEMLIST коллекция, созданная ранее, без фильтрации)

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

у меня есть код, место для создания динамического запроса LINQ (выстраивание WHERE выражения) на основе полого поиска, предоставляемых из запроса JSON (я не включаю код для этого здесь, поскольку он слишком длинный, и не уверен, что он полностью уместен ... YET). Однако, как анализируются выражения, вы должны иметь возможность ссылаться на имя свойства для поиска против - что, конечно, неизвестно, так как это значение свойства Name.

So - Как изменить запрос, чтобы учесть, что имена полей, которые определяют параметры запроса, неизвестны заранее?

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

Есть ли причина, по которой вы не добавляете 'using System.Xml.Serialization' в начало этого cs-файла? Это затрудняет чтение кода. Если вы добавите этот оператор 'using', вы сможете удалить' System.Xml.Сериализация' из объявления атрибута. Например: '[XmlArrayAttribute (..)' вместо '[System.Xml.Serialization.XmlArrayAttribute (..)' – Odys

+0

@odyodyodys: Согласовано. Для атрибутов вы также можете отбросить завершающий «Атрибут», как это видно на C#. – recursive

+0

На самом деле - нет, нет причин. Обычно я делаю именно это, чтобы сделать вещи чище. :) –

ответ

0

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

Как я решил, это было добавление каждого поля в модель напрямую, вместо использования списка пользовательских объектов. Из-за этого я удалил десериализацию XML и вместо этого процитировал все элементы в узле с помощью XDocument, а затем сравнил значение атрибута «Имя», чтобы определить, в каком поле оно было, и присвоить значение атрибуту «Значение» к соответствующему свойству в модели.

Немного лишний код, и миленькое заявление переключателя с 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; } 


} 

и что раньше метод десериализации:

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

Что позволило мне использовать следующий вызов для построения запроса LINQ:

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

Ура!

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) Разрешить только несколько фильтров и иметь условие фильтрации для каждого из них. В этом параметре вы можете разрешить только список фильтров и иметь какое-то действие для каждого из них. Вот простой пример: Скажите, что ваш контроллер принимает:

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


} 

Вы можете в дальнейшем дизайн, чтобы быть более ООП и SOLID, создав отдельный класс для каждого фильтра позволило и обнажая действие его фильтра.

Сообщите мне, если это поможет!

+0

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

+0

Где находится метод .AddWhere()? Я не смог выполнить вашу первую работу по предложению либо из-за того, что не знал, где был указан этот конкретный метод, так и функции c.GetType(). Функции GetProperty() не решались ... –

+0

Эй, метод AddWhere является псевдокодом. Вы упомянули, что у вас есть способ динамически перебирать, где заявления, вот что я здесь говорил. Кроме того, методы отражения также являются псевдокодами. Вы можете посмотреть тех, кто на этот вопрос, для тех: http://stackoverflow.com/questions/1196991/get-property-value-from-string-using-reflection-in-c-sharp – sTodorov

0

Я знаю, что это старое, но вот как я сделал это без того, чтобы ввести все возможное имя:

1) Anonymous IEnumerable приходя в, брошен как IEnumerable или IList в соответствии с требованиями
2) На первый элемент,

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

3) объект Foreach в собственности, добавить столбцы в DataTable:

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

4) It щая скорость вашей коллекции с самого начала: Еогеасп:

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

Это получает анонимные типы, извлекает их в ExpandoObject (который полностью взаимозаменяемо с IDictionary), а затем добавляет строку литья динамического объекта IDictionary как массив ,

Шкала с любым типом, которую вы можете наложить на нее и позволяет вам манипулировать данными по мере их построения.

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