【问题标题】:VB.NET - Returning dynamic class value from functionVB.NET - 从函数返回动态类值
【发布时间】:2018-03-28 06:18:22
【问题描述】:

我遇到的问题在以下代码中。该函数适用于 ControlCaption 实体类,但我必须为我需要从中读取的每个其他实体类复制并粘贴它,然后将 ControlCaption 更改为新的类名(我讨厌在不需要时重复代码)。因为还有一个更新/插入值的功能,我曾尝试将公共对象设置为等于该类(即 Public SomeClass as Object = New ControlCaption),但 IDE 抱怨它。

代码如下:

Public Function SelectList(Optional ByVal SQLString As String = "SELECT * FROM " & DatabaseName) As List(Of ControlCaption)
    Dim strConnectionString As String = ConnectionString()      '--Creates the connection string
    Dim intLineNumber As Integer = 0
    Dim InfoList As New List(Of ControlCaption) '--List of classes being returned

    Try
        If DatabaseType = SQLServer Then    '--User indicated previously that a SQL Server was being used
            Using myConnection As New SqlClient.SqlConnection(strConnectionString)
                myConnection.Open()
                Using myCommand As New SqlClient.SqlCommand(SQLString, myConnection)
                    Using myReader = myCommand.ExecuteReader
                        Do While myReader.Read
                            InfoList.Add(New ControlCaption()) '--Add a new element to the list

                            '--The next couple of lines need System.Reflection to be imported to work
                            '--The following for statement goes through each property in a class and assigns
                            '--the database value with the same name to the property. (Required to use entity classes)
                            Dim TheObject As New ControlCaption
                            Dim TheType As Type = TheObject.GetType()
                            Dim Properties() As PropertyInfo = TheType.GetProperties()
                            For Each Prop As PropertyInfo In properties
                                Try
                                    If UCase(Prop.Name) <> "ITEM" Then
                                        If TypeOf (myReader.Item(Prop.Name)) Is DateTime Then
                                            '--Convert value to date
                                            InfoList(InfoList.Count - 1).Item(Prop.Name) = CDate(IIf((myReader.Item(Prop.Name).ToString & String.Empty) = vbNullString, "1/1/1900", myReader.Item(Prop.Name).ToString))
                                        Else
                                            InfoList(InfoList.Count - 1).Item(Prop.Name) = myReader.Item(Prop.Name).ToString & String.Empty
                                        End If
                                    End If
                                Catch ex As Exception

                                End Try
                            Next
                        Loop
                    End Using
                End Using

                If myConnection.State <> ConnectionState.Closed Then
                    myConnection.Close()
                End If

                Return InfoList
            End Using
        ElseIf DatabaseType = AccessDatabase Then
            Using myConnection As New OleDb.OleDbConnection(strConnectionString)
                myConnection.Open()
                Using myCommand As New OleDb.OleDbCommand(SQLString, myConnection)
                    Using myReader = myCommand.ExecuteReader
                        Do While myReader.Read
                            InfoList.Add(New ControlCaption())

                            Dim TheObject As New ControlCaption
                            Dim TheType As Type = TheObject.GetType()
                            Dim Properties() As PropertyInfo = TheType.GetProperties()
                            For Each Prop As PropertyInfo In properties
                                Try
                                    If UCase(Prop.Name) <> "ITEM" Then
                                        If TypeOf (myReader.Item(Prop.Name)) Is DateTime Then
                                            InfoList(InfoList.Count - 1).Item(Prop.Name) = CDate(IIf((myReader.Item(Prop.Name).ToString & String.Empty) = vbNullString, "1/1/1900", myReader.Item("DateModified").ToString))
                                        Else
                                            InfoList(InfoList.Count - 1).Item(Prop.Name) = myReader.Item(Prop.Name).ToString & String.Empty
                                        End If
                                    End If
                                Catch ex As Exception

                                End Try
                            Next
                        Loop
                    End Using
                End Using

                If myConnection.State <> ConnectionState.Closed Then
                    myConnection.Close()
                End If

                Return InfoList
            End Using
        Else
            Return Nothing
        End If
    Catch ex As Exception
        Return Nothing
    End Try
