How to fill a listview from LINQ query

D

Dean Slindee

Currently using a legacy Winform listview, that is filled by a function from
datatable(0) in a dataset. This works fine as the datatable has column
headers and data rows that correspond to listview headers and listview rows.

Would like to use Linq to SQL to populate the same legacy WinForm listview.
The code below retrieves the IOrderedQuery object correctly:
Dim dc As New dcAdminDataContext
Dim query = From tblAdminAddressType In
dc.tblAdminAddressTypes _
Order By tblAdminAddressType.AddressTypeID
gvwAdminData.DataSource = query

The last statement fills a datagridview thru the .DataSource property.
Since, a listview has no .DataSource property, I am at an impasse as to how
to convert query to a datatable, or ? I do not want to specify the data
column names within each query nor declare a class for the query structure.
Any ideas?

Thanks in advance,
Dean S
 
C

Cor Ligthert[MVP]

Dean,

The listview is in noway an AdoNet control, while Linq is only AdoNet as it
is for Linq to AdoNet

Why not use bindable controls as meant for those. The listview is an old
verhicle basicly for things as windows explorer and more like that. The
bindable controls where probably not created as it was possible with
something that is from far in the past millenium.

However if you want to use a control as old as almost a T Ford, than make it
workable, but that has no purpose for others, who just use DataBindable
controls as they want to DataBind.

Cor
 
C

Cor Ligthert[MVP]

Dean,

It seems to me easier to loop through the result of the Linq querry, as the
listview is in fact a tree, which has items with rows which exist from
items.

A datatable is not such an easy verhicle as it hold its descriptions in the
columns and his values (as objects) in the rows.

Cor

Dean Slindee said:
Here is the Cast statement I would like to use:
Dim dt As DataTable
dt = CType(query.Cast(Of tblAdminAddressType)(),
System.Data.DataTable)

Perhaps my approach is in error, as today it produces this result:
Exception Type: System.InvalidCastException
Exception Message: Unable to cast object of type
'System.Data.Linq.DataQuery`1[Admin.tblAdminAddressType]' to type
'System.Data.DataTable'.


Cor Ligthert said:
Dean,

The listview is in noway an AdoNet control, while Linq is only AdoNet as
it is for Linq to AdoNet

Why not use bindable controls as meant for those. The listview is an old
verhicle basicly for things as windows explorer and more like that. The
bindable controls where probably not created as it was possible with
something that is from far in the past millenium.

However if you want to use a control as old as almost a T Ford, than make
it workable, but that has no purpose for others, who just use
DataBindable controls as they want to DataBind.

Cor
 
D

Dean Slindee

Cor,
The general points you make are well taken. However, the last time I
looked, that venerable Model T was still parked in the Microsoft VS2008
garage (toobox). That makes it a "current" control in my book. From that,
I would expect to be provided with a way to fill it from other "current"
technology, Linq in this case. All that is really needed is a way to Cast
the result of a Linq query back into an ADO.Net object (dataset or
datatable). Bingo, backward compatibility. It should be possible, because,
as you point out, Linq is based on ADO.Net.

If I could Cast a Linq query result into an ADO.Net object, then I could
replace legacy data acces layer code and stored procedures in existing
applications without much change to the GUI layer. Thus avoiding a major
rewrite, and honoring that old saying "don't fix what ain't broken".

So, how do we let MS know that they should take that one additional step to
provide backward compatibility?

Dean Slindee
 
D

Dean Slindee

Here is the Cast statement I would like to use:
Dim dt As DataTable
dt = CType(query.Cast(Of tblAdminAddressType)(),
System.Data.DataTable)

Perhaps my approach is in error, as today it produces this result:
Exception Type: System.InvalidCastException
Exception Message: Unable to cast object of type
'System.Data.Linq.DataQuery`1[Admin.tblAdminAddressType]' to type
'System.Data.DataTable'.
 
D

Dean Slindee

When I examine the Intellisense, before the query is executed, "query" looks
like a query string. After it is executed, "query" looks like an object
with rows. I was hoping/attempting to cast "query" to a datatable after
executing the "query".

I followed the link, which looked promising with code like:
Dim ds As New DataSet()
FillDataSet(ds)

Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = _
From order In orders.AsEnumerable() _
Where order.Field(Of DateTime)("OrderDate") > New DateTime(2001, 8, 1) _
Select order

' Create a table from the query.
Dim boundTable As DataTable = query.CopyToDataTable()

As much as the "query" above looks like LINQ syntax, it may not be in the
Linq namespace.
On my statements:
Dim dt As New DataTable
dt = query.CopyToDataTable()
the .CopyToDataTable() gives an Intellisense error message of:
"CopyToDataTable is not a member of 'System.Linq.IQueryable(Of
Admin.tblAdminAddressType)".

This looks like another good example of "close to backward compatibility".
If .CopyToDataTable resided within System.Linq, that might do it.

I would hope to be corrected on this...


Patrice said:
A LINQ query represents a query. You can't cast a query to a datatable
exactly as you couldn't cast a SqlCommand object to a DataTable...

Try :
http://msdn2.microsoft.com/en-us/library/bb396189.aspx (CopyToDataTable
method). It shoudl execute the query and create a datatable from the
results (never used it but remembered to have seen this).

---
Patrice



Dean Slindee said:
Here is the Cast statement I would like to use:
Dim dt As DataTable
dt = CType(query.Cast(Of tblAdminAddressType)(),
System.Data.DataTable)

Perhaps my approach is in error, as today it produces this result:
Exception Type: System.InvalidCastException
Exception Message: Unable to cast object of type
'System.Data.Linq.DataQuery`1[Admin.tblAdminAddressType]' to type
'System.Data.DataTable'.


Cor Ligthert said:
Dean,

The listview is in noway an AdoNet control, while Linq is only AdoNet as
it is for Linq to AdoNet

Why not use bindable controls as meant for those. The listview is an old
verhicle basicly for things as windows explorer and more like that. The
bindable controls where probably not created as it was possible with
something that is from far in the past millenium.

However if you want to use a control as old as almost a T Ford, than
make it workable, but that has no purpose for others, who just use
DataBindable controls as they want to DataBind.

Cor

"Dean Slindee" <[email protected]> schreef in bericht
Currently using a legacy Winform listview, that is filled by a function
from datatable(0) in a dataset. This works fine as the datatable has
column headers and data rows that correspond to listview headers and
listview rows.

Would like to use Linq to SQL to populate the same legacy WinForm
listview. The code below retrieves the IOrderedQuery object correctly:
Dim dc As New dcAdminDataContext
Dim query = From tblAdminAddressType In
dc.tblAdminAddressTypes _
Order By tblAdminAddressType.AddressTypeID
gvwAdminData.DataSource = query

The last statement fills a datagridview thru the .DataSource property.
Since, a listview has no .DataSource property, I am at an impasse as to
how to convert query to a datatable, or ? I do not want to specify the
data column names within each query nor declare a class for the query
structure. Any ideas?

Thanks in advance,
Dean S
 
C

Cor Ligthert [MVP]

Dean,

Something like this maybe, the rest you may do yourself.

\\\
Dim dct As New DataClasses1DataContext
Dim query = (From wh In dct.MyDataBaseTableName Where wh.CountryCode = "NL"
Select New With {wh.Code, wh.Description})
Dim dt As New DataTable
Dim dc As New DataColumn
Dim v = query.First
For Each info In (v.GetType().GetProperties())
dc = New DataColumn(info.Name)
dt.Columns.Add(dc)
Next
///
 
D

Dean Slindee

It appears that a .CopyToDataTable class existed in the Linq Betas, but the
class was cut due to lack of resources. If you follow the link provided by
Patrice: http://msdn2.microsoft.com/en-us/library/bb396189.aspx, you will
find the MSDN .CopyToDataTable documentation.

There is also another MSDN page that provides the .CopyToDataTable class
code (beta quality code, does not handle nullable types):
http://msdn2.microsoft.com/en-us/library/bb669096.aspx

At the end of this blog is the code needed to handle nullable types:
http://blogs.msdn.com/aconrad/archive/2008/01/11/pathetic-plea-for-help.aspx#7103127

Putting the pieces together and translating to VB, here is code that is
working for me. Caveat: it may be incomplete, but it does what I need it to
do thus far. Hopefully, MS will complete .CopyToDataTable in the next
release.

Calling code example:
Dim dt As New DataTable
Dim query = From tblAdminActivity In dc.tblAdminActivities _
Order By tblAdminActivity.ActivityID
dt = query.CopyToDataTable()

Insert into a module:
Imports System.Runtime.CompilerServices

Public Module CustomLINQtoDataSetMethods
<Extension()> _
Public Function CopyToDataTable(Of T)(ByVal source As IEnumerable(Of T))
As DataTable
Return New ObjectShredder(Of T)().Shred(source, Nothing, Nothing)
End Function

<Extension()> _
Public Function CopyToDataTable(Of T)(ByVal source As IEnumerable(Of T),
_
ByVal table As DataTable, _
ByVal options As LoadOption?) As
DataTable
Return New ObjectShredder(Of T)().Shred(source, table, options)
End Function
End Module

Create this class:
Option Strict Off
Option Explicit On
Imports System.Reflection

Public Class clsCopyToTable
Public Class ObjectShredder(Of T)
'fields
Private _fi As FieldInfo()
Private _ordinalMap As Dictionary(Of String, Integer)
Private _pi As PropertyInfo()
Private _type As Type

'constructor
Public Sub New()
Me._type = GetType(T)
Me._fi = Me._type.GetFields
Me._pi = Me._type.GetProperties
Me._ordinalMap = New Dictionary(Of String, Integer)
End Sub

Public Function ShredObject(ByVal table As DataTable, _
ByVal instance As T) As Object()
Dim fi As FieldInfo() = Me._fi
Dim pi As PropertyInfo() = Me._pi

If (Not instance.GetType Is GetType(T)) Then
'if the instance is derived from T, extend the table schema
'and get the properties and fields.
Me.ExtendTable(table, instance.GetType)
fi = instance.GetType.GetFields
pi = instance.GetType.GetProperties
End If

'add the property and field values of the instance to an array.
Dim values As Object() = New Object(table.Columns.Count - 1) {}
Dim f As FieldInfo
For Each f In fi
values(Me._ordinalMap.Item(f.Name)) = f.GetValue(instance)
Next

Dim p As PropertyInfo
For Each p In pi
values(Me._ordinalMap.Item(p.Name)) = p.GetValue(instance,
Nothing)
Next

'return the property and field values of the instance.
Return values
End Function


' Summary: Loads a DataTable from a sequence of objects.
' source parameter: The sequence of objects to load into the
DataTable.</param>
' table parameter: The input table. The schema of the table must
match that
' the type T. If the table is null, a new table is
created
' with a schema created from the public properties
and fields
' of the type T.
' options parameter: Specifies how values from the source sequence
will be applied to
' existing rows in the table.
' Returns: A DataTable created from the source sequence.

Public Function Shred(ByVal source As IEnumerable(Of T), _
ByVal table As DataTable, _
ByVal options As LoadOption?) As DataTable

'load the table from the scalar sequence if T is a primitive type.
If GetType(T).IsPrimitive Then
Return Me.ShredPrimitive(source, table, options)
End If

'create a new table if the input table is null.
If (table Is Nothing) Then
table = New DataTable(GetType(T).Name)
End If

'initialize the ordinal map and extend the table schema based on
type T.
table = Me.ExtendTable(table, GetType(T))

'enumerate the source sequence and load the object values into
rows.
table.BeginLoadData()
Using e As IEnumerator(Of T) = source.GetEnumerator
Do While e.MoveNext
If options.HasValue Then
table.LoadDataRow(Me.ShredObject(table, e.Current),
options.Value)
Else
table.LoadDataRow(Me.ShredObject(table, e.Current), True)
End If
Loop
End Using
table.EndLoadData()

'return table.
Return table
End Function


Public Function ShredPrimitive(ByVal source As IEnumerable(Of T), _
ByVal table As DataTable, _
ByVal options As LoadOption?) As
DataTable
'create a new table if the input table is null.
If (table Is Nothing) Then
table = New DataTable(GetType(T).Name)
End If
If Not table.Columns.Contains("Value") Then
table.Columns.Add("Value", GetType(T))
End If

'enumerate the source sequence and load the scalar values into
rows.
table.BeginLoadData()
Using e As IEnumerator(Of T) = source.GetEnumerator
Dim values As Object() = New Object(table.Columns.Count - 1) {}
Do While e.MoveNext
values(table.Columns.Item("Value").Ordinal) = e.Current
If options.HasValue Then
table.LoadDataRow(values, options.Value)
Else
table.LoadDataRow(values, True)
End If
Loop
End Using
table.EndLoadData()

'return table.
Return table
End Function

Public Function ExtendTable(ByVal table As DataTable, _
ByVal type As Type) As DataTable
'extend the table schema if the input table was null or if the
value
'in the sequence is derived from type T.
Dim f As FieldInfo
Dim p As PropertyInfo

Try
For Each f In type.GetFields
If Not Me._ordinalMap.ContainsKey(f.Name) Then
Dim dc As DataColumn

'add the field as a column in the table if it doesn't
exist already.
dc = IIf(table.Columns.Contains(f.Name),
table.Columns.Item(f.Name), table.Columns.Add(f.Name, f.FieldType))

'add the field to the ordinal map.
Me._ordinalMap.Add(f.Name, dc.Ordinal)
End If

Next

For Each p In type.GetProperties()
If Not _ordinalMap.ContainsKey(p.Name) Then
'add the property as a column in the table if it doesn't
exist already.
Dim colType As Type = p.PropertyType
If (colType.IsGenericType) _
AndAlso (colType.GetGenericTypeDefinition() Is
GetType(Nullable(Of ))) Then
colType = colType.GetGenericArguments()(0)
End If
Dim dc As DataColumn
dc = IIf(table.Columns.Contains(p.Name),
table.Columns(p.Name), table.Columns.Add(p.Name, colType))
Me._ordinalMap.Add(p.Name, dc.Ordinal)
End If
Next

Catch exc As Exception
'your exception handler
Finally
End Try

'return table
Return table
End Function

End Class
End Class
 
D

Dean Slindee

Looks like we were both working on this one yesterday. I thank you very
much for your effort. See my reply to my original post for a pseudo-MS
solution.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top