如何在 Visual Basic 中使用数据集 TableAdapter 查询和选择记录



>更新:

所以我能够取得一些进展,我让这段代码工作......我在评估 FOUND 条件时遇到问题,所以把它包裹在一个 For 循环中,你们都怎么看?

Dim ControlRow = NewBenefitsDataSet.Tables("FO_HealthcateHighways_Control").Select("NGID = 'HCHRXMEDTIP'")
For Each Row As DataRow In ControlRow
Do
'Check if Already Processed, if so skip to next record
If Row("NGID") = "HCHRXMEDTIP" Then
MessageBox.Show("FOUND: " + Row("NGID"))
Exit Do
Else
MessageBox.Show("GROUP NOT FOUND: " + Row("NGID"))
Return False
End If
Loop
Next

首先,我喜欢这个社区,我一直在努力从Visual Foxpro过渡到VB来获得正确的语法。 无论我搜索多少,我都会看到十几种不同的变体和示例,但发现很难找到我正在做的事情的神奇组合。

所以很简单,我的数据集和适配器已经在我的 Form.XSD 和我的代码中设置.VB我想查询记录,但不确定如何正确编码它......

我在这里做错了什么?再次提前感谢。

Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable
Dim drControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlRow
Dim daControlTableAdapter As New NewBenefitsDataSetTableAdapters.FO_HealthcateHighways_ControlTableAdapter
'Fill Control Table Dataset
daControlTableAdapter.Fill(NewBenefitsDataSet.FO_HealthcateHighways_Control)
'Try to Query the DataTable (Intelisense tells me Variable is used before assigning a value)
Dim cThisGroup() As DataRow = dtControl.Select("NPID = HCHRXMEDTIP")
'So then I tried this variation (tells me Select is not a member of the Adapter)
cThisGroup = daControlTableAdapter.Select("NPID = HCHRXMEDTIP")

所以我想我已经很接近了,只是无法找到一个好的例子来正确编码它。

我每天都在学习更多,但仍然觉得做这些简单的任务很愚蠢。

是的,你有点困惑,但很容易理解为什么,我清楚地记得几年前我处于同一职位时

数据集是数据表对象的集合。它还包含和管理数据关系

数据表是 DataRow 对象的集合和 DataColumn 对象的集合。

DataRow 是值、字符串、整数、日期时间等的集合。DataColumns 用于将 DataRow 上的特定值约束为类型。您可以拥有一个数据行,并要求它提供某些数据列引用的数据

———-

这些都与SQL Server或Access(或Foxpro ;))等数据库无关。数据库有表,表有行,行有单元格,单元格有值,列是类型化的等等......所以它看起来确实有点像数据集和数据库是相同/相似的东西,但重要的是要记住,它们只是基于相同的概念建模,并且具有使它们工作的位的相似名称

因此,DataSet/DataTable/DataRow/DataColumn 基础结构模仿数据库的布局方式,但您必须在头脑中绝对清楚:它们不是数据库。您可以使用数据表和数据关系创建数据集,并使用数据加载表,并从磁盘保存/加载,而无需查看 SQL Server 等数据库。DataSet 是数据的本地缓存,是一组对象,其设计意图是将数据本地存储在您的应用程序中并允许某些类似数据库的行为,不是因为它们试图成为数据库,而是因为这些行为允许对相关数据进行合理的建模,通过模拟数据库,这意味着它们对部分数据库数据进行合适的本地缓存 - 您可以同时拥有数据集和一个数据库,并将部分数据从数据库下载到数据集中,编辑它,发回它,将新数据添加到 DS,也发送它。

将任何应用程序拆分为多个层是一个聪明的主意,有一个层纯粹处理将数据保存在某处并对其进行建模。数据集/表/等之所以能够做到这一点,是因为您可以拥有一组名称甚至与数据库不匹配的表,但是有关如何将 mone 映射到另一个的信息就在那里 - 您的个人数据表可以有一个映射到数据库中的姓氏列的姓氏列。几年后,有人决定移动到新的数据库服务器,重命名列等,您更改数据集中的映射,以便 FamilyName 现在引用名为 LastName 的列。您的代码继续使用 FamilyName,并且该集曾经将其映射到 Surname 的数据库列,现在它使用 LastName。这就是为什么我们分层;以尽量减少更改,只需在该层内

––––

