Vb.net 插入查询不能包含多值字段

Vb.net 插入查询不能包含多值字段,vb.net,ms-access,Vb.net,Ms Access,我想和你们分享我的代码…下面是我想开发的一个简短的解释:我已经创建了一个连接到我的数据库(Access Db)的表单。通过表单,当我想添加数据时,我将按下一个按钮,新表单将显示一组控件,其中将添加我的数据。然后我读取这些数据并向绑定到datagridview的表中添加一个新的datarow。直到这里一切都很完美 但当我试图更新数据集以保存更改时,它给了我一个错误:“插入查询不能包含多值字段” 我试图解决这个问题,但没有成功: Private Function AddDataFromContro

我想和你们分享我的代码…下面是我想开发的一个简短的解释:我已经创建了一个连接到我的数据库(Access Db)的表单。通过表单,当我想添加数据时,我将按下一个按钮,新表单将显示一组控件,其中将添加我的数据。然后我读取这些数据并向绑定到datagridview的表中添加一个新的datarow。直到这里一切都很完美

但当我试图更新数据集以保存更改时,它给了我一个错误:“插入查询不能包含多值字段”

我试图解决这个问题,但没有成功:

 Private Function AddDataFromControlArrayToDataRow(dataset As DataSet, tableName As String, Ctrl As Control())
        Dim row As DataRow = dataset.Tables(tableName).NewRow()
        Dim c As Integer = Ctrl.Length - 1
        For k As Integer = 0 To c - 1
            If (Ctrl(k).GetType.ToString = "System.Windows.Forms.CheckBox") Then
                Dim CheckBox As CheckBox = Ctrl(k)
                row(k) = CheckBox.Checked
            Else
                If Not Ctrl(k).Text = String.Empty Then
                    row(k) = Convert.ChangeType(Ctrl(k).Text, dataset.Tables(tableName).Columns(k).DataType)
                End If
            End If
        Next
        dataset.Tables(tableName).Rows.Add(row) '<----up ot here Code is running without error
        Form1.TableAdapterManager.UpdateAll(dataset)
        Return Nothing
    End Function
私有函数AddDataFromControlArrayToDataRow(数据集作为数据集,tableName作为字符串,Ctrl作为控件())
作为DataRow=dataset.Tables(tableName).NewRow()的Dim行
尺寸c为整数=Ctrl.Length-1
对于k作为整数=0到c-1
如果(Ctrl(k).GetType.ToString=“System.Windows.Forms.CheckBox”),则
将复选框调整为复选框=Ctrl(k)
行(k)=复选框。选中
其他的
如果不是Ctrl(k).Text=String.Empty,则
行(k)=Convert.ChangeType(Ctrl(k).Text,dataset.Tables(tableName).Columns(k).DataType)
如果结束
如果结束
下一个

dataset.Tables(tableName).Rows.Add(row)您在评论中提到您在使用DAO时遇到问题。您需要添加与此图像类似的对访问引擎的引用(您的版本号可能不同):

然后将此
Imports
语句添加到代码中:

Imports DAO = Microsoft.Office.Interop.Access.Dao
在这个SO问题中可以找到一个使用DAO操作附件字段的示例

如果您不介意使用hack,您可以使用OleDB添加/删除/提取附件。下面的实用程序类是我六年前在MSDN论坛上参加的黑客会话的结果。我发现从
AttachFieldName.FileData
字段检索到的
Byte
数组是一个头前缀数组。前4个字节定义实际文件数据的整数偏移量。这允许您重新创建附加文件。我还发现,如果您在文件数据前面加上8个字节(前四个字节同样是文件数据的偏移量,其余四个字节保留为零),Access将接受它作为存储。它将使用对您不重要的其他值扩展此标头,并使用新偏移量重写前4个字节。我知道这项技术适用于Access 2007和2010,但我还没有在新版本上进行测试。另外,据我所知,您仍然需要使用DAO中的数据偏移量来提取原始文件;由于使用了
Field2.LoadFromFile
命令,加载附件更容易

很抱歉出现这种代码转储,但是正确设置命令有点麻烦。所以我写了这个库来简化事情

Imports System.Data.OleDb

Public NotInheritable Class Attachments
    Private Const prependLength As Int32 = 8

    ''' <summary>Expands the attachment field name to the 2 fields that need to be retrieved</summary>
    ''' <param name="attachmentFieldName"></param>
    ''' <returns>"attachmentFieldName.FileData, attachmentFieldName.FileName"</returns>
    Public Shared Function ExpandAttachmentFieldNames(attachmentFieldName As String, tableName As String) As String
        Return String.Format("[{0}].[{1}].FileData, [{0}].[{1}].FileName", tableName, attachmentFieldName)
    End Function

    ''' <summary>Prepends the 8 byte header block required by Access to store the bytes that comprise the fiel</summary>
    ''' <param name="source"></param>
    ''' <returns>8 bytes + source</returns>
    Public Shared Function PrependFileDataHeader(source As Byte()) As Byte()
        ' Through reverse engineering, it has been determined that while length of the 
        ' actual data header varies based on file type.

        ' This header always begins with the first 4 bytes representing the offset to the actual data bytes.
        ' The bytes from index 4 (zero based) to the beginning of the data appear to represent the file type 
        ' and other undetermined information.

        ' If an 8 byte field in prepended to the data bytes, with the first 4 bytes representing the
        ' length of 8, it has been found that that Access will accept it.  The second 4 bytes will be expanded
        ' by Access and filled by it.  The initial four bytes is modified to the new data offset.

        Dim ret As Byte() = New Byte(0 To (prependLength + source.Length) - 1) {}
        Dim lengthBytes As Byte() = BitConverter.GetBytes(prependLength)
        Array.ConstrainedCopy(lengthBytes, 0, ret, 0, lengthBytes.Length)
        Array.ConstrainedCopy(source, 0, ret, prependLength, source.Length)
        Return ret
    End Function

    ''' <summary>Extracts the bytes that define the attached file</summary>
    ''' <param name="fileData">the header prepended bytes array returned by Access</param>
    ''' <returns>the bytes defining the attached file</returns>
    Public Shared Function ExtractFileBytes(fileData As Byte()) As Byte()
        Dim dataOffset As Int32 = BitConverter.ToInt32(fileData, 0)
        Dim databytes(0 To (fileData.Length - dataOffset) - 1) As Byte
        Array.ConstrainedCopy(fileData, dataOffset, databytes, 0, fileData.Length - dataOffset)
        Return databytes
    End Function

    ''' <summary>
    ''' Takes an Access FileData byte array and returns a stream of it contained file.
    ''' </summary>
    ''' <param name="fileData">the attachment data as received from Access</param>
    ''' <returns>MemoryStream constrained to the contained file data</returns>
    Public Shared Function FileDataAsStream(fileData As Byte()) As IO.MemoryStream
        Dim dataOffset As Int32 = BitConverter.ToInt32(fileData, 0)
        Dim datalength As Int32 = fileData.Length - dataOffset
        Return New IO.MemoryStream(fileData, dataOffset, datalength)
    End Function

    ''' <summary>
    ''' Takes FileData Byte() from Access and writes its contained file to the passed stream.
    ''' </summary>
    ''' <param name="fileData">the attachment data as received from Access</param>
    ''' <param name="strm">stream to copy file contents to</param>
    ''' <remarks></remarks>
    Public Shared Sub WriteFileDataToStream(fileData As Byte(), strm As IO.Stream)
        Dim dataOffset As Int32 = BitConverter.ToInt32(fileData, 0)
        Dim datalength As Int32 = fileData.Length - dataOffset
        strm.Write(fileData, dataOffset, datalength)
    End Sub

    ''' <summary>
    ''' Copies entire stream to an Access FileData Byte()
    ''' </summary>
    ''' <param name="strm">source stream to wrap in Access FileData Byte()</param>
    ''' <returns>An Access FileData Byte()</returns>
    ''' <remarks></remarks>
    Public Shared Function FileDataFromStream(strm As IO.Stream) As Byte()
        Dim ret As Byte() = Nothing
        If strm.CanSeek Then
            Dim dataLength As Int32 = CInt(strm.Length)
            strm.Position = 0
            ret = New Byte(0 To (prependLength + dataLength) - 1) {}
            Dim lengthBytes As Byte() = BitConverter.GetBytes(prependLength)
            Array.ConstrainedCopy(lengthBytes, 0, ret, 0, lengthBytes.Length)
            strm.Read(ret, prependLength, dataLength)
        Else
            Throw New IO.IOException("Stream must be seekable.")
        End If
        Return ret
    End Function

    ''' <summary>
    ''' Copies data from read file to an Access FileData Byte()
    ''' </summary>
    ''' <param name="filePath">Full path to source file</param>
    ''' <returns>An Access FileData Byte()</returns>
    Public Shared Function FileDataLoadFile(filePath As String) As Byte()
        Dim ret As Byte() = Nothing
        Dim fi As New IO.FileInfo(filePath)
        If fi.Exists Then
            Dim strm As IO.Stream = IO.File.OpenRead(filePath)
            ret = FileDataFromStream(strm)
        Else
            Throw New IO.FileNotFoundException(filePath)
        End If
        Return ret
    End Function

    ''' <summary>
    ''' Prepares a OleDBCommand with parameters to add an attachment to record
    ''' </summary>
    ''' <param name="connection">OleDbConnection to use</param>
    ''' <param name="attachmentFieldname">Name of attachment field</param>
    ''' <param name="tableName">DB Table name</param>
    ''' <param name="attachmentFileData">the raw bytes that comprise the file to attach</param>
    ''' <param name="attachmentFileName">a name for this attachment</param>
    ''' <param name="pkName">the primary key field name</param>
    ''' <param name="pkType">the type of the primary key, typically OleDbType.Integer</param>
    ''' <param name="pkValue">primary key value of the racord to add attachment to</param>
    ''' <returns>prepared OleDbCommand</returns>
    Public Shared Function AddAttachmentCommand(connection As OleDbConnection,
                                                                attachmentFieldname As String,
                                                                tableName As String,
                                                                attachmentFileData As Byte(),
                                                                attachmentFileName As String,
                                                                pkName As String,
                                                                pkType As OleDbType,
                                                                pkValue As Object) As OleDbCommand
        Dim insertCommand As New OleDb.OleDbCommand()

        insertCommand.CommandText = String.Format("INSERT INTO [{0}] ([{0}].[{1}].FileData, [{0}].[{1}].FileName) VALUES (?, ?) WHERE ([{0}].[{2}]=?)", tableName, attachmentFieldname, pkName)
        insertCommand.Connection = connection

        ' Parameter Order: FileData, FileName, pk

        Dim paramData As New OleDbParameter("FileData", OleDbType.Binary)
        paramData.Value = PrependFileDataHeader(attachmentFileData)
        insertCommand.Parameters.Add(paramData)

        Dim paramName As New OleDbParameter("FileName", OleDbType.WChar)
        paramName.Value = attachmentFileName
        insertCommand.Parameters.Add(paramName)

        insertCommand.Parameters.Add(pkName, pkType).Value = pkValue

        Return insertCommand
    End Function

    ''' <summary>
    ''' Prepares an OleDBCommand that removes the specified attachment from a record.
    ''' </summary>
    ''' <param name="connection">OleDbConnection to use</param>
    ''' <param name="attachmentFieldname">Name of attachment field</param>
    ''' <param name="tableName">DB Table name</param>
    ''' <param name="attachmentFileName">the attachment name as received from Access</param>
    ''' <param name="pkName">the primary key field name</param>
    ''' <param name="pkType">the type of the primary key, typically OleDbType.Integer</param>
    ''' <param name="pkValue">primary key value of the racord to delete attachment to</param>
    ''' <returns>prepared OleDbCommand</returns>
    Public Shared Function DeleteAttachmentCommand(connection As OleDbConnection,
                                                                attachmentFieldname As String,
                                                                tableName As String,
                                                                attachmentFileName As String,
                                                                attachmentFileData As Byte(),
                                                                pkName As String,
                                                                pkType As OleDbType,
                                                                pkValue As Object) As OleDbCommand

        ' Note:  Eventhough Access acts like FileName is the pk for attachments, 
        ' we need to include filedata in contraints, orelse all attachments for record are deleted.
        Dim deleteCommand As New OleDbCommand()
        deleteCommand.CommandText =
            String.Format("DELETE [{0}].[{1}].FileData From [{0}] WHERE ( ([{0}].[{2}] = ?) AND ([{0}].[{1}].FileName = ?) AND ([{0}].[{1}].FileData = ?) )",
                                tableName, attachmentFieldname, pkName)
        deleteCommand.Connection = connection

        ' Parameter Order: pk, FileName
        Dim paramPK As OleDbParameter = deleteCommand.Parameters.Add(pkName, pkType)
        paramPK.Value = pkValue

        Dim paramName As New OleDbParameter("FileName", OleDbType.WChar)
        paramName.Value = attachmentFileName
        deleteCommand.Parameters.Add(paramName)

        Dim paramData As New OleDbParameter("FileData", OleDbType.Binary)
        paramData.Value = attachmentFileData
        deleteCommand.Parameters.Add(paramData)
        Return deleteCommand
    End Function