End Function

我觉得必须有办法让这个函数接受任何类,但我还没有想出办法。

有人对如何进行这项工作有任何建议吗?任何改进建议也会有所帮助。

谢谢。


对于任何感兴趣的人,这里是生成的最终代码:

Public Function SelectList(Of EntityClass As New)(Optional ByVal SQLString As String = "SELECT * FROM " & DatabaseName) As List(Of EntityClass)
    Try
        Dim Entities As New List(Of EntityClass)()
        Using Connection As New SqlClient.SqlConnection(ConnectionString)
            Using Command As New SqlClient.SqlCommand(SQLString, Connection)
                Connection.Open()
                Using Reader = Command.ExecuteReader()
                    Dim Properties = GetType(EntityClass).GetProperties()
                    Do While Reader.Read
                        Dim Entity = CreateEntity(Of EntityClass)(Reader, Properties)
                        Entities.Add(Entity)
                    Loop
                End Using
            End Using
        End Using

        Return Entities
    Catch ex As Exception
        MsgBox(Err.Description)
        Return Nothing
    End Try
End Function

Private Function CreateEntity(Of PassedEntity As New)(ByVal reader As DbDataReader, ByVal properties As PropertyInfo()) As PassedEntity
    Dim Entity As New PassedEntity()
    For Each _Property As PropertyInfo In properties
        If _Property.Name.ToUpper() = "ITEM" Then Continue For

        Dim value = reader.Item(_Property.Name)
        _Property.SetValue(Entity, value, Nothing)
    Next
End Function

【问题讨论】:

  • 使其通用
  • 你确定你的方法中带有默认值的可选参数被编译了吗?我担心默认值应该是常量值
  • 类在 VB.NET 中对我来说相当新。感谢您的帮助。

标签: vb.net list class


【解决方案1】:

似乎是具有“新”约束的泛型的良好候选者。
“新”约束将提供创建给定类型的新实例的可能性。

Public Function SelectList(Of T As New)(query As String ) As List(Of T)
    Dim list As New List(Of T)()

    ' Somewhere in the rows loop
        Dim item = New T()
        ' Fill properties
        list.Add(item)

    Return list 
End Function

如果您想避免重复代码,则可以使用DbDataReader 类删除一些重复,该类是SqlDataReaderOleDbDataReader 的基类。

Private Function CreateEntity<(Of T As New)(reader As DbDataReader, 
                                            properties As PropertyInfo()) As T
    Dim entity As New T()
    For Each property As PropertyInfo In properties
        If property.Name.ToUpper() = "ITEM" Then Continue For

        Dim value = reader.Item(property.Name)
        ' Should work if property type is correspondent .NET type of sql column type
        ' For null values property should be of Nullable type
        property.SetValue(entity, value)
        ' If not - use your logic
    Next       
End Function

然后你可以在读取逻辑中使用这个函数。请注意,您不需要在每个循环中运行 Type.GetProperties() - 为类型获取一次并重用相同的集合

Public Function SelectList(Of T As New)(query As String ) As List(Of T)
    Dim entities As New List(Of T)()

    Using connection As New SqlConnection(connectionString)
        Using command As new SqlCommand(query, connection)
            connection.Open()
            Using reader = command.ExecuteReader()
                Dim properties = GetType(T).GetProperties()
                Do While reader.Read()
                    Dim entity = CreateEntity(reader, properties)
                    list.Add(entity)
                Loop
            End Using
        ' No need for closing connection explicitly - End Using - will handle it
        End Using
    End Using

    Return entities 
End Function