现在,框架中存在一个名为DataAdapter的设备 - 它知道如何使用DbCommand/DbConnection从数据库表中填充DataTable。它是对DataReader(不知道DataSets/Tables)的抽象。如果您使用 DataReader 访问数据库,则必须在循环中自己将数据戳入 DataTable,这非常乏味。将 DataReader 视为最低的;有点像想要编写一个3D游戏,并且必须自己在显示器上绘制,而不是使用OpenGL之类的东西。它们有其用途,但主要是如果您想要对数据进行快速、只读访问并且您不想存储结果。例如,假设从数据库表动态生成 CSV,并立即将 CSV 数据写入网络连接。在制作 CSV 并发送之前,您不必从数据库缓存整个 1Gb 表并占用服务器内存负载;您可以在几千字节的内存中逐行执行

用于将人员从数据库中提取并将他们放入数据表中的 DataReader 代码可能如下所示:

Dim r = connection.ExecuteReader("SELECT * FROM Person")
While(r.HasRows)
Dim dt = myDS.Tables("Person")
Dim ro = dt.NewRow()
ro("Name") = r.GetString(1)
ro("NumberChildren") = r.GetInt(2)
ro("BirthDate") = r.GetDateTime(3)
End While

是的。我宁愿把别针插在眼睛里,也不愿以此为生。我什至不会把它交给初级开发人员


从低谷中低端向上一步;DataAdapter 将选择和更新代码减少到几行,但它仍然以非常通用的方式运行:

Dim dt as New DataTable
Dim da as New DataAdapter("SELECT...","connstr")
da.Fill(dt) 'it'll autocreate columns etc

我所说的通用是指表中的数据存储为基本对象;甚至不像字符串那样复杂。你称之为:

myDataTable.Rows(0).Item("Name")

它是一个对象。它是对象内部的字符串,但要将其用作字符串,您必须强制转换它。您需要使用字符串"名称"来访问它。如果您输入错误,智能感知将无济于事;你只会得到一个崩溃,说"'Naem'不是这个数据表的成员"。所以所有这些仍然是非常低的级别,我们像访问数组一样访问行,必须记住名字位于位置 1,或者使用列名和强制转换的字符串:

myPerson.Name = DirectCast(myDataTable.Rows(0).Item("Name") as String)
myPerson.BirthDate = DirectCast(myDataTable.Rows(0).Item(2) as DateTime)

真令人头疼; 这不是我们使用 VB/C# 等现代类型安全语言注册的,代码丑陋如罪,我们没有得到 VS 代码的任何帮助;智能感知不擅长建议列名,如果它们在字符串中

所以。。。超级数据集等是这些复杂的数据缓存,但它感觉有点像一个倒退步骤,一切都是对象并通过给出一个字符串来访问。我们可以通过扩展 DataRow 并使其自定义来编写一些样板:

Class PersonDataRow Extends DataRow
Property fullName() As String
Get
If this.Item("Person") IsNot Nothing Then
Return DirectCast(this.Item("Name"), String)
Else
Return Nothing
End If
End Get

然后我们可以说:

myPerson.Name = myPersonDataRow.Name;

甚至更好;只需使用 PersonDataRow 作为我们程序中存储人员的实体,并一起抛弃另一个 Person 类。我们可以花几天时间为每个项目编写所有这些样板,并且永远不会在其他地方使用凌乱的代码

要是上面有一层为我们写了所有这些无聊的样板就好了......

——————

进入数据集设计器,即所谓的 XSD。这是一个内置在Visual Studio中的可视化设备,可以连接到数据库,并帮助你在所有这些样板已经完成的情况下创建自定义数据表,它将创建称为TableAdapters的东西(它是基本DataAdapter的包装器)以在数据库和数据集之间来回推送数据。

所以这是踢球者;DataSet 设计器制作的这些 DataTabkes 不是我之前讨论的基本低级 DataTabkes;它们具有这些额外的属性和函数,可以在更高级别的数据管理中执行操作。表中的每一列都有一个属性,该属性强制转换并返回基础行中的值。而不是一个DataTable,它是DataRow的集合,其中包含必须转换回字符串和日期时间等的对象负载,您将拥有一个具有PersonDataRows的PersonDataTable,并且PersonDataRows具有Intellisense可以读取的属性,如Name和Birthdate。这一切都是通过点击几下并选择一些名称、数据类型等以及当值为 null 时会发生什么而编写的。然后,您的代码可以从:

Dim bd as DateTime = DirectCast(myDT.Rows(0).Item("birhdate"), DateTime) 'generic weak typing, note the typo!

Dim bd as DateTime = myPersonDt(0).Birthdate 'first row, get the name

