【问题标题】:Issues on loop for retrieve the dynamic sql parameter values from dynamic textboxes从动态文本框中检索动态 sql 参数值的循环问题
【发布时间】:2020-12-05 05:20:34
【问题描述】:

全部。我是一个新的 VB.NET 初学者。我面临如何从动态文本框中传递动态 SQL 参数值来搜索数据的问题。我添加了对动态文本框和标签的控制,并希望根据用户输入的动态文本框值搜索数据库表上的数据。目前,我只能从 1 个动态文本框值中搜索数据。

我希望用户输入的动态文本框中的所有值都可以检索,并根据用户输入的SQL参数变量名称和值搜索数据。

有人可以就如何解决这个问题给我一些解决方案吗?我在这个问题上坚持了几天。谢谢大家的帮助!

我尝试过的:

Private Sub FilterData()

    Dim count As Integer = 0

    'filterdata for radiobutton
    Try
        For Each TextBox As TextBox In grp2.Controls.OfType(Of TextBox)()
            For Each Label As Label In grp2.Controls.OfType(Of Label)()

                Using connection As New SqlConnection("connectionString")

                    'user key in the SQL statement
                    sql = TextBox1.Text
                    Dim sql2 As String
                    sql2 = sql
                    Dim sp1 As String() = sql.Split(New String() {"where"}, StringSplitOptions.None)
                    sql = sp1(0) & " where " & Label.Text & " = @parameter or " & Label.Text & " =@parameter"

                    If (TextBox.Text <> "") Then
                        count += 1
                        For j As Integer = 0 To count - 1
                            Using cmd As New SqlCommand(sql, connection)
                                cmd.Parameters.AddWithValue("@parameter", TextBox.Text)
                                'cmd.Parameters.Add("@parameter", SqlDbType.NVarChar, 20).Value = TextBox.Text
                                connection.Open()
                                Dim dt As New DataTable()
                                Dim reader As SqlDataReader
                                reader = cmd.ExecuteReader()
                                dt.Load(reader)
                                DataGridView1.DataSource = dt
                            End Using
                        Next
                    Else
                        GetData()
                    End If
                    'cmd.Dispose()
                    connection.Close()
                End Using
            Next
        Next

    Catch ex As Exception
        'MsgBox(ex.Message)
    End Try

End Sub

