Linq to Object from XML

duphus

Member
Joined
Dec 26, 2009
Messages
20
Programming Experience
Beginner
Have the following XML

VB.NET:
</assay>
<assay assayid="1234" assayname="name" assaytype="type">
[INDENT]<importfromfile>True</importfromfile>
<directories>
[INDENT]<inputdirectory>
[INDENT]<directoryname>c:\</directoryname>
<extension>*.txt</extension>
<extension>*.csv</extension>
<status/>[/INDENT]
</inputdirectory>
<inputdirectory>
[INDENT]<directoryname>c:\</directoryname>
<extension>*.*</extension>
<status />[/INDENT]
</inputdirectory>[/INDENT]
[INDENT]<outputdirectory>
[INDENT]<directoryname>c:\</directoryname>
<status />[/INDENT]
</outputdirectory>[/INDENT]
</directories>[/INDENT]
</assay>

So created an object Assay and a corresponding list of AssayDirectories with a list of Extensions

Having trouble using linq to populate the object
This works
VB.NET:
 Dim result = From a In doc.<assays>.<assay> Where a.@assayname = assayQuery _
            Select New AssayConfig With { _
                  .AssayName = a.@assayname, _
                  .AssayId = a.@assayid, _
                  .AssayType = a.@assaytype, _
                  .AssayFromFile = Convert.ToBoolean(a.<importfromfile>.Value)}

This throws exceptions because it cannot cast to the list
VB.NET:
Dim inputdir = From a In doc...<assay> Where a.@assayname = assayQuery _
            Select New AssayDirectories With { _
                  .DirectoryName = a.<directories>.<inputdirectory>.<directoryname>.Value, _
                  .DirectoryType = "INPUT", _
                  .Extension = New List(Of Extension)(From ext In a.<directories>.<inputdirectory>.<extension> Where a.@assayname = assayQuery Select ext.ToList), _
                  .Status = a.<directories>.<inputdirectory>.<status>.Value}
 
'ext' is a single XElement, which represent a single String value. You can do a (From ... Select ext).ToList to get a List(Of XElement), or (From ... Select ext.Value).ToList to get a List(Of String).
 
I try
VB.NET:
.Extension = New List(Of Extension)(From ext In a.<directories>.<inputdirectory>.<extension>
                                                      Where a.@assayname = assayQuery Select ext).ToList, _

VB.NET:
.Extension = New List(Of Extension)(From ext In a.<directories>.<inputdirectory>.<extension>
                                                      Where a.@assayname = assayQuery Select ext.Value).ToList, _

I get
Unable to cast object of type 'WhereSelectEnumerableIterator`2[System.Xml.Linq.XElement,System.String]' to type 'System.Collections.Generic.IEnumerable`1[Project.Extension]'.

Unable to cast object of type 'WhereSelectEnumerableIterator`2[System.Xml.Linq.XElement,System.Xml.Linq.XElement]' to type 'System.Collections.Generic.IEnumerable`1[Project.Extension]'.

In my AssayDirectory Class I have
VB.NET:
Public Property Extension() As List(Of Extension)
        Get
            Return _extensionList
        End Get
        Set(ByVal Value As List(Of Extension))
            _extensionList = Value
        End Set
    End Property

Advice?
 
(... Select ext.Value).ToList returns a List(Of String) as I said.
 
Declare the Extension property as String() and assign it the query result ToArray.
 
I did
VB.NET:
Public Property Extension() As List(Of String)
        Get
            Return _extensionList
        End Get
        Set(ByVal Value As List(Of String))
            _extensionList = Value
        End Set
    End Property

VB.NET:
Dim inputdir = From a In doc...<assay> Where a.@assayname = assayQuery _
            Select New AssayDirectories With { _
                  .DirectoryName = a.<directories>.<inputdirectory>.<directoryname>.Value, _
                  .DirectoryType = "INPUT", _
                  .Extension = a.Elements("extension").[Select](Function(ext) ext.Value).ToList(), _
                  .Status = a.<directories>.<inputdirectory>.<status>.Value}

Seems to work.

How can I do something like
VB.NET:
AssayDirectories = From a In doc...<assay> Where a.@assayname = assayQuery _
            Select New AssayDirectories With { _
                  .DirectoryName = a.<directories>.<inputdirectory>.<directoryname>.Value, _
                  .DirectoryType = "INPUT", _
                  .Extension = a.Elements("extension").[Select](Function(ext) ext.Value).ToList(), _
                  .Status = a.<directories>.<inputdirectory>.<status>.Value}.ToAssayDirectories
 
ToAssayDirectories? What is that supposed to mean? The query returns IEnumerable(Of AssayDirectories) where AssayDirectories is your class that really is a AssayDirectory. If you need a list use ToList, or ToArray for an array.
 