对于强类型数据集,代码会整齐;有一个.PersonDataRow 的 birthdate 属性,该属性从行中提取基础日期,为您强制转换并返回它。

如果你有一个强类型数据集,尽量不要进入弱类型模式,即不要这样做:

Dim myDT = myDs.Tables("Person")

因为您必须将其强制转换为 PersonDataTable(它已经是一个 PersonDataTable,但它将被装箱为普通数据表)才能充分利用它:

Dim myDT = DirectCast(myDs.Tables("Person"), PersonDataTable)

开始变得不必要的混乱,不必要的因为...你猜对了..有一个强类型数据集的属性,它将返回 PersonDataTable 作为完全正确的 PersonDataTable 类型:

Dim myDT = myDS.Person

你甚至不需要制作 temp 变量......它通常只会更干净、更整洁地直接引用 DataSet 属性:

ForEach x as PersonDataRow in myDS.Person

同样,不要访问 .Rows 属性 - 它返回 DataRow 的集合,而不是 PersonDataRow 的集合。每当智能感知告诉您正在访问的属性返回一个普通的 DataTable、DataRow 等时,请改为查找返回强类型的命名属性:

myDS.Tables(0).Columns("Name") 'no; Tables(0) returns a DataTable - we just dropped to base types
myDS.Person.Columns("Name") 'no; we started well, got a PersonDataTable type out vis the .Person property of the dataset, but then went and accessed Columns("Name") which returns a DataColumn so we're back in base type land
myDS.Person.NameColumn 'yes; we stayed with the named properties all the way

我在这里不断回到同样的事情 - 尽可能努力避免放弃使用强类型数据集的强名称属性。回到低水平没有任何好处


我之前提到了 TableAdapters。他们是类固醇的数据适配器。它们旨在在数据库与现有或新的强类型数据表之间来回推送数据。它们被键入并与一种 DataTable 配对 - PersonDataTable 具有匹配的 PersonTableAdapter。

创建 TableAdapter 时,可以通过数据集设计器上的向导执行此操作,除非您将数据库表的表示形式从数据源窗口拖到数据集中(在这种情况下,它是使用一些默认假设进行的)

在向导中,您通常键入一个选择查询,该查询从数据库中选取列或其子集,向导会制作一个表示所查询内容的本地 DataTable。此时没有数据库移出数据库,向导只是查看了您的SELECT id, name, age, testpassdate FROM person WHERE id = @id查询并走了"好的,所以这是一个 guid、一个字符串、一个 int、一个日期,来自一个名为 person 的表,它正在查找 id 这是一个 guid,现在我有足够的信息来制作一个具有这些类型的 4 个属性的 DataTable, 并填充参数集合以查询使用 GUID 的数据库。并且由于选择了主键列,我还可以生成更新和删除查询...">

因此,您最终会得到一个环绕内部 DataAdapter 的 PersonTableAdapter,它需要一个 PersonDataTable 进行填充和更新操作,您可以在代码中使用它,如下所示:

'SELECT ... FROM Person WHERE id = @id
personTA.Fill(myDS.Person, SOME_GUID_HERE)

这是我们最初用于创建适配器的查询的表现形式。我们根据该查询完成了向导;它进行了一些查询来选择和更新,并将它们加载到表适配器中。您可以通过调试应用程序并签出 .选择命令、 .更新命令, .插入命令等

那么,如果您想按姓名查询数据库中的人员怎么办?这个只能通过 ID 做。

右键单击数据集中的 TableAdapter,添加SELECT whatever FROM person WHERE name =@name查询,并告诉向导您要将其命名为 FillByName。在代码中执行以下操作:

personTA.FillByName(myDS.Person, "John Smith")

最终,你将在不同的表适配器中将所有不同的查询放在一起,以使你的应用正常工作。我的一些表适配器有 20 个或更多查询。他们从表中选择相同的数据,但采用不同的条件。有些甚至使用联接,如FillChildrenByParentName:

SELECT child.* FROM person parent INNER JOIN person child ON child.ParentID = parent.ID WHERE parent.Name = @name

因为我们只选择所有子记录 (child,*),所以我们仍然有一组来自 Person 的列,没有额外的父信息。这是适合PersonDataTable的有效数据集,这意味着数据层可以满足"用户应有权检索属于名为X的人的所有子项的列表"的业务要求

