将新行 VB.net ADODB 添加到 ADO.NEt 后从数据表更新数据库时出错

Error when updating database from datatable after adding New Row VB.net ADODB to ADO.NEt

我正在尝试将代码转换为 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”中查看 table 数据时,该行已添加并填充,它不会使用更新命令更新到实际数据库。

我做错了什么?

以下是在调用 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

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

我不知道这是否对您的问题有帮助,但我确实检查了代码并做了一些更改以适应新的做法,这通常看起来很有帮助。具体来说,我确实发现了有关如何将新行添加到数据集的错误,这将导致始终将 Count 设置为零。否则,我个人倾向于不使用 CommandBuilderDataSet.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

您的问题似乎是您使用了 commandBuilder,并且只获得更新命令 - 您需要插入命令。

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

此外,命令生成器有一个 reader 和一个连接,因此无需创建更多。

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

往这边走:

    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")

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

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

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

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

需要处理连接、命令和数据适配器等数据库对象。 Using...End Using 块处理这个。您可以在使用完 DataAdapter 后对其进行处置。

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

DataAdapter 将打开和关闭连接。我没有看到需要 DataSetDataTable.

的名称
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

我认为传递的参数太多了。如果您可以创建一个 class 模型,您的数据库字段将很容易传递具有所有属性的单个对象。智能感知在这里很有用。

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