【讨论】:

  • 感谢您的帮助。我认为我在 VB6 和 VB.NET 之间的转换做得很好,但似乎有很多东西我错过了,需要尽快学习。在你、Fabio 和 DJV 的一些事情之间,我能够让它发挥作用。代码大小的减少是惊人的。多谢你们。 code
【解决方案2】:

使其通用。只需返回您可以实例化的对象列表。

它会被这样调用:

Dim myFoo As List(Of Foo) = SelectList(Of Foo)("Select * FROM Foo")

这是新代码

Public Function SelectList(Of T As New)(Optional ByVal SQLString As String = "SELECT * FROM " & DatabaseName) As List(Of T)
    Dim InfoList As New List(Of T) '--List of classes being returned
    Dim connectionType = If(DatabaseType = SQLServer, connectionType.MsSql, connectionType.MsAccess)
    Try
        Using myConnection = getConnection(connectionType, ConnectionString())
            myConnection.Open()
            Using myCommand = getCommand(connectionType, SQLString, myConnection)
                Using myReader = myCommand.ExecuteReader
                    Do While myReader.Read
                        Dim TheObject As New T()
                        InfoList.Add(TheObject) '--Add a new element to the list
                        Dim TheType As Type = TheObject.GetType()
                        Dim Properties() As PropertyInfo = TheType.GetProperties()
                        For Each Prop As PropertyInfo In Properties
                            Try
                                If UCase(Prop.Name) <> "ITEM" Then
                                    Prop.SetValue(TheObject, Convert.ChangeType(myReader.Item(Prop.Name), Prop.PropertyType), Nothing)
                                End If
                            Catch
                            End Try
                        Next
                    Loop
                End Using
            End Using
            Return InfoList
        End Using
    Catch ex As Exception
        Return Nothing
    End Try
End Function

哦,等等,这里也发生了很多其他事情!我无法忍受你复制函数几乎和复制查询代码一样多的想法,所以我做了一个循环。使用这些函数来创建 IDbConnectionIDbCommand 对象,因为其余代码是相同的(而且它们实际上只是调用这些接口可用的函数)。

Private Function getConnection(connectionType As ConnectionType, connectionString As String) As IDbConnection
    Select Case connectionType
        Case ConnectionType.MsSql
            Return New SqlClient.SqlConnection(connectionString)
        Case ConnectionType.MsAccess
            Return New OleDb.OleDbConnection(connectionString)
        Case Else
            Return Nothing
    End Select
End Function

Private Function getCommand(connectionType As ConnectionType, commandString As String, connection As IDbConnection) As IDbCommand
    Select Case connectionType
        Case ConnectionType.MsSql
            Return New SqlClient.SqlCommand()
        Case ConnectionType.MsAccess
            Return New OleDb.OleDbCommand()
        Case Else
            Return Nothing
    End Select
End Function

Private Enum ConnectionType
    MsSql
    MsAccess
End Enum

(如果您要添加另一种连接类型,只要实现这些接口就很简单)。

属性设置代码可以简化,假设日期为标准格式,只需一行即可设置所有属性

Prop.SetValue(TheObject, Convert.ChangeType(myReader.Item(Prop.Name), Prop.PropertyType), Nothing)

此外,您可以看到没有理由在此处创建 两个 ControlCaption 对象...

最后,您不需要显式关闭连接,因为您已经正确地将它放在 using 块中。

【讨论】:

  • 你是绝对正确的。谢谢您的帮助。这也很好用。
【解决方案3】:

只需将函数参数设置为Object,即可传递任何对象类型。例如:

    Public Function SelectList(Optional ByVal SQLString As String = "SELECT * FROM " & DatabaseName) As List(Of Object)

【讨论】:

  • 感谢您的建议。这是我在寻求帮助之前尝试过的事情之一,但没有奏效。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-21
  • 1970-01-01
  • 2016-02-05
  • 2018-03-03
相关资源
最近更新 更多