【问题标题】:Error when updating database from datatable after adding New Row VB.net ADODB to ADO.NEt将新行 VB.net ADODB 添加到 ADO.NEt 后从数据表更新数据库时出错
【发布时间】:2021-09-13 18:06:07
【问题描述】:

我正在尝试将代码转换为 ADO.Net,但在更新时遇到了错误。这是旧代码:

    Private Sub TrackStandardUnit(ByVal iMode As Short, ByVal sSpecies As String, ByVal sProdDesc As String, ByVal sLength As String, ByVal sMultiLgth As String, ByVal sGrades As String, ByVal sCure As String, ByVal sSurface As String, ByVal sThick As String, ByVal sWidth As String, ByVal sgUnits As Single, ByVal iFtg As Short, ByVal iPcs As Short, ByVal iStdPcs As Short, ByVal iStdFtg As Short)
    Dim sNewProd As String
    If Len(sGrades) = 3 Then
        sNewProd = sProdDesc
    Else
        sNewProd = $"{Mid(sProdDesc, 1, 2)}({Strings.Left(sGrades, Len(sGrades) - 1)}){Mid(sProdDesc, 5)}"
    End If

    Dim sSql As String = $"SELECT * FROM tblStdUnitTracking WHERE Species = '{sSpecies}' AND ProdDesc = '{sProdDesc}' AND StdUnits = {sgUnits}"

    Dim bLgths As Boolean = StdUnitLgthUsed(sThick, sWidth, sSpecies, sGrades, sCure, sSurface) ' Lengths present in std unit file
    If bLgths = True Then ' If lengths present in std unit file
        sSql = $"{sSql} AND Length = '{sLength}' AND MultiLgth = '{sMultiLgth}'"
    End If

    Dim rsUnit As ADODB.Recordset = NewRs()
    OpenRsOptimistic(sSql, rsUnit)
    If rsUnit.RecordCount = 0 Then
        rsUnit.AddNew()
        rsUnit.Fields("Species").Value = sSpecies
        rsUnit.Fields("ProdDesc").Value = sNewProd
        rsUnit.Fields("StdUnits").Value = sgUnits
        If sgUnits = 0 Then
            rsUnit.Fields("PieceCnt").Value = 0
        ElseIf iStdPcs > 0 Then
            rsUnit.Fields("StdPieceCnt").Value = iStdPcs
            rsUnit.Fields("StdFootage").Value = 0
            rsUnit.Fields("PieceCnt").Value = iPcs
        ElseIf iStdFtg > 0 Then
            rsUnit.Fields("StdFootage").Value = iStdFtg
            rsUnit.Fields("StdPieceCnt").Value = 0
            rsUnit.Fields("PieceCnt").Value = 0
        End If
        If bLgths = True Then                                           ' If lengths present in std unit file
            rsUnit.Fields("Length").Value = sLength                         ' Save length info
            rsUnit.Fields("MultiLgth").Value = sMultiLgth
        Else                                                            ' Else - no lengths
            rsUnit.Fields("Length").Value = ""
            rsUnit.Fields("MultiLgth").Value = ""
        End If
    End If
    rsUnit.Fields("Count").Value = rsUnit.Fields("Count").Value + 1
    Try
        rsUnit.Update()
        rsUnit.Close()
    Catch ex As Exception

    End Try

End Sub