【问题讨论】:

    标签: vb.net loops dynamic sqlparameter


    【解决方案1】:

    首先,如果 LabelsTextBoxes 之间存在 1:1 对应关系,那么您不应该使用两个嵌套的 For Each 循环,因为这会将每个 Label 与每个和每个TextBox。您应该做的是创建数组,然后使用单个 For 循环来访问控件对:

    Dim labels = grp2.Controls.OfType(Of Label)().ToArray()
    Dim textBoxes = grp2.Controls.OfType(Of TextBox)().ToArray()
    
    For i = 0 To labels.getUpperBound(0)
        Dim label = labels(i)
        Dim textBox = textBoxes(i)
    
        '...
    Next
    

    至于构建 SQL 和添加参数,我倾向于这样做:

    Dim labels = grp2.Controls.OfType(Of Label)().ToArray()
    Dim textBoxes = grp2.Controls.OfType(Of TextBox)().ToArray()
    Dim criteria As New List(Of String)
    Dim command As New SqlCommand
    
    For i = 0 To labels.getUpperBound(0)
        Dim label = labels(i)
        Dim textBox = textBoxes(i)
        Dim parameterName = "@" & label.Text.Replace(" ", "_")
    
        criteria.Add($"[{label.Text}] = {parameterName}")
        command.Parameters.AddWithValue(parameterName, textBox.Text)
    Next
    
    Dim sql = "SELECT * FROM MyTable"
    
    If criteria.Any() Then
        sql &= " WHERE " & String.Join(" OR ", criteria)
    End If
    
    command.CommandText = sql
    

    【讨论】:

    • 嗨,我试过你的代码,但有语法错误:criteria.Add($"[{label.Text}] = {parameterName}")
    • @aliceteh,你用的是什么版本的VS?该行使用字符串插值,这基本上是对复杂格式的内置语言支持。我想它是在 2015 年首次支持的,尽管它可能是 2017 年。如果您使用的是旧版本,您可以使用 Striing.Format 代替:criteria.Add(String.Format("[{0}] = {1}", label.Text, parameterName))
    【解决方案2】:

    我认为你应该开始分离 UI 和数据逻辑这里是一个实现的例子:

    首先你在数据库中有一个表:

    CREATE TABLE Customer (
        Id              INT IDENTITY (1, 1) PRIMARY KEY,
        FirstName       VARCHAR (255) NOT NULL,
        LastName        VARCHAR (255) NOT NULL,
        Phone           VARCHAR (25),
        Email           VARCHAR (255) NOT NULL,
        Street          VARCHAR (255),
        City            VARCHAR (50),
        State           VARCHAR (25),
        ZipCode         VARCHAR (5)
    );
    

    然后在 VB 中创建底层实体。网:

    Public Class Customer
        Public Property Id As Integer
        Public Property FirstName As String
        Public Property LastName As String
        Public Property Phone As String
        Public Property Email As String
        Public Property Street As String
        Public Property City As String
        Public Property State As String
        Public Property ZipCode As String
    End Class
    

    数据加载器

    现在您需要一个数据访问组件,将记录加载到上述实体的列表中,这是一个很好的实现:

    Imports System.Data.SqlClient
    
    Public Class CustomerDataAccess
        Public Property ConStr As String
    
        Public Sub New(ByVal constr As String)
            constr = constr
        End Sub
    
        Public Function GetCustomersByCriterias(constraints As Object) As List(Of Customer)
            Dim query As String = "SELECT Id, FirstName, LastName, Phone, Email, Street, City, State, ZipCode
                                 FROM   [dbo].[Customer] "
            Dim result = New List(Of Customer)()
    
            Using con = New SqlConnection(ConStr)
    
                Using cmd = con.CreateCommand()
                    cmd.CommandType = CommandType.Text
                    cmd.CommandText = query
    
                    '' here the magic to add dynamic criteria coming from constraints
                    cmd.ApplyConstraints(Of Customer)(constraints)
    
                    con.Open()
                    LoadCustomerData(cmd, result)
                End Using
            End Using
    
            Return result
        End Function
    
        Private Sub LoadCustomerData(ByVal cmd As SqlCommand, ByVal result As List(Of Customer))
            Using rdr = cmd.ExecuteReader()
                Dim idIdx As Integer = rdr.GetOrdinal("Id")
                Dim firstNameIdx As Integer = rdr.GetOrdinal("FirstName")
                Dim lastNameIdx As Integer = rdr.GetOrdinal("LastName")
                Dim phoneIdx As Integer = rdr.GetOrdinal("Phone")
                Dim emailIdx As Integer = rdr.GetOrdinal("Email")
                Dim streetIdx As Integer = rdr.GetOrdinal("Street")
                Dim cityIdx As Integer = rdr.GetOrdinal("City")
                Dim stateIdx As Integer = rdr.GetOrdinal("State")
                Dim zipCodeIdx As Integer = rdr.GetOrdinal("ZipCode")
    
                While rdr.Read()
                    Dim item = New Customer()
                    item.Id = rdr.GetValueOrDefault(Of Integer)(idIdx)
                    item.FirstName = rdr.GetValueOrDefault(Of String)(firstNameIdx)
                    item.LastName = rdr.GetValueOrDefault(Of String)(lastNameIdx)
                    item.Phone = rdr.GetValueOrDefault(Of String)(phoneIdx)
                    item.Email = rdr.GetValueOrDefault(Of String)(emailIdx)
                    item.Street = rdr.GetValueOrDefault(Of String)(streetIdx)
                    item.City = rdr.GetValueOrDefault(Of String)(cityIdx)
                    item.State = rdr.GetValueOrDefault(Of String)(stateIdx)
                    item.ZipCode = rdr.GetValueOrDefault(Of String)(zipCodeIdx)
                    result.Add(item)
                End While
            End Using
        End Sub
    End Class
    

    扩展方法

    以下是上面引用的扩展方法,它们可以发挥您正在寻找的魔力:

    DataReader 扩展,使用 Dbnull 特殊大小写和强制转换可以轻松地从 SalDataReader 读取值

    Module DataReaderExtenions
        <Extension()>
        Function GetValueOrDefault(Of T)(row As IDataRecord, fieldName As String) As T
            Dim ordinal = row.GetOrdinal(fieldName)
            Return row.GetValueOrDefault(Of T)(ordinal)
        End Function
    
        <Extension()>
        Function GetValueOrDefault(Of T)(row As IDataRecord, ordinal As Integer) As T
            Return (If(row.IsDBNull(ordinal), Nothing, row.GetValue(ordinal)))
        End Function
    
        <Extension()>
        Function GetValueOrDefaultSqlite(Of T)(row As IDataRecord, fieldName As String) As T
            Dim ordinal = row.GetOrdinal(fieldName)
            Return row.GetValueOrDefault(Of T)(ordinal)
        End Function
    
        <Extension()>
        Function GetValueOrDefaultSqlite(Of T)(row As IDataRecord, ordinal As Integer) As T
            Return (If(row.IsDBNull(ordinal), Nothing, Convert.ChangeType(row.GetValue(ordinal), GetType(T))))
        End Function
    End Module
    

    可让您从匿名对象值中提取标准的命令扩展:

    Imports System.Reflection
    Imports System.Runtime.CompilerServices
    
    
    Module CommandExtensions
        <Extension()>
        Function AddParameter(command As IDbCommand, name As String, value As Object) As IDataParameter
            If command Is Nothing Then Throw New ArgumentNullException("command")
            If name Is Nothing Then Throw New ArgumentNullException("name")
            Dim p = command.CreateParameter()
            p.ParameterName = name
            p.Value = If(value, DBNull.Value)
            command.Parameters.Add(p)
            Return p
        End Function
    
        <Extension()>
        Function ToDictionary(data As Object) As Dictionary(Of String, Object)
            If TypeOf data Is String OrElse data.[GetType]().IsPrimitive Then Return New Dictionary(Of String, Object)()
            Return (From [property] In data.[GetType]().GetProperties(BindingFlags.[Public] Or BindingFlags.Instance)
                    Where [property].CanRead
                    Select [property]).ToDictionary(Function([property]) [property].Name, Function([property]) [property].GetValue(data, Nothing))
        End Function
    
        <Extension()>
        Sub ApplyConstraints(Of TEntity)(cmd As IDbCommand, constraints As Object)
            If constraints Is Nothing Then Return
            Dim dictionary = constraints.ToDictionary()
            Dim whereClause = " WHERE "
    
            For Each kvp In dictionary
                Dim columnName = kvp.Key
                Dim propertyName = kvp.Key
                Dim prefix = "@"c
                Dim value = kvp.Value
                whereClause += $"{columnName} **like** {prefix}{propertyName} AND "
                cmd.AddParameter(propertyName, value)
            Next
    
            If String.IsNullOrEmpty(whereClause) Then Return
            cmd.CommandText += whereClause.Remove(whereClause.Length - 5, 5)
        End Sub
    End Module
    

    示例:

    编码完所有这些内容后,您现在可以执行以下操作:

    Dim DataGridView1 As DataGridView = New DataGridView()
    Dim ConStr As String = ConfigurationManager.ConnectionStrings("MyApp").ConnectionString
    Dim dal As CustomerDataAccess = New CustomerDataAccess(ConStr)
    Dim criterias = New With {.FirstName = "%James%", .LastName = "%Nadin%"}
    DataGridView1.DataSource = dal.GetCustomersByCriterias(criterias)
    

    尽管有所有这些代码,您仍然需要将您的文本框(在正确命名它们之后)绑定到 SearchEntity 并使用该实体来提供条件

    我希望本材料可以帮助您解决问题并激励您提高架构和开发技能

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-14
      • 1970-01-01
      相关资源
      最近更新 更多