Dynamic LINQ to XML

By , September 8, 2019 1:59 am

Language Integrated Query (LINQ) is a cool feature of .NET languages like C# that allows you to perform SQL-like query right within the language against language’s data structures (lists, arrays etc.) But one drawback of LINQ – you have to know in advance, at compile time which fields to select, what filter conditions would be. Sometimes there’s a need to supply these at runtime – e.g. user selects which fields they want to see

Thankfully there exists Dynamic LINQ Library that allows you to supply LINQ parameters as a string akin Dynamic SQL. Here’s an example of such query from the library’s homepage:

var query = db.Customers
    .Where("City == @0 and Orders.Count >= @1", "London", 10)
    .OrderBy("CompanyName")
    .Select("new(CompanyName as Name, Phone)");

Now, one thing that LINQ can do is query XML. So in theory if we load, say, this XML:

<DATA_CENTER>
   <SERVER IP="1.2.3.4">
      <OS>Windows</OS>
   </SERVER>
   <SERVER IP="5.6.7.8">
      <OS>Linux</OS>
   </SERVER>
</DATA_CENTER>

into an XElement and run something like this

var query0 = myXElement.Elements()
          .AsQueryable()
          .Select("new (Attribute(\"IP\").Value as IP, Element(\"OS\").Value as OS)")

it would produce list of IPs and OSes. Unfortunately this doesn’t work.

If you try to run this query as is you will get an error:

“No applicable method Attribute exists in type XElement

or

“No applicable method Element exists in type XElement

Which you might think is nonsense since clearly that class has those methods. The reason this is happening Dynamic LINQ operates on predefined list of types and does not recognize types not in that list. And XEelement is not in that list. Thankfully the library provides the way to extend list of supported types by adding your own.

First you have to define a class that would allow you to extend type list:

public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider {
    public override HashSet<Type> GetCustomTypes() => 
      new[] { typeof(XName), typeof(XElement) }.ToHashSet();
}

Then assign instance of that class to be custom type provider:

ParsingConfig.Default.CustomTypeProvider = new MyCustomTypeProvider();

And finally you can run your query:

var query0 = myXElement.Elements()
          .AsQueryable()
          .Select("new (Attribute(XName.Get(\"IP\")).Value as IP, Element(XName.Get(\"OS\").Value as OS))")

Notice one more difference: XName.Get. Both Attribute and Element methods of XElement require parameter of type XName. When you supply a string in your code it’s implicitly converted to XName. Dynamic LINQ can’t do this, so we need to do explicit conversion for it.

Huge thanks to @NetMage for providing this solution on Stack Overflow.

Leave a Reply

Panorama Theme by Themocracy

Bitnami