这是新代码:

  Private Sub TrackStandardUnit(ByVal iMode As Short, ByVal sSpecies As String, ByVal sProdDesc As String, ByVal sLength As String, ByVal sMultiLgth As String, ByVal sGrades As String, ByVal sCure As String, ByVal sSurface As String, ByVal sThick As String, ByVal sWidth As String, ByVal sgUnits As Single, ByVal iFtg As Short, ByVal iPcs As Short, ByVal iStdPcs As Short, ByVal iStdFtg As Short)
    Dim sNewProd As String

    If Len(sGrades) = 3 Then
        sNewProd = sProdDesc
    Else
        sNewProd = $"{Mid(sProdDesc, 1, 2)}({Strings.Left(sGrades, Len(sGrades) - 1)}){Mid(sProdDesc, 5)}"
    End If

    Dim sSql As String = $"SELECT * FROM tblStdUnitTracking WHERE Species = '{sSpecies}' AND ProdDesc = '{sProdDesc}' AND StdUnits = {sgUnits}"

    Dim bLgths As Boolean = StdUnitLgthUsed(sThick, sWidth, sSpecies, sGrades, sCure, sSurface) ' Lengths present in std unit file
    If bLgths = True Then ' If lengths present in std unit file
        sSql = $"{sSql} AND Length = '{sLength}' AND MultiLgth = '{sMultiLgth}'"
    End If



    Dim dtUni As New DataTable
    Dim constring As String = GetDbConnectionString(gDataPath & "Lisa.mdb", "")
    Dim con As OleDbConnection = GetOleDBConnection(constring)
    Dim cmd As OleDbCommand = GetOleDBCommand(sSql, con)
    Dim daX As New OleDbDataAdapter(cmd)

    con.Open()
    daX.Fill(dtUni)
    ds.Tables.Add(dtUni)
    dtUni.TableName = ("AddStdUnit")
    con.Close()


    Dim dtBuilder As New OleDbCommandBuilder(daX)
    dtBuilder.GetUpdateCommand()
    daX.UpdateCommand = dtBuilder.GetUpdateCommand()

    Dim r As DataRow = dtUni.NewRow
    If dtUni.Rows.Count = 0 Then
        r("Species") = sSpecies
        r("ProdDesc") = sNewProd
        r("StdUnits") = sgUnits
        r("Footage") = 0
        If sgUnits = 0 Then
            r("PieceCnt") = 0
        ElseIf iStdPcs > 0 Then
            r("StdPieceCnt") = iStdPcs
            r("StdFootage") = 0
            r("PieceCnt") = iPcs
        ElseIf iStdFtg > 0 Then
            r("StdFootage") = iStdFtg
            r("StdPieceCnt") = 0
            r("PieceCnt") = 0
        End If
        If bLgths = True Then                                           ' If lengths present in std unit file
            r("Length") = sLength                         ' Save length info
            r("MultiLgth") = sMultiLgth
        Else                                                            ' Else - no lengths
            r("Length") = "0"
            r("MultiLgth") = "0"
        End If                   ' THIS IS WHERE YOU STOPPED, IT IS NOT UPDATING TO THE MDB BELOW!!!!!!!!!!!!!!!
    End If
    If IsDBNull(r("Count")) = True Then
        r("Count") = 0
    Else
        r("Count") += 1
    End If

    Try
        dtUni.Rows.Add(r)
        daX.AcceptChangesDuringUpdate = True
        daX.Update(ds, "AddStdUnit")

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

    ds.Tables.Remove("AddStdUnit")
End Sub

在“daX.Update(ds, "AddStdUnit")”处获取“Invalid Insert Syntax”,当我检查“daX.UpdateCommand.CommandText”时,所有值都是“?”但是当我在“dtUni”中查看表数据时,该行已添加并填充,它不会使用 updatecommand 更新到实际数据库。

我在这里做错了什么?

这是在调用 daX.Update(ds, "AddStdUnit") 之前插入和更新的命令文本:

?dax.UpdateCommand.CommandText
"UPDATE tblStdUnitTracking SET Species = ?, ProdDesc = ?, Length = ?, MultiLgth = ?, StdUnits = ?, StdFootage = ?, StdPieceCnt = ?, PieceCnt = ?, Footage = ?, Count = ? WHERE ((Species = ?) AND (ProdDesc = ?) AND (Length = ?) AND (MultiLgth = ?) AND (StdUnits = ?) AND (StdFootage = ?) AND (StdPieceCnt = ?) AND (PieceCnt = ?) AND ((? = 1 AND Footage IS NULL) OR (Footage = ?)) AND ((? = 1 AND Count IS NULL) OR (Count = ?)))"

?dax.InsertCommand.CommandText
"INSERT INTO tblStdUnitTracking (Species, ProdDesc, Length, MultiLgth, StdUnits, StdFootage, StdPieceCnt, PieceCnt, Footage, Count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"

注意所有的“?”。

在调用更新命令之前,这里是 dtUni 的 DataTable Visualizer:datatable visualizer

【问题讨论】:

  • 这看起来很可怕——容易受到 sql 注入问题的影响。
  • Count 可能是保留字。如果可以,可能必须将字段名称包装在 [...] 中或重命名它。重命名是我的建议
  • 旁注:在将代码转换为 .NET 时删除匈牙利符号。
  • @Hursey 你是对的! Count 是一个保留字,当我更改它时,一切正常。谢谢。

标签: vb.net ms-access ado.net oledb ado


【解决方案1】:

一个方法中有太多的代码做了太多不同的事情。

按钮代码严格处理用户界面。我已经声明了所有变量。我不知道这些值的来源,所以我只是添加了一个默认值。该按钮调用各种方法并显示错误消息框。

需要处理连接、命令和数据适配器等数据库对象。 Using...End Using 块处理这个。您可以在完成后将 DataAdapter 丢弃。

总是使用参数来避免Sql注入。我不得不猜测参数的数据类型。检查您的数据库中的正确类型。

DataAdapter 将打开和关闭连接。我没有看到需要DataSet 或DataTable 的名称。

Private constring As String = GetDbConnectionString(gDataPath & "Lisa.mdb", ""))
Private daX As OleDbDataAdapter
Private dt As DataTable

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim sThick As String = ""
    Dim sWidth As String = ""
    Dim sSpecies As String = ""
    Dim sGrades As String = ""
    Dim sCure As String = ""
    Dim sSurface As String = ""
    Dim sProdDesc As String = ""
    Dim sgUnits As Single = 0
    Dim sLength As String = ""
    Dim sMultiLgth As String = ""
    Dim iFtg As Short = 0
    Dim iPcs As Short = 0
    Dim iStdPcs As Short = 0
    Dim iStdFtg As Short = 0
   
    Dim bLgths As Boolean = StdUnitLgthUsed(sThick, sWidth, sSpecies, sGrades, sCure, sSurface) ' Lengths present in std unit file
    Try
        FillDataTable(bLgths, sSpecies, sProdDesc, sgUnits, sLength, sMultiLgth)
    Catch ex As Exception
        MessageBox.Show(ex.Message,"Error Filling DataTable")
        Exit Sub
    End Try
    Dim sNewProd As String
    If sGrades.Length = 3 Then
        sNewProd = sProdDesc
    Else
        sNewProd = $"{sProdDesc.Substring(0, 2)}({sGrades.Substring(0, sGrades.Length - 1)}){sProdDesc.Substring(5)}"
    End If
    AddRowtoDataTable(sSpecies, sProdDesc, sGrades, sNewProd, sgUnits,iStdPcs,iPcs,iStdFtg,bLgths,sLength,sMultiLgth)
    Try
        UpdateDatabase()
    Catch ex As Exception
        MessageBox.Show(ex.Message, "Error Updating Database")
    End Try
End Sub

Private Sub FillDataTable(bLgths As Boolean, sSpecies As String, sProdDesc As String, sgUnits As Single, sLength As String, sMultiLgth As String) As DataTable
    Dim sSql As String = $"SELECT * FROM tblStdUnitTracking WHERE Species = @Species 
                            AND ProdDesc = @ProdDesc 
                            AND StdUnits = @Units"

    dt = New DataTable
    Using con As New OleDbConnection(constring),
            cmd As New OleDbCommand()
        With cmd.Parameters
            .Add("@Species", OleDbType.VarChar).Value = sSpecies
            .Add("@ProdDesc", OleDbType.VarChar).Value = sProdDesc
            .Add("@Units", OleDbType.SmallInt).Value = sgUnits
        End With
        If bLgths Then ' If lengths present in std unit file
            sSql &= $" AND Length = @Length AND MultiLgth = @MultiLgth"
            cmd.Parameters.Add("@Length", OleDbType.VarChar).Value = sLength
            cmd.Parameters.Add("@MultiLgth", OleDbType.VarChar).Value = sMultiLgth
        End If
        sSql &= ";"
        cmd.CommandText = sSql
        cmd.Connection = con
        daX = New OleDbDataAdapter(cmd)
        daX.Fill(dt)
    End Using
End Sub

Private Sub AddRowtoDataTable(sSpecies As String, sProdDesc As String, sGrades As String, sNewProd As String, sgUnits As Single, iStdPcs As Integer, iPcs As Integer, iStdFtg As Integer, bLgths As Boolean, sLength As String, sMultiLgth As String)
    Dim r As DataRow = dt.NewRow
    If dt.Rows.Count = 0 Then
        r("Species") = sSpecies
        r("ProdDesc") = sNewProd
        r("StdUnits") = sgUnits
        r("Footage") = 0
        If sgUnits = 0 Then
            r("PieceCnt") = 0
        ElseIf iStdPcs > 0 Then
            r("StdPieceCnt") = iStdPcs
            r("StdFootage") = 0
            r("PieceCnt") = iPcs
        ElseIf iStdFtg > 0 Then
            r("StdFootage") = iStdFtg
            r("StdPieceCnt") = 0
            r("PieceCnt") = 0
        End If
        If bLgths Then  ' If lengths present in std unit file
            r("Length") = sLength ' Save length info
            r("MultiLgth") = sMultiLgth
        Else    ' Else - no lengths
            r("Length") = "0"
            r("MultiLgth") = "0"
        End If ' THIS IS WHERE YOU STOPPED, IT IS NOT UPDATING TO THE MDB BELOW!!!!!!!!!!!!!!!
    End If
    r("Count") = 0 'The If statement is useless, this is a New Row with no value set for Count.
    dt.Rows.Add(r) 'Adding a blank row if rows.count =0