End Class ' Attachments

Public NotInheritable Class Connect 
    Public Shared Function ConnectionString(dbFilePath As String) As String
        Return String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source='{0}';Persist Security Info=True", dbFilePath)
    End Function
End Class
你也可以找到下面这篇有趣的文章


您的DB表是否包含附件字段?附件字段是多值字段的一个示例。这些字段需要特殊处理。我建议您在Access DB中显示创建的查询和表结构。执行INSERT时,多值列需要追加查询(使用
[column\u name].Value
)。如果可能,还可以尝试重新创建没有多值列的表。正如所建议的,ADO.NET无法真正处理访问
附件
列。它可以检索这些数据,但您需要做一些额外的工作来获取数据,而它根本无法保存这些数据。如果您确实必须使用
附件
列,那么您基本上需要使用DAO来处理它们。如果您不特别需要使用
附件
列,则不要使用<代码>OLE对象
列可以存储二进制数据,您可以使用ADO.NET将其保存为
字节
数组。它是多值文本字段还是附件类型?我有一个附件字段。
    ' Table Schema
    ' Field                 Type            
    ' --------------        --------------------
    ' ID                        AutoNumber
    ' ClientName            Text
    ' Attachments           Attachment

Public Class Form1
    Const pkName As String = "ID"
    Const pkDataType As OleDb.OleDbType = OleDb.OleDbType.Integer
    Const attachmentFieldName As String = "Attachments"
    Const tableName As String = "MyTable"
    Const dbPath As String = "C:\Users\UserName\Documents\DBs\Access\AttachmentDemoTest.accdb"

    ' adds a empty record and returns its PK (ID)
    Private Function AddNewRecord(clientName As String) As Int32
        Dim ret As Int32
        Using conn As New OleDb.OleDbConnection(AccessUtilities.Connect.ConnectionString(dbPath))
            Dim insertCommand As New OleDb.OleDbCommand()
            insertCommand.CommandText = String.Format("INSERT INTO {0} (ClientName) VALUES (?)", tableName)
            Dim paramDesc As OleDb.OleDbParameter = insertCommand.Parameters.Add("Description", OleDb.OleDbType.VarWChar)
            paramDesc.IsNullable = True
            paramDesc.Value = DBNull.Value
            insertCommand.Connection = conn

            conn.Open()
            ret = insertCommand.ExecuteNonQuery

            If ret = 1 Then ' one record inserted, retrieve pk
                insertCommand.CommandText = "Select @@IDENTITY"
                insertCommand.Parameters.Clear()
                ret = CInt(insertCommand.ExecuteScalar)
            End If

            conn.Close()
        End Using
        Return ret
    End Function

    Private Sub AddRecordAndAttachment(clientName As String, fileBytes As Byte(), fileName As String)
        Dim id As Int32 = AddNewRecord(clientName)
        ' adding an attachment only needs a way to refer to the record to attach to
        ' this is done using the pk (ID)
        Using conn As New OleDb.OleDbConnection(AccessUtilities.Connect.ConnectionString(dbPath))
            Dim attachCommand As OleDb.OleDbCommand = AccessUtilities.Attachments.AddAttachmentCommand(conn, attachmentFieldName, tableName, fileBytes, fileName, pkName, pkDataType, id)
            conn.Open()
            Dim result As Int32 = attachCommand.ExecuteNonQuery()
            conn.Close()
        End Using
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' demo adding new record and attachment
        Dim fileBytes As Byte() = IO.File.ReadAllBytes("C:\Users\UserName\Pictures\BottleTop.png")
        AddRecordAndAttachment("Fred Inc.", fileBytes, "FredsPic.png")
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click

        Dim id As Int32 = 1 ' assumed - change as needed

        ' need to retrieve FileData and FileName from an attachment
        Using conn As New OleDb.OleDbConnection(AccessUtilities.Connect.ConnectionString(dbPath))
            ' retrieve all attachments for id
            Dim selectCommand As New OleDb.OleDbCommand
            selectCommand.CommandText = String.Format("Select {0} From [{1}] Where [{1}].[{2}]=?", AccessUtilities.Attachments.ExpandAttachmentFieldNames(attachmentFieldName, tableName), tableName, pkName)
            selectCommand.Parameters.Add("ID", OleDb.OleDbType.Integer).Value = id
            selectCommand.Connection = conn
            Dim dt As New DataTable
            Dim da As New OleDb.OleDbDataAdapter(selectCommand)
            da.Fill(dt)

            If dt.Rows.Count > 0 Then ' we have attachments
                ' delete the 1st attachment retrieved
                Dim accessFileData As Byte() = CType(dt.Rows(0).Item(String.Format("{0}.{1}.FileData", tableName, attachmentFieldName)), Byte())
                Dim accessFileName As String = CStr(dt.Rows(0).Item(String.Format("{0}.{1}.FileName", tableName, attachmentFieldName)))

                Dim deleteCommand As OleDb.OleDbCommand = AccessUtilities.Attachments.DeleteAttachmentCommand(conn, attachmentFieldName, tableName, accessFileName, accessFileData, pkName, pkDataType, id)
                conn.Open()
                Dim result As Int32 = deleteCommand.ExecuteNonQuery()   ' if = 1 then attachment deleted
                conn.Close()
            End If
        End Using

    End Sub

End Class