简体   繁体   中英

Dynamic Linq to Xml example

I need a basic example on how to use System.Linq.Dynamic with Xml. Here's a functioning statement I want to convert to dynamic 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;
}

Here's the approach I am using:

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

It executes without error but produces no results.

I am trying to code this similar to the canonical example found at http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx where a constructed string is applied against a database:

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

What am I missing?


I finally got it working. I abandoned my original approach because as of now I'm not convinced it was even intended for use with Xml. I've seen little posted anywhere to argue against that statement. Instead, I used Jon Skeet's response to this question as the basis for my answer:

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

This approach, however, is obviously far from perfect.

  1. I have separate functions for resolving and compiling the expressions-- just to incorporate different conditional operators. What's worse, I will be adding more to support additional logical operators and numeric values;
  2. The GetValueFromXml routine is clunky and will have to grow additional cases as I add more parameters.

Any ideas or suggestions would be greatly appreciated.

There are two issues here really, your where clause:

("AGMNT_TYPE_CODE") == "ISDA"

... will of course evaluate to false , because they are both strings.

The second issue is that the ExpressionParser is limited in scope, it can only do comparisons on a set of predefined types. You need to recompile the Dynamic library and either allow some additional types (you can do this by modifying the predefinedTypes static field of the ExpressionParser type) or, remove the check for predefined types (which is what I have done previously):

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));
        }
  // ...
}

Those lines I have commented out are where the check for predefined types are done.

Once you have made that change, you need to update your query (remember, ExpressionParser builds expressions that are compiled, so simply using "(\\"AGRMNT_TYPE_CODE\\") == \\"ISDA\\"" wont work. You would need something like:

string where = "Element(\"AGMNT_TYPE_CODE\").Value == \"ISDA\"";

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM