Type of Generic seems worthless - vb.net

Please see the code below:
Public Function ExecuteDynamicQuery(Of T As New)(ByVal sql As String, ByVal type As T) As List(Of T) Implements IGenie.ExecuteDynamicQuery
Dim iConnectionBLL As iConnectionBLL = New clsConnectionBLL
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
Dim list As List(Of T) = New List(Of T)
While rdr.Read()
Dim hello As New T
Dim method As MethodInfo = GetType(clsType).GetMethod("PopulateDataReader")
method.Invoke(hello, New Object() {rdr})
list.Add(hello)
End While
Return list
End Using
End Function
Is there a way of executing the SQL statement above without passing in type as an arguement. It seems a bit pointless - the only reason it is there is to let the function know the type of the generic.

Well you can change the method to not have the second parameter:
Public Function ExecuteDynamicQuery(Of T As New)(ByVal sql As String) As List(Of T) Implements IGenie.ExecuteDynamicQuery
However:
You'd need to change IGenie as well
The caller would then need to explicitly specify the type argument, instead of letting the compiler infer it on the basis of the argument (which would no longer be present)

Related

How to get IEnumerable(Of DataRow) From DataTable query

All the examples I have found of complex grouping of DataTable results that use linq query commands look to have no problems getting an IEnumerable(Of DataRow) object as the result.
However I seem to only get a AnonymousType Enumerator return that I cannot cast to DataTable.
I have workarounds, but would prefer to convert the results to a DataTable, as it looks possible and I may be doing something wrong.
It's a simple table with Many ClientID and ClientName columns and other columns with login timestamps.
Dim dtMatrix As DataTable = New DataTable()
... (populate DataTable)
Dim qClients = From row In dtMatrix
Group row By client = New With {Key .ClientID = row("ClientID"), Key .ClientName = row("ClientName")} Into Group
Select New With {Key .ClientID = client.ClientID, Key .ClientName = client.ClientName}
This returns the generic Enumerator result, however
Dim qClients As IEnumerable(Of DataRow) = From row In dtMatrix
Group row By client = New With {Key .ClientID = row("ClientID"), Key .ClientName = row("ClientName")} Into Group
Select New With {Key .ClientID = client.ClientID, Key .ClientName = client.ClientName}
Throws an exception
Unable to cast object of type... to type
'System.Collections.Generic.IEnumerable`1[System.Data.DataRow]'.
I will be happy to paste the whole error message if it will add more clarity.
My base assumption is that the DataTable should allow the cast to occur inherently as it is the object being queried. However this does not seem to be the case. Have I constructed my query incorrectly? (Framework 4.6.2)
You can use OfType on the Rows property of the DataTable:
Dim dtMatrix As DataTable = New DataTable()
'' Populate code goes here...
Dim dtRows As IEnumerable(Of DataRow) = dtMatrix.Rows.OfType(Of DataRow)()
The Rows property returns a DataRowCollection, that implements (through inheritance) the IEnumerable interface but not the IEnumerable(Of T) interface, that's why you can't use most of linq over it directly.
The following extension uses Reflection to create a new DataTable and create DataColumns in it that match the properties and fields of the type passed in. In general, if you are creating anonymous types in LINQ, you can't just convert to a DataRow which must be tied to a DataTable which must already have matching columns. I went ahead and wrote a second extension to DataTable that adds an IEnumerable<T> with matching field/property names to it.
Public Module Ext
<Extension()>
Public Function GetValue(member As MemberInfo, srcObject As Object) As Object
If TypeOf member Is FieldInfo Then
Return DirectCast(member, FieldInfo).GetValue(srcObject)
ElseIf TypeOf member Is PropertyInfo Then
Return DirectCast(member, PropertyInfo).GetValue(srcObject)
Else
Throw New ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", Nameof(member))
End If
End Function
<Extension()>
Public Function GetMemberType(member As MemberInfo) As Type
If TypeOf member Is FieldInfo Then
Return DirectCast(member, FieldInfo).FieldType
ElseIf TypeOf member Is PropertyInfo Then
Return DirectCast(member, PropertyInfo).PropertyType
ElseIf TypeOf member Is EventInfo Then
Return DirectCast(member, EventInfo).EventHandlerType
Else
Throw New ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or EventInfo", Nameof(member))
End If
End Function
<Extension()>
Public Function ToDataTable(Of T)(rows As IEnumerable(Of T)) As DataTable
Dim dt = New DataTable
If (rows.Any()) Then
Dim rowType = rows.First().GetType()
Dim memberInfos = rowType.GetProperties.Cast(Of MemberInfo)().Concat(rowType.GetFields).ToArray()
For Each info In memberInfos
dt.Columns.Add(New DataColumn(info.Name, info.GetMemberType()))
Next
For Each r In rows
dt.Rows.Add(memberInfos.Select(Function (i) i.GetValue(r)).ToArray())
Next
End If
Return dt
End Function
<Extension()>
Public Function AddObjects(Of T)(dt As DataTable, rows As IEnumerable(Of T))
If (rows.Any()) Then
Dim rowType = rows.First().GetType()
Dim memberInfos = rowType.GetProperties().Cast(Of MemberInfo)().Concat(rowType.GetFields()).ToArray()
For Each r In rows
Dim newRow = dt.NewRow()
For Each memberInfo In memberInfos
newRow(memberInfo.Name) = memberInfo.GetValue(r)
Next
dt.Rows.Add(newRow)
Next
End If
Return dt
End Function
End Module
Note that I write in C# and translated this from my C# extension. It is untested but compiles.
Using the extension, you should be able to get a DataTable from your qClients by:
Dim dtClients = qClients.ToDataTable()

