Question Return value of a LINQ query

sebastian.

Member
Joined
Sep 20, 2009
Messages
17
Programming Experience
1-3
Hi everybody!
I'm new here and I'm not a native speaker, so please don't be to severe ;).

My XML source file:

VB.NET:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<pricelist>

    <item>

        <item_no>123</item_no>

        <item_description>

            <description1>new item abc</description1>

            <description2>has this properties</description2>

        </item_description>

        <item_group>group 1 blabla</item_group>

    </item>

    <item>

...
</pricelist>


The following code is working, but I want to modify it a bit (see below):

VB.NET:
      Public Class XMLdb

          ' Fields

          Dim XMLfile As XElement

       
          ' Constructor

          Sub New(ByRef strXMLFilePath As String)

              ' create XDocument and load xml file

              Me.XMLfile = XElement.Load(strXMLFilePath)

          End Sub

       

          'Methods

           Public Function GetXMLData(ByRef grid As DataGridView) As Object

              Dim result = From item In XMLfile.Elements("item") _

                          Where item.Element("item_group").Value Like "group 1 *" _

                          Select New With { _

                          .no = item.<item_no>.Value, _

                          .group = item.<item_group>.Value _

                          '...

                          }

       

              grid.DataSource = result.ToList

              Return result

          End Function

      End Class


The following is part of a call of the function in another class:

VB.NET:
      Dim XMLdbFile As New XMLdb(ofdXML.FileName)  'I get the ofdXML.FileName from an "OpenFileDialog"
      Dim xmlresult = XMLdbFile.GetXMLData(Me.DataGridView1)

But now I don't want to hand over the Me.DataGridView1, but rather want to have a proper return value of the function so I can write the following line in this part of the code, where I'm calling the function:

VB.NET:
Me.DataGridView1.DataSource = xmlresult.ToList

So I don't want to use the ByRef parameter.

What return value instead of Object do I have to use?
I think result is of this type:
System.Collections.Generic.IEnumerable(Of <anonymous Typ>)

Many thanks in advance!

Sebastian
 
this help page explains why you can't declare and share an anonymous type across methods:
Anonymous Types
and this help page show the solution where you define a type to hold data:
How to: Return a LINQ Query Result as a Specific Type (Visual Basic)

You can of course also return type Object and still bind the grid to this. The DataSource property is after all also type Object, but the object provided must be usable, f.ex "Return result.ToList" returns an object that can be assigned directly to the DataSource.

If you decide to keep the grid parameter, change ByRef to ByVal, see for example this post about why: http://www.vbdotnetforums.com/107314-post5.html
 
Hi John!

Thanks for your help!
Think the solution with the "Return result.ToList" is great!

So I don't need to hand over a parameter...
(And I also tried it with a ByVal parameter. I know that I should use this one as often as possible, but I thought I would need a ByRef parameter, because I modified the Datagrid in the function...but that was obviously wrong...works also with ByVal.)

-------------------------------

But now I've another problem:

I have the following sample xml code (almost the same as before):

VB.NET:
<ITEM_LIST>
	<ITEM>
		<ITEM_NO>12345</ITEM_NO>
		<ITEM_PRODUCT_GROUP_DESCRIPTION>Group 1 blabla</ITEM_PRODUCT_GROUP_DESCRIPTION>
		<SMS_BAR>
			<SMS_BAR_FEATURE>
				<FEATURE_NO>1</FEATURE_NO>
				<FEATURE_NAME>Current I</FEATURE_NAME>
				<FEATURE_VALUE>8</FEATURE_VALUE>
			</SMS_BAR_FEATURE>
			<SMS_BAR_FEATURE>
				<FEATURE_NO>2</FEATURE_NO>
				<FEATURE_NAME>Value X</FEATURE_NAME>
				<FEATURE_VALUE>123</FEATURE_VALUE>
			</SMS_BAR_FEATURE>
			<SMS_BAR_FEATURE>
				<FEATURE_NO>3</FEATURE_NO>
				<FEATURE_NAME>Voltage U</FEATURE_NAME>
				<FEATURE_VALUE>20</FEATURE_VALUE>
			</SMS_BAR_FEATURE>
		</SMS_BAR>
	</ITEM>
	<ITEM>
	...
</ITEM_LIST>

Now I want to get the ITEMs of the List which are in "<ITEM_PRODUCT_GROUP_DESCRIPTION>" "Group 1" for example.
From these ITEMs I save the "ITEM_NO" and the following:

From the "<SMS_BAR_FEATURE>" Tags I need only those, where <FEATURE_NO> is e.g. "1" and from those I need the value of <FEATURE_VALUE>.

Hope it gets a bit clearer with the following code snippet (especially the ".FEATURE = ..." part is not working! -> and the question is, how do I get it working):

VB.NET:
Imports System.Linq