End Sub
Private Sub UpdateDatabase()
    Using dtBuilder As New OleDbCommandBuilder(daX)
    dtBuilder.GetUpdateCommand()
    daX.UpdateCommand = dtBuilder.GetUpdateCommand()
    Try
        daX.AcceptChangesDuringUpdate = True
        daX.Update(dt)
    Catch ex As Exception
        MsgBox(ex.Message)
    End Try
    End Using
End Sub

我认为传递的参数太多了。如果您可以为您的数据库字段的模型创建一个类,则很容易传递具有所有属性的单个对象。 Intellisense 在这里很有帮助。

【讨论】:

    【解决方案2】:

    您的问题看起来是您使用 commandBuilder,并且只获取更新命令 - 您需要插入命令。

    只需创建命令构建器 - 它会并且应该为您自动构建所有 3 个(插入、更新、删除)

    另外,命令生成器有一个阅读器和一个连接,所以不需要做更多。

    事实上,有了阅读器,并且不需要更新,那么就不要创建数据适配器。

    往这边走:

        Dim dtUni As New DataTable
        Using cmd As New OleDbCommand(Sql,
                     New OleDbConnection(GetDBConnectionString(gDataPath & "Lisa.mdb", "")))
    
            cmd.Connection.Open()
            dtUni.Load(cmd.ExecuteReader)
        End Using
    

    因此,如果您要更新数据,您只需要一个“适配器” - 这是您的情况。

    所以,试试这个:

        Dim dtUni As New DataTable
        Using cmd As New OleDbCommand(Sql,
                     New OleDbConnection(GetDBConnectionString(gDataPath & "Lisa.mdb", "")))
    
            cmd.Connection.Open()
            dtUni.Load(cmd.ExecuteReader)
    
            Dim daX As New OleDbDataAdapter(cmd)
            Dim dtBuilder As New OleDbCommandBuilder(daX)
    
            cmd.Connection.Open()
            daX.Fill(dtUni)
            ds.Tables.Add(dtUni)
            dtUni.TableName = ("AddStdUnit")
            cmd.Connection.Close()
    
            Dim r As DataRow = dtUni.NewRow
    
            ' your code here, then
    
            Try
                cmd.Connection.Open()
                dtUni.Rows.Add(r)
                daX.AcceptChangesDuringUpdate = True
    
                daX.Update(dtUni) '<---- just table here. 
    
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using
    
        ds.Tables.Remove("AddStdUnit")
    

    我不确定您是否需要关闭,然后重新打开(但无论哪种方式都可以尝试)。

    因此,您仅从命令生成器中获取“更新”命令。 (不需要 - 只需让命令生成器按照上面的方式构建,然后您就可以更新,将行插入该数据表,一个 daX.Update(dtUni) 应该可以工作。

    【讨论】:

    • 感谢您的帮助,但我仍然遇到相同的插入语法错误。
    • 哼,以前有用吗?该表是否定义了 PK 列?我会检查我的一些代码,但我一直都这样做。
    • 不确定评论者是谁,但指出“计数”可能是保留字的人是 100% 正确的。将数据库字段名称更改为“xCount”,所有对它的引用和代码都有效。谢谢阿农。
    • @Hursey 对我的问题做出了正确的诊断。
    【解决方案3】:

    ? 占位符对于 OleDb 来说正确。这就是 OleDb 的参数化查询的工作方式(其他提供程序使用命名参数有更好的机制)。 ? 是最终值的占位符,它 NEVER 被替换到 sql 命令字符串中,即使在数据库本身上也是如此。这些值作为变量保存在单独的位置,几乎就像您编写了一个存储过程一样。这样就杜绝了任何sql注入的可能。

    我不知道这是否会对您的问题有所帮助,但我确实浏览了代码并进行了一些更改以匹配新的实践,这通常似乎有很大帮助。具体来说,我确实发现了一个关于如何将新行添加到数据集中的错误,这将导致始终将Count 设置为零。否则,我个人倾向于不使用CommandBuilder 或DataSet.Update(),而是酌情在代码中(使用有效的查询参数)构建INSERT 或UPDATE 语句。

    Private Sub TrackStandardUnit(ByVal Mode As Short, ByVal Species As String, ByVal ProdDesc As String, ByVal Length As String, ByVal MultiLgth As String, ByVal Grades As String, ByVal Cure As String, ByVal Surface As String, ByVal Thick As String, ByVal Width As String, ByVal Units As Single, ByVal Ftg As Short, ByVal Pcs As Short, ByVal StdPcs As Short, ByVal StdFtg As Short)
    
        Dim NewProd As String = ProdDesc
        If Grades.Length <> 3 Then
            NewProd = $"{ProdDesc.SubString(1, 2)}({Grades.Left(Grades.Length -1)}){ProdDesc.SubString(5)}"
        End If
    
        Dim sql As String = "SELECT * FROM tblStdUnitTracking WHERE Species = ? AND ProdDesc = ? AND StdUnits = ?"
    
        Dim Lgths As Boolean = StdUnitLgthUsed(Thick, Width, Species, Grades, Cure, Surface) ' Lengths present in std unit file
        If Lgths Then ' If lengths present in std unit file
            sql += " AND Length = ? AND MultiLgth = ?"
        End If
    
        Dim ds As New DataSet
        Dim constring As String = GetDbConnectionString(gDataPath & "Lisa.mdb", "")
        Using con As OleDbConnection = GetOleDBConnection(constring), _
              cmd As New OleDbCommand(sql, con), _
              da As New OleDbDataAdapter(cmd)
    
            ' OleDb uses positional parameters. They work in the order they are found in the string
            ' I have to guess at parameter types and lengths. You should use the exact types from the database.
            cmd.Parameters.Add("?", OleDbType.VarWChar, 30).Value = Species
            cmd.Parameters.Add("?", OleDbType.VarWChar, 120).Value = ProdDesc
            cmd.Parameters.Add("?", OleDbType.Single).Value = StdUnits
            If Lgths Then
                cmd.Parameters.Add("?", OleDbType.Integer).Value = Convert.ToInt32(Length)
                cmd.Parameters.Add("?", OleDbType.Integer).Value = Convert.ToInt32(MultiLgth)
            End If
    
            da.Fill(ds)
            Dim Uni As DataTable = ds.Tables(0)
    
            Dim Builder As New OleDbCommandBuilder(da)
            da.UpdateCommand = Builder.GetUpdateCommand()
    
            If Uni.Rows.Count = 0 Then
                Dim r As DataRow = Uni.NewRow
                r("Species") = Species
                r("ProdDesc") = NewProd
                r("StdUnits") = Units
                r("Footage") = 0
                If sgUnits = 0 Then
                    r("PieceCnt") = 0
                ElseIf iStdPcs > 0 Then
                    r("StdPieceCnt") = StdPcs
                    r("StdFootage") = 0
                    r("PieceCnt") = Pcs
                ElseIf iStdFtg > 0 Then
                    r("StdFootage") = StdFtg
                    r("StdPieceCnt") = 0
                    r("PieceCnt") = 0
                End If
                If Lgths Then                                          
                    r("Length") = Length                         
                    r("MultiLgth") = MultiLgth
                Else                                                           
                    r("Length") = "0"
                    r("MultiLgth") = "0"
                End If  
                Uni.Rows.Add(r)                
            End If
            Dim row As DataRow = Uni.Rows(0)
            If IsDBNull(row("Count")) Then
                row("Count") = 0
            Else
                row("Count") += 1
            End If
    
            Try
                da.AcceptChangesDuringUpdate = True
                con.Open()
                da.Update(ds)
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using
    End Sub
    

    【讨论】:

    • Coehoom 获取变量 'r' 在封闭块错误中隐藏了一个变量。尝试将第二个 Dim r 更改为另一个变量,我得到相同的语法错误。感谢您尝试提供帮助。 ADODB 记录集工作得很好,这对我来说太奇怪了。即使构建插入语句仍然给我一个语法错误,并且尝试在数据库中运行查询给我错误。似乎它不喜欢其中一个变量。 DB 表中有 10 个字段,Species、ProdDesc、Length 和 LultiLgth 是字符串,其他都是数字
    • 我更新了“变量 r”错误的答案。如果您仍然看到这一点,那是因为还有一个名为 r 的变量的作用域比我们从问题中看到的要高。
    • “尝试将第二个 Dim r 更改为另一个变量,我得到相同的语法错误。”我已经试过了。还是不行。不过,我感谢您的帮助。
    • @CircleDot 关键是你不能在大的if 块内部和外部都使用r。更改声明是不够的,除非您还更改了用于 IsDBNULL() 部分的内容。
    【解决方案4】:

    “Count”是一个保留字,在尝试使用 daX.update 命令时会抛出无效的 INSERT 语法。更改了字段和对“xCount”的所有引用,上面的 ADO.Net 代码版本工作得很好。出于某种原因删除他的评论的评论者是这个答案的感谢。

    【讨论】:

      猜你喜欢
      • 2013-05-11
      • 2015-08-15
      • 1970-01-01
      • 2012-03-07
      • 2013-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多