Defining the type of a List in vb.net at runtime

I'm creating a class at run time using typebuilder and after I create this class I want to define its type for a list like
dim fooList as new List(of DynamicClassName)
Since this doesn't exist at compile time of course it throws an error. When I generate this type I return the type so I can't do something like
dim newType = createNewType(foobar)
dim fooList as new List(of getType(newType))
How do I assign the type of a List at runtime?
You can create a List(Of T), but AFAIK you won't be able to cast it to a typed object. I've used the String type in the following example.
Dim list As Object = Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {GetType(String)}))
Debug.WriteLine((TypeOf list Is List(Of String)).ToString())
Output
True
So in your case it would look like this:
Dim newType = createNewType(foobar)
'Creates a List(Of foobar):
Dim list As IList = Ctype(Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {newType})), IList)
'Creates a BindingList(Of foobar):
Dim bindingList As IBindingList = Ctype(Activator.CreateInstance(GetType(BindingList(Of )).MakeGenericType(New Type() {newType})), IBindingList)
This does not answer your question, but may solve your problem.
Another option would be to use an ArrayList (for which you don't have to assign a type). You can see the details here: http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=vs.110).aspx.
Here is a basic example:
Dim anyArrayList as new ArrayList()
anyArrayList.add("Hello")
anyArrayList.add("Testing")

Yield with delegate

Please see the code below:
Public Iterator Function Read(Of T)(ByVal sql As String, ByVal make As Func(Of IDataReader, T), ParamArray ByVal parms() As Object) As IEnumerable(Of T)
Using connection = CreateConnection()
Using command = CreateCommand(sql, connection, parms)
Using reader = command.ExecuteReader()
Do While reader.Read()
Yield make(reader) --line 7
Loop
End Using
End Using
End Using
End Function
Private Shared Make As Func(Of IDataReader, Member) =
Function(reader) _
New Member() With {
.MemberId = Extensions.AsId(reader("MemberId")),
.Email = Extensions.AsString(reader("Email")),
.CompanyName = Extensions.AsString(reader("CompanyName")),
.City = Extensions.AsString(reader("City")),
.Country = Extensions.AsString(reader("Country"))
}
Please see line 7. Make populates an object of type Member with values from the data reader row. I have read the following documentation: http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx. The documentation does not seem to explain what happens when you use a delegate i.e. Yield make(datareader), rather than Yield return datareader. Is control passed back to the calling function as well as the delegate (Make)?
Make(reader) is a shortcut for Make.Invoke(reader). I.e., you
invoke the delegate, passing reader as a parameter, which yields a value of type Member.
Then, you return that value using Yield.
It is equivalent to :
...
Do While reader.Read()
Dim myMember As Member = make(reader)
Yield myMember
Loop
...
PS: If you get a compile-time error in your code (you don't say so in your question): This is due to the fact that your method is declared to return an IEnumerable(Of T), when in fact it returns an IEnumerable(Of Member).

Calling Generic function + lambda

I have this function in vb.net that I converted from C# for a project I'm working on.
Private Function GetAllFactory(Of T)(ByVal ctor As Construct(Of T)) As List(Of T)
'TODO: Data Access stuff
Dim ds As New DataSet()
Dim entities = New List(Of T)()
For Each dataRow As DataRow In ds.Tables(0).Rows
Dim entity As T = ctor(dataRow)
entities.Add(entity)
Next
Return entities
End Function
and the following delegate
Private Delegate Function Construct(Of T)(ByVal dataRow As DataRow) As T
I tried converting the code to call the function from C# to vb.net
Return GetAllFactory(Of MyType)(row >= New MyType(row))
the above line doesn't work. I'm sort of stuck. I haven't used lambda much in C# and even less in vb.net.
MyType constructor:
Public Sub New(ByVal dataRow As DataRow)
.
.
.
End Sub
Any suggestions on how to call the GetAllFactory?
You use the Function keyword in VB to write a lambda expression:
Return GetAllFactory(Of MyType)(Function(row) New MyType(row))
Note that >= is a comparison operator while => is the lamda operator in C#. VB might give you some unexpected error message for code using => as it accepts that as an undocumented alias for the >= operator.
VB.Net lambda expressions look like this:
Return GetAllFactory(Of MyType)(Function(row) New MyType(row))

Exception when casting from concrete class to interface in LINQ query

I have a class SomeClass, which can populate itself from a datarow in it's constructor. This class implements IInterface. However, when I execute the code below:
Dim fpQuery As IEnumerable(Of IInterface) = _
From dr As DataRow In DataLayer.SomeMethodToGetADataTable.AsEnumerable _
Select New SomeClass(dr)
I get the error
Unable to cast object of type
'System.Data.EnumerableRowCollection`1[Classes.SomeClass]'
to type
'System.Collections.Generic.IEnumerable`1[Interfaces.IInterface]'
I should probably add that the following code works fine.
Dim fpQuery As IEnumerable(Of SomeClass) = _
From dr As DataRow In DataLayer.SomeMethodToGetADataTable.AsEnumerable _
Select New SomeClass(dr)
As does the simple cast
Dim myInterface As IInterface = New SomeClass(myDataRow)
Any ideas?
EDIT :
Jon Skeet got it spot on. I used the following code and it worked perfectly.
Dim fpQuery2 As IEnumerable(Of IInterface) = fpQuery.Cast(Of IInterface)
You're running into a lack of variance in generics. To put it in a simpler example, you can't treat IEnumerable(Of String) as IEnumerable(Of Object).
The simplest thing would probably be to add a call to Cast(Of TResult).