Module Module1

    Sub Main()
       Dim XMLfile As XElement = XElement.Load("D:\test.xml")

        Dim result = From item In XMLfile.<ITEM> _
            Where item.<ITEM_PRODUCT_GROUP_DESCRIPTION>.Value Like "Group 1*" _
            Select New With { _
            .ITEM_NO = item.<ITEM_NO>.Value, _
            .FEATURE = (From features In item.<SMS_BAR> Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "1" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value) _
            }
        
        Dim z As Integer = 0

        For Each i In result
            z += 1
            Console.WriteLine("{0} ", z)
            Console.WriteLine("{0} ", i.ITEM_NO)
            Console.WriteLine("{0} ", i.FEATURE)
        Next

        Console.ReadLine()
    End Sub

End Module

The result is:
1
12345
System.Linq.Enumerable+WhereSelectEnumerableInterator`2[System.Xml.Linq.XElement,System.String]
, but not "8" (the value of the "Current I").

Hope my explanation is not to complicated?! :confused:

Greetings, Sebastian
 
Whenever you make a Linq selection with the From statement an enumerated list of items that match the criteria is returned, this is typed as the generic Enumerable(T). If you're only interested in the first item you can access it like an array with the Integer index:
VB.NET:
dim res = (From ...)[B](0)[/B]
A better variant of this is the First extension:
VB.NET:
dim res = (From ...)[B].First[/B]
If the query could result in no matches there would also be no first element, so both the above statements would throw an exception, here it is also possible to use the FirstOrDefault extension, it returns either the first element or the default value for the generic type in question, for example Nothing for String:
VB.NET:
dim res = (From ...)[B].FirstOrDefault[/B]
 
Hi John,

think I don't get it working...

Instead of the line
VB.NET:
.FEATURE = (From features In item.<SMS_BAR> Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "1" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value) _

I can write the following - sure:
VB.NET:
.FEATURE1 = item.<SMS_BAR>.<SMS_BAR_FEATURE>(0).<FEATURE_VALUE>.Value _

But isn't it possible to do something like a second "From ... Where ... Select" in the "Select New With { ... }" part like this:

VB.NET:
Dim result = From item In XMLfile.<ITEM> _
            Where item.<ITEM_PRODUCT_GROUP_DESCRIPTION>.Value Like "Group 1*" _
            Select New With { _
            .ITEM_NO = item.<ITEM_NO>.Value, _
            .FEATURE1 = (From features In item.<SMS_BAR>.Elements Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "1" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value), _
            .FEATURE3 = (From features In item.<SMS_BAR>.Elements Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "3" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value) _
            }

Greetings, Sebastian
 
But isn't it possible to do something like a second "From ... Where ... Select" in the "Select New With { ... }" part like this:
Why not??

What I don't get is what your intended result is. Is the property supposed to contain one or multiple values? If it's one then just apply the solution in my previous post. As I said in previous post, a Linq query ALWAYS returns a list of matches, that list contains zero or more items. The natural thing to do here is to remove the Where clause and keep the feature value in the list that is returned, For-each them to output later.
 
But my "subselect" isn't working :(.

The solution with the "hard-coded" numbers ( .FEATURE1 = item.<SMS_BAR>.<SMS_BAR_FEATURE>(0).<FEATURE_VALUE>.Value _ ) isn't bad, but if the order / sequence of my XML file changes, this won't work anymore.

Therefore I want these "subselects". It should look s.th. like this:
VB.NET:
.FEATURE1 = (From features In item.<SMS_BAR> Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "1" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value), _
.FEATURE3 = (From features In item.<SMS_BAR> Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "3" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value) _
'...

In my example, .FEATURE1 should then have the value "8" and .FEATURE3 should have the value "20".


BTW: how do I do something like

VB.NET:
Dim str As String = "abc"
Dim xmlOut As XElement
xmlOut = <root></root>
xmlOut.Add(<node>str</node>)
xmlOut.Save("D:\newXMLfile.xml")

It always writes "str" instead of my string-variable...of course...but how do I get this one working?! :confused:


Greetings, Sebastian
 
Okay, I got the solution for the "btw. - problem":
VB.NET:
Dim str As String = "abc"
Dim xmlOut As XElement = New XElement("node", str)
xmlOut.Save("D:\newXMLfile.xml")
 
I think you misunderstood what I was saying. When you do a .FEATURE1 = (From ...) your Feature1 property points to a list of matches. If you only want the first result of these matches you can use the solution I suggested: .FEATURE1 = (From ...).First
 
Hi John,

you where right! Think my sequence/order of statements wasn't correct:

before:
.FEATURE1 = (From features In item.<SMS_BAR> Where features.<SMS_BAR_FEATURE>.<FEATURE_NO>.Value Is "1" Select features.<SMS_BAR_FEATURE>.<FEATURE_VALUE>.Value)

afterwards:
.FEATURE1 = (From features In item.<SMS_BAR>.<SMS_BAR_FEATURE> Where features.<FEATURE_NO>.Value = "1" Select features.<FEATURE_VALUE>.Value).FirstOrDefault

Now it's working!

Thanks again for your help!

Sebastian
 
Back
Top