重要提示:TableAdapters 具有插入/更新/删除功能(如果您在向导的高级设置中勾选了 GenerateDBDirect 方法"),但除非您删除/更新从未下载的数据,否则这些功能实际上并不打算直接使用。使用表适配器将数据保存到数据库的函数称为Update但它

获取数据行
  1. 、数据行集合或数据表
  2. 查看数据行状态(已添加、已修改、已删除等),以确定它是否应该运行插入、更新或删除
  3. 运行相应的 SQL

我希望他们称之为Save,但它Update- 请记住,您将数据下载到带有 Fill 的数据表中。您可以修改数据表,向其添加数据表,从数据表中删除数据表,以及何时要保留更改myTableAdapter.Update(theDataTable)


现在,快速批评一下您的代码。

''Don't need this, but you'd benefit from renaming the DataTable to have a shorter name like NBDS.Control
Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable
''Don't need this either
Dim drControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlRow
''Again, call your dataset Nbds perhaps, and call the TA ControlTableAdapter
Dim daControlTableAdapter As New NewBenefitsDataSetTableAdapters.FO_HealthcateHighways_ControlTableAdapter
''Caution.. this probably my fills the whole database into the dataset
''Don't do this; put parameters into the query to restrict the data that comes down from the db     
''ie add a query to the TA of SELECT * FROM control WHERE NPID = @npid daControlTableAdapter.Fill(NewBenefitsDataSet.FO_HealthcateHighways_Control)
''Then do this
daControlTableAdapter.FillByNpid(NewBenefitsDataSet.FO_HealthcateHighways_Control, "HCHRXMEDTIP" )

然后你有这些尝试

'Try to Query the DataTable (Intelisense tells me Variable is used before assigning a value)
Dim cThisGroup() As DataRow = dtControl.Select("NPID = HCHRXMEDTIP")

Intelisense 之所以这样说,是因为您将 dtControl 声明为类型化变量,但没有给它一个值。在这方面,Vb 并不是一门很好的语言,因为它很容易混淆 MyDatset.PersonDataTable,前者是 MyDataSet 类型对象中的一种 PersonDataTable 类型,后者是名为 Person 的 PersonDataTable 的实例,位于名为 MyDataSet 的 MyDataSet 类型的实例中。

困惑?这完全是 vb 的错,它允许我们创建变量与类型同名的对象实例,然后 Windows 窗体团队错误地使表单上的新数据集实例与类型同名。其他语言(如 c#)不允许创建与类型同名的类型实例

黄金法则,添加后重命名表单上的所有内容!当您将数据集放在表单上时,vb 确实如此

Dim MyDataSet as New MyDataSet

重命名表单上数据集的名称,使其称为myDS等,以便在幕后vb正在执行

Dim myDS as new MyDataSet

这意味着您永远不会混淆对象的类型和对象实例的名称

。如果你这样做了,然后写了这个:

Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable
= myNBDS.FO_HealthcateHighways_Control

它会起作用的。实际上这是有效的:

Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable
= NewBenefitsDataSet.FO_HealthcateHighways_Control

但是它令人困惑的是*&!#,因为第一个NewBenefitsDataSet是一个类型,第二个NewBenefitsDataSet是一个实例,他两个是非常不同的东西。始终避免将变量命名为其类型完全相同的名称

'So then I tried this variation (tells me Select is not a member of the Adapter)
cThisGroup = daControlTableAdapter.Select("NPID = HCHRXMEDTIP")

确实,tableadapter 没有名为 Select 的函数,除非您通过删除向导中建议的"FillBy"并编写"Select"来告诉它您想要一个函数。具有参数的填充操作的默认名称为 fillBy。我建议您始终编辑名称以包含其填充的内容,就像我称我的 FillByName 时一样。我不建议你使用select,因为它不会帮助你在DataTable(本地数据的缓存),数据库(远程数据的存储)和tableadpater的角色之间保持心理上的区别。它无济于事,因为您将有一个名为 select 的子填充提供的数据表,因此它填充而不是选择。称事物为不反映其作用的名称是另一种快速混淆的方法


关于选择的注意事项:选择是数据表的一个功能,它将搜索数据表中缓存的数据。它不会命中数据库。实际上,如果您已将整个数据库表下载到DataTable中,则只能使用它来查找数据库中的数据。别这样。将表适配器创建为具有新的 SELECT 查询,该查询选择数据库行的一小部分,将其称为 fillByX,并仅填充所需的行

如果这篇文章的任何部分难以理解,我们深表歉意。它是在一台旧的iPad上编写的,该iPad具有非常不稳定的自动更正和大量的输入延迟,所以如果有任何没有意义的地方,请告诉我,我会修复它

最新更新