I've recently implemented reflection to replace the more tedious aspects of our data retrieval from a SQL database. The old code would look something like this:
_dr = _cmd.ExecuteReader (_dr is the SQLDataReader)
While _dr.Read (_row is a class object with public properties)
_row.Property1 = Convert.ToInt16(_dr("Prop1"))
_row.Property2 = Convert.ToInt16(_dr("Prop2"))
_row.Property3 = Convert.ToInt16(_dr("Prop3"))
If IsDBNull(_dr("Prop4")) = False Then _row.Prop4 = _dr("Prop4")
...
Since my code base has a lot of functionality like this, reflection seemed like a good bet to simplify it and make future coding easier. How to assign datareader data into generic List ( of T ) has a great answer that was practically like magic for my needs and easily translated into VB. For easy reference:
Public Shared Function GenericGet(Of T As {Class, New})(ByVal reader As SqlDataReader, ByVal typeString As String)
'Dim results As New List(Of T)()
Dim results As Object
If typeString = "List" Then
results = New List(Of T)()
End If
Dim type As Type = GetType(T)
Try
If reader.Read() Then
' at least one row: resolve the properties
Dim props As PropertyInfo() = New PropertyInfo(reader.FieldCount - 1) {}
For i As Integer = 0 To props.Length - 1
Dim prop = type.GetProperty(reader.GetName(i), BindingFlags.Instance Or BindingFlags.[Public])
If prop IsNot Nothing AndAlso prop.CanWrite Then
props(i) = prop
End If
Next
Do
Dim obj = New T()
For i As Integer = 0 To props.Length - 1
Dim prop = props(i)
If prop Is Nothing Then
Continue For
End If
' not mapped
Dim val As Object = If(reader.IsDBNull(i), Nothing, reader(i))
If val IsNot Nothing Then SetValue(obj, prop, val)
Next
If typeString = "List" Then
results.Add(obj)
Else
results = obj
End If
Loop While reader.Read()
End If
Catch ex As Exception
Helpers.LogMessage("Error: " + ex.Message + ". Stacktrace: " + ex.StackTrace)
End Try
Return results
End Function
The only caveat with this is that it is somewhat slower.
My question is how to optimize. Sample code I find online is all in C# and does not convert neatly into VB. Scenario 4 here seems like exactly what I want, but converting it to VB gives all kinds of errors (Using CodeFusion or converter.Telerik.com).
Has anyone done this in VB before? Or can anyone translate what's in that last link?
Any help's appreciated.
Couple ideas for you.
Don't use the DataReader when reading ALL records at once, it is slower than using a DataAdapter.
When you use the DataAdapter to fill a DataSet, you can iterate through the rows and columns which does NOT use reflection and will be much faster.
I have a program I created (and many other programmers do this too) that generate the code from a database for me. Each table and row is a class that is specifically named an generated in such a way that I can use intellisense and prevents many run-time errors by making them compile-time errors when data changes. This is very much like the EntityFramework but lighter because it fits MY specific needs.
Related
I have a question about life cycle for vb.net object
what's the difference about there two functions:
function 1:
Private Function MyF() As Integer
Using c As New cMyclass
If c.somework() = 1 Then Return 1
End Using
Return 0
End Function
function 2:
Private Function MyF() As Integer
Dim c As New myClass
If c.somework() = 1 Then Return 1
Return 0
End Function
what is the better solution?
The documentation gives you a really good explanation. Even with examples.
This
Using resource As New resourceType
' Insert code to work with resource.
End Using
Is the same as this
' For the acquisition and disposal of resource, the following
' Try construction is equivalent to the Using block.
Dim resource As New resourceType
Try
' Insert code to work with resource.
Finally
If resource IsNot Nothing Then
resource.Dispose()
End If
End Try
Disposing objects is something that always need to be done when possible.
I am a relative VB.Net noob, and I'm learning by doing. I'm sure what I'm about to ask has been asked 10^19 times before, but whatever code word it's under, I can't figure out how to Google it. Here goes...
We have an object model with one or more Project objects that consists of several Tables, which contain Rows which have Fields. This leads to code all over our apps that looks something like this...
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
In our application, if any of the objects in this "call chain" does not exist, the correct value for theColor should be Nothing*. This is just like Excel - the value of an empty cell in an empty row is vbnull, not "Excel has crashed".
This is not how VB.Net works, however. If, for instance, Rows(22) does not exist, the Fields(3) is called on Nothing and an exception is thrown. My question is how to best deal with this...
1) I could check each value to see it it's not Nothing, but that leads to horrible amounts of code...
If Projects(1) IsNot Nothing AndAlso Projects(1).Tables(5) AndAlso...
We have thousands of these, the amount of code this would require would be enormous.
2) I could wrap all accessors in try/catch, but that's really just a different sort of (1)
3) I could have a special instance of each object that has empty values. So, for instance, Tables(5) returns NullTable and Row(22) returns NullRow. But this means I have to always use accessor methods, I can't just look in the underlying arrays. You're probably saying good, but sadly a lot of our older code does just that (yes, duh).
4) Something else entirely? Am I missing some magic that everyone other than me knows?
You could have a function called GetField
Instead of
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
You would have
Dim theColor As String = GetField(1, 5, 22, 3)
Function GetField(ByVal projectIndex As Integer, ByVal tableIndex As Integer, ByVal rowIndex As Integer, byVal fieldIndex As Integer) As Object
If Projects(projectIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex) Is Nothing Then
Return Nothing
End If
Return Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex)
End Function
But I got to say... what you are doing looks sloppy. You should think of using classes with properties.
You could concoct a "project manager" of sorts. It is hard to know how viable this is from the post, but something like:
Class ProjectManager
Public Property CurrentProject As ProjectThing
Public Property CurrentTable As Integer
Get
Return tblIndex
End Get
Set
If CurrentProject IsNot Nothing Then
If CurrentProject.Tables(value) Is Nothing Then
Throw exception
Else
tblIndex = value
End If
End If
End Set
End Property
' etc
Then use it to store the current reference to the project and/or table. All the Is Not Nothings can be embedded there.
Private myPrj As New ProjectManager
...
myPrj.CurrentProject = Project(1)
myPrj.CurrentTable = 5
With the "manager" doing all the checking for everyone, you dont have to (much):
Dim theColor As String = myPrj.Rows(22).Fields(3)
or
Dim theColor As String = myPrj.GetRowValue(22, 3)
What it would really be doing is storing a validated object references for you, and testing those not worth storing. It could go as deep as you needed. Even if all it really did was encapsulate those Is Nothing/Is Not Nothing tests, it might add some value.
Public Function GetRowValue(r As Integer, f as Integer) As Something
If r < CurrentProject.Tables(tblIndex).Rows.Count AndAlso
f < CurrentProject.Tables(tblIndex).Rows(r).Fields.Count Then
Return ...
'or
Public Function GetRowValue(Of T)(r As Integer, f as Integer) As T
Once a project is specified, it could expose helpful properties like TableCount. It is possible that the data represented by some of the most used, most important Const definitions, could be exposed as properties:
' swap a property interface for "3"
Dim theColor As String = myPrj.FooColor(22)
You can handle the exception:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch
End Try
If you want to do it 'properly' you should specify the exceptions you are guarding against:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch ex As IndexOutOfRangeException
Catch ex As NullReferenceException
End Try
I'm trying to get this LINQ expression:
Result = Result.Where(Function(Row) _WhereExpressions(0).InElements.Contains(Convert.ToString(Row(0))))
I have this code for it:
convertMethod = GetType(System.Convert).GetMethod("ToString", New Type() {GetType(Object)})
containsMethod = GetType(System.Collections.Generic.List(Of String)).GetMethod("Contains", New Type() {GetType(String)})
Dim listParameter = Expression.Parameter(GetType(List(Of String)), "_WhereExpressions(0).InElements")
expr = Expression.Call(whereMethod, Result.AsQueryable.Expression,
Expression.Lambda(Expression.Call(listParameter, containsMethod,
Expression.Call(convertMethod, Expression.ArrayAccess(rowParameter, Expression.Constant(index)))), rowParameter))
I get the desired expression, but if I compile, I get the error:
variable '_WhereExpressions(0).InElements' of type 'System.Collections.Generic.List`1[System.String]' referenced from scope '', but it is not defined
The _WhereExpressions(0).InElements is of course declared.
How can I fix it?
Thanks.
EDIT: here are all the declarations:
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Where").MakeGenericMethod(GetType(Object()))
Dim convertMethod As MethodInfo = Nothing
Dim containsMethod As MethodInfo = Nothing
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
The _WhereExpressions(0).InElements is a simple list of string, like this here:
Dim idlist As New List(Of String)
idlist.Add("1")
idlist.Add("2")
I read the linked post, but I can't really figure out, how I should solve my problem.
Expression trees have a lot capability, but looks a bit difficult for me.
EDIT2:
This is an example, what exactly I would like to achieve. Just copy and paste in vs:
Dim dt As New DataTable
dt.Columns.Add("f1", Type.GetType("System.String"))
dt.Columns.Add("f2", Type.GetType("System.Int32"))
For i = 0 To 100
dt.Rows.Add(i.ToString, i * 2)
Next
Dim indexes As New List(Of Integer)
indexes.Add(0)
indexes.Add(1)
Dim lst As New List(Of String)
lst.Add("10")
lst.Add("11")
Dim datarows As New List(Of DataRow)
For i = 0 To dt.Rows.Count - 1
datarows.Add(dt.Rows(i))
Next
Dim result As IEnumerable(Of Object())
result = datarows.Select(Function(row) indexes.Select(Function(index) row(index)).ToArray)
'I would like this as an expression:
result = result.Where(Function(row) lst.Contains(Convert.ToString(row(0))))
EDIT3: I got it:
Dim lst As Expression = Expression.Constant(list, GetType(System.Collections.Generic.List(Of String)))
It is hard to replicate code without knowing the full variables. I think your mistake though is in your understanding of Expression.Parameter. This is mainly used as a way to pass explicit parameters into the lambda: In your example, Row is a good example for when Expression.Parameter should be used. _WhereExpressions isn't an explicit parameter, it is a variable that I assume is in scope that you want to create a closure around.
You should also note that the second variable of the Expression.Parameter method is optional, and for debug purposes only: If you changed it to say: Expression.Parameter(GetType(List(Of String)), "Nonsense.nonsense"), you would probably see the error message change accordingly.
It sounds like you're trying to introduce a closure around _WhereExpressions. Using raw expression trees, this is fairly hard to do. The closest thing to an easy way to do this is to wrap Expression.Constant around _WhereExpressions.InElements. However, you're going to run into trouble if you're executing the compiled expression when _WhereExpressions is out of scope. See Issue with closure variable capture in c# expression.
Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class
Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post