Пример динамического Linq to Xml

Мне нужен базовый пример использования System.Linq.Dynamic с Xml. Вот работающий оператор, который я хочу преобразовать в динамический Linq:

XElement e = XElement.Load(new XmlNodeReader(XmlDoc));
var results =
    from r in e.Elements("TABLES").Descendants("AGREEMENT")
    where (string)r.Element("AGRMNT_TYPE_CODE") == "ISDA"
    select r.Element("DATE_SIGNED");

foreach (var x in results)
{
    result = x.Value;
    break;
}

Вот подход, который я использую:

string whereClause = "(\"AGRMNT_TYPE_CODE\") == \"ISDA\"";
string selectClause = "(\"DATE_SIGNED\")";
var results = e.Elements("TABLES").Descendants<XElement>("AGREEMENT").
                AsQueryable<XElement>().
                Where<XElement>(whereClause).
                Select(selectClause); 

foreach (var x in results)
{
    result = (string)x;
    break;
}

Он выполняется без ошибок, но не дает никаких результатов.

Я пытаюсь закодировать это аналогично каноническому примеру, найденному по адресу http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx, где созданная строка применяется к базе данных:

Dim Northwind as New NorthwindDataContext
Dim query = Northwind.Products _
                     .Where("CategoryID=2 and UnitPrice>3") _
                     .OrderBy("SupplierId")
GridView1.Datasource = query
GridView1.Databind()

Что мне не хватает?


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

XElement e = XElement.Load(new XmlNodeReader(XmlDoc));

List<Func<XElement, bool>> exps = new List<Func<XElement, bool>> { };
exps.Add(GetXmlQueryExprEqual("AGRMNT_TYPE_CODE", "ISDA"));
exps.Add(GetXmlQueryExprNotEqual("WHO_SENDS_CONTRACT_IND", "X"));

List<ConditionalOperatorType> condOps = new List<ConditionalOperatorType> { };
condOps.Add(ConditionalOperatorType.And);
condOps.Add(ConditionalOperatorType.And);

//Hard-coded test value of the select field Id will be resolved programatically in the
//final version, as will the preceding literal constants.
var results = GetValueFromXml(171, e, exps, condOps);

foreach (var x in results)
{
    result = x.Value;
break;
}

return result;
...
public static Func<XElement, bool> GetXmlQueryExprEqual(string element, string compare)
{
    try
    {
        Expression<Func<XElement, bool>> expressExp = a => (string)a.Element(element) == compare;
        Func<XElement, bool> express = expressExp.Compile();
        return express;
    }   
    catch (Exception e)     
    {
        return null;
    }
}

public static Func<XElement, bool> GetXmlQueryExprNotEqual(string element, string compare)
{
    try
    {
        Expression<Func<XElement, bool>> expressExp = a => (string)a.Element(element) != compare;
        Func<XElement, bool> express = expressExp.Compile();
        return express;
    }
    catch (Exception e)
    {
        return null;
    }
}

private IEnumerable<XElement> GetValueFromXml(int selectFieldId, XElement elem, 
    List<Func<XElement, bool>> predList, List<ConditionalOperatorType> condOpsList)
{
    try
    {
        string fieldName = DocMast.GetFieldName(selectFieldId);
        string xmlPathRoot = DocMast.Fields[true, selectFieldId].XmlPathRoot;
        string xmlPathParent = DocMast.Fields[true, selectFieldId].XmlPathParent;
        IEnumerable<XElement> results = null;
        ConditionalOperatorType condOp = ConditionalOperatorType.None; 

    switch (predList.Count)
    {
        case (1):
          results =
            from r in elem.Elements(xmlPathRoot).Descendants(xmlPathParent)
            where (predList[0](r))
            select r.Element(fieldName);
          break;
        case (2):
            CondOp = (ConditionalOperatorType)condOpsList[0];
            switch (condOp)
            {  
                case (ConditionalOperatorType.And):
                    results =
                    from r in elem.Elements(xmlPathRoot).Descendants(xmlPathParent)
                    where (predList[0](r) && predList[1](r))
                    select r.Element(fieldName);
                    break;
                case (ConditionalOperatorType.Or):
                    results =
                    from r in elem.Elements(xmlPathRoot).Descendants(xmlPathParent)
                    where (predList[0](r) || predList[1](r))
                    select r.Element(fieldName);
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
    return results;
}
    catch (Exception e)
    {
        return null;
    }
}

Однако такой подход явно далек от совершенства.

  1. У меня есть отдельные функции для разрешения и компиляции выражений — просто для включения разных условных операторов. Что еще хуже, я буду добавлять больше для поддержки дополнительных логических операторов и числовых значений;
  2. Процедура GetValueFromXml неуклюжа, и по мере добавления дополнительных параметров ей придется увеличивать число случаев.

Мы будем очень признательны за любые идеи или предложения.


person Jack Straw    schedule 28.09.2011    source источник


Ответы (1)


На самом деле здесь есть две проблемы, ваше предложение where:

("AGMNT_TYPE_CODE") == "ISDA"

..., конечно, будет оцениваться как false, потому что обе они являются строками.

Вторая проблема заключается в том, что ExpressionParser имеет ограниченный охват, он может выполнять сравнения только с набором предопределенных типов. Вам нужно перекомпилировать динамическую библиотеку и либо разрешить некоторые дополнительные типы (вы можете сделать это, изменив статическое поле predefinedTypes типа ExpressionParser), либо убрать проверку предопределенных типов (что я и сделал ранее):

Expression ParseMemberAccess(Type type, Expression instance)
{
  // ...
        switch (FindMethod(type, id, instance == null, args, out mb))
        {
            case 0:
                throw ParseError(errorPos, Res.NoApplicableMethod,
                    id, GetTypeName(type));
            case 1:
                MethodInfo method = (MethodInfo)mb;
                //if (!IsPredefinedType(method.DeclaringType)) // Comment out this line, and the next.
                    //throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType));
                if (method.ReturnType == typeof(void))
                    throw ParseError(errorPos, Res.MethodIsVoid,
                        id, GetTypeName(method.DeclaringType));
                return Expression.Call(instance, (MethodInfo)method, args);
            default:
                throw ParseError(errorPos, Res.AmbiguousMethodInvocation,
                    id, GetTypeName(type));
        }
  // ...
}

В тех строках, которые я закомментировал, выполняется проверка предопределенных типов.

После того, как вы внесли это изменение, вам нужно обновить свой запрос (помните, ExpressionParser строит выражения, которые компилируются, поэтому простое использование "(\"AGRMNT_TYPE_CODE\") == \"ISDA\"" не сработает. Вам понадобится что-то вроде:

string where = "Element(\"AGMNT_TYPE_CODE\").Value == \"ISDA\"";
person Matthew Abbott    schedule 28.09.2011
comment
Ранее я пробовал: string where = Element(\AGMNT_TYPE_CODE\).Value == \ISDA\; и он создает System.Linq.Dynamic.ParseException: не существует применимого метода «Элемент» в типе «XElement». Кажется, что этот общий подход должен работать. (см. мое дополнение к исходному сообщению выше) Я уверен, что это просто вопрос правильного синтаксиса. Я безуспешно делал проб и ошибок неоднократно, прежде чем сделать эту публикацию. - person Jack Straw; 28.09.2011