Here's where I'm having trouble again:

VB.NET:
    Public Property AssayColumns() As List(Of AssayColumn)
        Get
            Return _assayColumns
        End Get
        Set(ByVal Value As List(Of AssayColumn))
            _assayColumns = Value
        End Set
    End Property

I try:

VB.NET:
        AssayColumns = From a In doc...<assay> Where a.@assayname = assayQuery _
            Select New AssayColumn With { _
                    .ColumnDataType = Type.GetType(a.<columns>.<column>.<columndatatype>.Value), _
                    .ColumnHeaderFill = a.<columns>.<column>.<columnheaderfill>.Value, _
                    .ColumnHeaderText = a.<columns>.<column>.<columnheadertext>.Value, _
                    .ColumnReadOnly = a.<columns>.<column>.<columnreadonly>.Value, _
                    .ItemList = a.Elements("item").[Select](Function(item) item.Value).ToList()}

Returns an InvalidCastException. How do I set the list of custom object to the public property? I can't convert this object to a list of string like I could above.
 
Use the ToList function to convert the IEnumerable(Of AssayColumn) to a List(Of AssayColumn).

Btw, a collection type property should not have a setter, instead you should use the AddRange method when adding multiple objects to the collection. CA2227: Collection properties should be read only
 
You're right, I forgot the parantheses

VB.NET:
AssayColumns = (From a In doc...<assay> Where a.@assayname = assayQuery _
            Select New AssayColumn() With { _
                    .ColumnDataType = Type.GetType(a.<columns>.<column>.<columndatatype>.Value), _
                    .ColumnHeaderFill = a.<columns>.<column>.<columnheaderfill>.Value, _
                    .ColumnHeaderText = a.<columns>.<column>.<columnheadertext>.Value, _
                    .ColumnReadOnly = a.<columns>.<column>.<columnreadonly>.Value, _
                    .ItemList = a.Elements("item").[Select](Function(item) item.Value).ToList()}).ToList
 
Strange, its only selecting the first instance of column. This is the structure (there are multiple columns in each section):

VB.NET:
<assay>
<columns>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name3</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.Boolean</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name4</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
    </columns>
</assay>
<assay>
<columns>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name3</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.Boolean</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name4</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
    </columns>
</assay>

MsgBox(AssayColumns.Count) returns 1

Edit: Realized its only selecting the first column element of the query. Each assayquery should have multiple column elements though, how do I write my query correctly?
 
Last edited:
Restructure your From statement to go to <column>.

Here's a small example that will get you 4 items in your List(Of AssayColumn).

Public Class Form1

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Dim xdoc = XDocument.Parse(IO.File.ReadAllText("C:\Temp\Assay.xml"))
        Dim cols = (From n In xdoc...<column>
                   Select New AssayColumn With {
                       .ColumnDataType = Type.GetType(n.<columndatatype>.Value),
                       .ColumnHeaderFill = n.<columnheaderfill>.Value,
                       .ColumnHeaderText = n.<columnheadertext>.Value,
                       .ColumnReadOnly = CBool(n.<columnreadonly>.Value)
                   }).ToList
    End Sub

End Class

Friend Class AssayColumn
    Public Property ColumnDataType As Type
    Public Property ColumnHeaderFill As String
    Public Property ColumnHeaderText As String
    Public Property ColumnReadOnly As Boolean
End Class


I have no idea the full structure of your XML of AssayWhatever classes. Here's the XML file I used in the example.

VB.NET:
<?xml version="1.0" encoding="utf-8" ?>
<columns>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name2</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.String</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name3</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
      <column>
        <columndatatype>System.Boolean</columndatatype>
        <columnreadonly>False</columnreadonly>
        <columnheadertext>name4</columnheadertext>
        <columnheaderfill>40</columnheaderfill>
      </column>
</columns>
 
Thank you, that was the correct LINQ query. I had to do:

VB.NET:
AssayColumns = (From a In xdoc...<column> Where a.Parent.Parent.Attribute("assayname") = assayQuery _
            Select New AssayColumn With { _
                    .ColumnDataType = Type.GetType(a...<columndatatype>.Value), _
                    .ColumnHeaderFill = a...<columnheaderfill>.Value, _
                    .ColumnHeaderText = a...<columnheadertext>.Value, _
                    .ColumnReadOnly = a...<columnreadonly>.Value, _
                    .ItemList = a.Elements("item").[Select](Function(item) item.Value).ToList()}).ToList

in order to get only the columns in that section
 
Rather than do a.Parent.Parent.Attribute("assayname") thing it's cleaner to use XNode.Ancestors

a.Ancestors("NodeName").Attribute("assayname")
 
Back
Top