DataAdapter与数据源提供程序相关,操作ACCESS数据库数据提供程序为OleDb.net,命名空间为System.Data.OleDb,相应的DataAdapter对象在System.Data.OleDb空间里对应的是OleDbDataAdapter对象。
而DataTable对象和DataSet对象同属于System.Data命名空间,可见其与数据源无关。DataTable对象可作为DataSet对象中的表,也可以单独作为数据集。下图为ADO.NET 体系结构。
使用OleDbDataAdapter对象的Fill方法可从数据源检索数据并填充 DataSet 中的表,实例化OleDbDataAdapter对象时必须设置其SelectCommand属性。 使用OleDbDataAdapter对象的Update方法还可将对 DataSet 所做的更改解析回数据源,实例化OleDbDataAdapter对象时必须设置InsertCommand、 UpdateCommand或 DeleteCommand属性。
一、OleDbDataAdapter对象填充数据集并将更改保存到数据库
以下示例代码演示使用OleDbDataAdapter对象填充数据集,修改数据集中的表数据,然后将修改过的数据更新到数据库。
Imports System.Data.OleDb
Public Class Form2
Private Const strconn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=F:\test.accdb"
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'连接数据库
Dim conn As New OleDbConnection(strconn)
'打开数据库
conn.Open()
'实例化OleDbDataAdapter
Dim adapter As New OleDbDataAdapter("SELECT stutbl.* FROM stutbl", conn)
'实例化DataSet
Dim dst As New DataSet("mydst")
'添加表
dst.Tables.Add("mytbl")
'填充数据到DataSet
adapter.Fill(dst, "mytbl")
'窗口显示数据
DataGridView1.DataSource = dst.Tables("mytbl")
'操作数据表DataTable
With dst.Tables("mytbl")
'插入行
Dim newrow As DataRow = .NewRow()
newrow("stuname") = "李四"
newrow("sex") = "男"
newrow(3) = "二年级"
.Rows.Add(newrow)
'更新行
.Rows(2)("stuname") = "王二"
'删除行
.Rows(.Rows.Count - 2).Delete()
End With
'添加insert命令参数
Dim insertcmd As New OleDbCommand("Insert INTO stutbl(sex,stuname,grade) VALUES (?,?,?)", conn)
insertcmd.Parameters.Add("@sex", OleDbType.VarChar, 1, "sex")
insertcmd.Parameters.Add("@stuname", OleDbType.VarChar, 10, "stuname")
insertcmd.Parameters.Add("@grade", OleDbType.VarChar, 10, "grade")
adapter.InsertCommand = insertcmd
'添加update命令参数
Dim updatecmd As New OleDbCommand("Update stutbl set sex=?,stuname=?,grade=? Where ID=?", conn)
updatecmd.Parameters.Add("@sex", OleDbType.VarChar, 1, "sex")
updatecmd.Parameters.Add("@stuname", OleDbType.VarChar, 10, "stuname")
updatecmd.Parameters.Add("@grade", OleDbType.VarChar, 10, "grade")
updatecmd.Parameters.Add("@ID", OleDbType.VarChar, 10, "ID").SourceVersion = DataRowVersion.Original
adapter.UpdateCommand = updatecmd
'添加delete命令参数
Dim deletecmd As New OleDbCommand("Delete * FROM stutbl Where ID=?", conn)
deletecmd.Parameters.Add("@ID", OleDbType.BigInt, Nothing, "ID").SourceVersion = DataRowVersion.Original
adapter.DeleteCommand = deletecmd
Dim dstchanges As DataSet
'检查数据是否发生更改
If dst.HasChanges Then
'将更改的行保存到新数据集中
dstchanges = dst.GetChanges()
'将更改显示到窗口
DataGridView2.DataSource = dstchanges.Tables("mytbl")
'检查发生更改的行是否有错误
If dstchanges.HasErrors() Then
'拒绝更改
dst.RejectChanges()
Else
'更新数据库
adapter.Update(dstchanges, "mytbl")
End If
End If
'关闭数据库
conn.Close()
End Sub
End Class
代码解读:
1、操作数据表部分代码,如添加新行、更新行数据、删除行等可以不用,直接在DataGridView控件修改数据即可即时反映回数据集。写这部分代码主要是为了做笔记。
需要特别注意的是删除行的方法有两个:Delete方法和Remove方法。
Delete方法只是把该行的行状态修改为Deleted,此行还在数据集中,并没有真正的删除。可以使用DataAdapter的update方法来删除数据库中对应的这一行。
Remove方法是真的把该行从数据集移除了,此行真的不在数据集中,使用DataAdapter的update方法时不会删除数据库中对应的这一行的。
2、为命令添加参数的语句,如
updatecmd.Parameters.Add("@sex", OleDbType.VarChar, 1, "sex")
第一个参数"@sex"是Parameter的名称,可以自由发挥,但为了更好解读代码,最好对应数据集中的列名,最后一个参数"sex"是指这个参数的值从数据集中的"mytbl"表的sex列取值,这个不是数据库中的列,需要特别注意。
3、添加update命令和delete命令参数时需要设置主键列的SourceVersion属性值为DataRowVersion.Original,这是表示取从数据库加载时的值,也就是添加到数据集中最原始的值。因为可能在数据集中修改了该主键的值,这样的话就没办法在数据库找到该主键值或找错,造成更新出错。相关内容可以参阅官网帮助《行状态和行版本》
二、OleDbCommandBuilder 对象自动生成更新命令的SQL语句
看着为命令添加参数的代码就头痛,好哆嗦,其实也有简洁的方法。
可以把这一长串代码:
'添加insert命令参数
Dim insertcmd As New OleDbCommand("Insert INTO stutbl(sex,stuname,grade) VALUES (?,?,?)", conn)
insertcmd.Parameters.Add("@sex", OleDbType.VarChar, 1, "sex")
insertcmd.Parameters.Add("@stuname", OleDbType.VarChar, 10, "stuname")
insertcmd.Parameters.Add("@grade", OleDbType.VarChar, 10, "grade")
adapter.InsertCommand = insertcmd
'添加update命令参数
Dim updatecmd As New OleDbCommand("Update stutbl set sex=?,stuname=?,grade=? Where ID=?", conn)
updatecmd.Parameters.Add("@sex", OleDbType.VarChar, 1, "sex")
updatecmd.Parameters.Add("@stuname", OleDbType.VarChar, 10, "stuname")
updatecmd.Parameters.Add("@grade", OleDbType.VarChar, 10, "grade")
updatecmd.Parameters.Add("@ID", OleDbType.VarChar, 10, "ID").SourceVersion = DataRowVersion.Original
adapter.UpdateCommand = updatecmd
'添加delete命令参数
Dim deletecmd As New OleDbCommand("Delete * FROM stutbl Where ID=?", conn)
deletecmd.Parameters.Add("@ID", OleDbType.BigInt, Nothing, "ID").SourceVersion = DataRowVersion.Original
adapter.DeleteCommand = deletecmd
用如下代码替换:
Dim builder As OleDbCommandBuilder = New OleDbCommandBuilder(adapter)
builder.GetUpdateCommand()
builder.GetDeleteCommand()
builder.GetInsertCommand()
OleDbCommandBuilder 对象可以自动生成OleDbDataAdapter对象的DeleteCommand 、 InsertCommand和UpdateCommand 命令 。
但使用OleDbCommandBuilder时需注意:
1、数据集必须是单个数据库表中生成,也就是说只能更新单个表。
2、为了能够自动生成命令,必须设置 SelectCommand 属性,这是最低要求。SelectCommand 还必须至少返回一个主键或唯一列。 如果不存在任何主键和唯一列,则会生成 InvalidOperation 异常,并且不会生成命令。
3、如果您在自动生成 INSERT、UPDATE 或 DELETE 命令后修改 SelectCommand,则可能会发生异常。为避免出错,可以通过调用 OleDbCommandBuilder 的 RefreshSchema 方法来刷新由OleDbCommandBuilder。
三、将数据库表架构填充到数据集中
连续使用OleDbDataAdapter对象Fill方法填充数据集,因为没有主键(唯一)约束,刷新数据时可能会出现数据重复,如:
可以先使用OleDbDataAdapter对象FillSchema方法自动将数据库中的表架构填充到数据集中,然后再用Fill方法将数据填充到数据集。
adapter.FillSchema(dst.Tables("mytbl"), SchemaType.Mapped)
adapter.Fill(dst, "mytbl")
有了主键约束就不会出现重复数据了,但这只能用于单表查询结果集,如果是多表查询结果集只能自定义数据集中的表架构了。
其实也有简单方法:先使用DataTable的Clear方法清除表内的所有数据,然后再调用OleDbDataAdapter对象Fill方法重新填充(即刷新数据)DataTable,这样就不会有重复数据出现了,如:
dst.Tables("mytbl").Clear()
adapter.Fill(dst, "stutbl")
使用DataSet对象的Clear方法可以清除DataSet数据集中所有表内的所有数据行。
四、自定义表构架并将数据库数据填充到数据集
Public Class Form2
Private Const strconn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=F:\test.accdb"
Private dst As DataSet
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dst = New DataSet()
'添加表
Dim dt As DataTable = dst.Tables.Add("mytbl")
'添加列
dt.Columns.Add("stsex", Type.GetType("System.String"))
dt.Columns.Add("stgrade", Type.GetType("System.String"))
dt.Columns.Add("stname", Type.GetType("System.String"))
'添加自增长和主键
Dim coldi As DataColumn = dt.Columns.Add("di", Type.GetType("System.Int32"))
coldi.AutoIncrement = True '设置为自增长列
'添加主键约束
Dim columns(0) As DataColumn
columns(0) = coldi
dt.PrimaryKey = columns
'自定义DataGridView列
DataGridView1.AutoGenerateColumns = False '不自动创建列
DataGridView1.DataSource = dt '数据源
Dim colID As New DataGridViewTextBoxColumn()
colID.DataPropertyName = "di"
colID.HeaderText = "编号"
colID.Name = "ID"
DataGridView1.Columns.Add(colID)
Dim colname As New DataGridViewTextBoxColumn()
With colname
.DataPropertyName = "stname"
.HeaderText = "姓名"
.Name = "stuname"
End With
DataGridView1.Columns.Add(colname)
Dim colsex As New DataGridViewTextBoxColumn()
With colsex
.DataPropertyName = "stsex"
.HeaderText = "性别"
.Name = "stusex"
End With
DataGridView1.Columns.Add(colsex)
Dim colgrade As New DataGridViewTextBoxColumn()
With colgrade
.DataPropertyName = "stgrade"
.HeaderText = "年级"
.Name = "stugrade"
End With
DataGridView1.Columns.Add(colgrade)
End Sub
'查询数据库填充数据集并显示到窗体
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'连接数据库
Dim conn As New OleDbConnection(strconn)
'打开数据库
conn.Open()
'实例化OleDbDataAdapter
Dim strSQL As String = "SELECT studenttbl.ID as di,studenttbl.stuname as stname, sextbl.sex as stsex, gradetbl.grade as stgrade" _
& " FROM sextbl,studenttbl,gradetbl" _
& " WHERE (([studenttbl].[gradeID]=[gradetbl].[gradeID]) AND ([studenttbl].[sexID]=[sextbl].[sexID]));"
Dim adapter As New OleDbDataAdapter(strSQL, conn)
'填充数据到DataSet
dst.Tables("mytbl").Columns("di").AutoIncrement = False
adapter.Fill(dst, "mytbl")
dst.Tables("mytbl").Columns("di").AutoIncrement = True
'关闭数据库
conn.Close()
End Sub
End Class
将数据集声明为公有变量并在加载窗体时自定义表架构,特别注意的是在设置SelectCommand命令的SQL语句时,查询结果集各列名称要与数据集定义的列名称相同。
五、使用ColumnMappings关联OleDbDataAdapter与DataTable间的列
如果查询结果集各列名称与数据集定义的列名称不相同的话,还可以这样处理:使用OleDbDataAdapter的TableMappings属性来关联DataSet中DataTable名称与OleDbDataAdapter源表名称,然后再使用DataTableMapping的ColumnMappings属性来关联DataTable与OleDbDataAdapter各列名称。
把按钮Button1的单击事件代码修改如下:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'连接数据库
Dim conn As New OleDbConnection(strconn)
'打开数据库
conn.Open()
'实例化OleDbDataAdapter
Dim strSQL As String = "SELECT studenttbl.ID,studenttbl.stuname, sextbl.sex, gradetbl.grade" _
& " FROM sextbl,studenttbl,gradetbl" _
& " WHERE (([studenttbl].[gradeID]=[gradetbl].[gradeID]) AND ([studenttbl].[sexID]=[sextbl].[sexID]));"
Dim adapter As New OleDbDataAdapter(strSQL, conn)
Dim mapping As System.Data.Common.DataTableMapping = adapter.TableMappings.Add("stutbl", "mytbl") '关联表名
'关联列名
mapping.ColumnMappings.Add("ID", "di")
mapping.ColumnMappings.Add("stuname", "stname")
mapping.ColumnMappings.Add("sex", "stsex")
mapping.ColumnMappings.Add("grade", "stgrade")
'填充数据到DataSet
adapter.Fill(dst, "stutbl")
'关闭数据库
conn.Close()
End Sub
写在最后:
1、调用OleDbDataAdapter对象Fill方法时,不需要手工打开OleDbConnection。
如果OleDbDataAdapter对象的SelectCommand属性的OleDbConnection已经关闭,OleDbDataAdapter将自动打开连接、提交查询、提取结果,然后关闭OleDbConnection。但是如果在调用Fill方法之前已经打开OleDbConnection,调用以后OleDbConnection仍然处于打开状态。
如果用多个OleDbDataAdapter对象将多个查询的结果填充到DaraSet中,不手动打开OleDbConnection的话,每次都调用OleDbDataAdapter对象的Fill方法都会打开和关闭OleDbConnection,为了避免重复地多次打开和关闭OleDbConnection对象,在对OleDbDataAdapter对象调用Fill方法之前,先调用OleDbConnection对象的Open方法。填充数据结束后调用OleDbConnection对象的Close方法关闭OleDbConnection。如:
'打开数据库
conn.Open()
adapter.Fill(dst, "stutbl")
adapter1.Fill(dst, "stutbl")
'关闭数据库
conn.Close()
2、ACCESS每次只能执行一条SQL语句,所以当然不会支持逻辑语句,也就写不出真正意义的存储过程。
如果试图在SelectCommand等属性里设置多条SQL语句会报错,提示“System.Data.OleDb.OleDbException:“在 SQL 语句结尾之后找到字符。”。如图:
试图进行多表查询然后更新有一定的难度,不懂该如何写SQL语句。