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

Jef*_*rke 1 vb.net select dataset

更新:

所以我能够取得一些进展,我让这段代码可以工作......我在评估 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
Run Code Online (Sandbox Code Playgroud)

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

非常简单,我的数据集和适配器已经在我的 Form.XSD 中设置,并在我的 Code.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")
Run Code Online (Sandbox Code Playgroud)

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

我每天都在学习更多知识,但仍然觉得尝试完成这些简单的任务很愚蠢。

Cai*_*ard 5

是的,你\xe2\x80\x99有点困惑,但很容易看出原因,而且我清楚地记得几年前我处于同一位置时

\n\n

DataSet 是DataTable 对象的集合。它还包含并管理数据关系

\n\n

DataTable 是DataRow 对象的集合和DataColumn 对象的集合。

\n\n

DataRow 是值、字符串、整数、日期时间等的集合。DataColumn 用于将 DataRow 上的特定值限制为某种类型。您可以拥有一个 DataRow 并向其询问某些 DataColumn 引用的数据

\n\n

\xe2\x80\x94\xe2\x80\x94\xe2\x80\x94-

\n\n

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

\n\n

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

\n\n

将任何应用程序分成多个层是一个聪明的想法,其中一个层纯粹处理在某处保存数据并对其进行建模。数据集/表/等实现了这一点,因为您可以拥有一组名称甚至与数据库不匹配的表,但是如何从一个映射到另一个的信息都在那里 - 您的个人数据表可以有映射到数据库中 Surname 列的 FamilyName 列。几年后,有人决定迁移到新的数据库服务器,重命名列等,您更改数据集中的映射,以便 FamilyName 现在引用名为 LastName 的列。您的代码继续使用 FamilyName,并且该集曾经将其映射到 Surname 的数据库列,现在它使用 LastName。这就是为什么我们要分层;最大限度地减少该层内所需的更改

\n\n

\xe2\x80\x93\xe2\x80\x93\xe2\x80\x93\xe2\x80\x93

\n\n

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

\n\n

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

\n\n
Dim r = connection.ExecuteReader("SELECT * FROM Person")\nWhile(r.HasRows)\n    Dim dt = myDS.Tables("Person")\n    Dim ro = dt.NewRow()\n    ro("Name") = r.GetString(1)\n    ro("NumberChildren") = r.GetInt(2)\n    ro("BirthDate") = r.GetDateTime(3)\nEnd While\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n

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

\n\n
Dim dt as New DataTable\nDim da as New DataAdapter("SELECT...","connstr")\nda.Fill(dt) \'it\'ll autocreate columns etc\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
myDataTable.Rows(0).Item("Name")\n
Run Code Online (Sandbox Code Playgroud)\n\n

它是一个对象。它是对象内部的字符串,但要将其用作字符串,您必须对其进行强制转换。您需要使用字符串“Name”来访问它。如果您输入错误,智能感知将无法帮助您;你会遇到崩溃,提示“Naem 不是该数据表的成员”。因此,所有这些仍然是非常低级别的,我们\xe2\x80\x99re访问像他们\xe2\x80\x99re数组一样的行,必须记住第一个名称位于位置1,或者使用列名称的字符串和铸件:

\n\n
myPerson.Name = DirectCast(myDataTable.Rows(0).Item("Name") as String)\nmyPerson.BirthDate = DirectCast(myDataTable.Rows(0).Item(2) as DateTime)\n
Run Code Online (Sandbox Code Playgroud)\n\n

真让人头疼;这并不是我们使用现代类型安全语言(如 VB/C# 等)所签署的,代码丑陋得像罪孽一样,而且我们没有从 VS 获得任何有关代码的帮助;如果列名称\xe2\x80\x99 位于字符串中,则 Intellisense 不擅长建议列名称

\n\n

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

\n\n
Class PersonDataRow Extends DataRow\n  Property fullName() As String\n    Get\n      If this.Item("Person") IsNot Nothing Then\n          Return DirectCast(this.Item("Name"), String)\n      Else\n          Return Nothing\n      End If\n\n    End Get\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么我们可以说:

\n\n
myPerson.Name = myPersonDataRow.Name;\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者甚至更好;只需使用 PersonDataRow 作为我们程序中存储人员的实体,并完全抛弃其他 Person 类。我们可以花几天时间为每个项目编写所有这些样板文件,而不会在其他地方出现混乱的代码

\n\n

如果在此之上有一层为我们编写所有这些无聊的样板就好了……

\n\n

\xe2\x80\x94\xe2\x80\x94\xe2\x80\x94\xe2\x80\x94\xe2\x80\x94\xe2\x80\x94

\n\n

输入数据集设计器,即您所说的 XSD。这是一个内置于 Visual Studio 中的可视设备,可以连接到数据库,并帮助您使用已完成的所有样板制作自定义 DataTable,并且 It\xe2\x80\x99ll 创建称为 TableAdapters 的东西(这是基本 DataAdapter 的包装器)在数据库和数据集之间来回推送数据。

\n\n

所以这就是关键点;数据集设计者制作的这些 DataTabke 不是像我之前讨论的那样的基本低级数据;它们具有这些额外的属性和功能,可以在更高级别的数据管理上执行操作。表中的每一列都有一个属性,用于转换并返回基础行的值。而不是拥有一个 DataTable,它是 DataRow 的集合,其中包含大量必须转换回字符串和日期时间等的对象,您\xe2\x80\x99将拥有一个具有 PersonDataRows 的 PersonDataTable,并且 PersonDataRows 具有 Intellisense 可以读取的属性,例如姓名和出生日期。只需点击几下鼠标并选择一些名称、数据类型等以及值为空时会发生什么,就可以编写所有这些内容。然后你的代码可以来自:

\n\n
Dim bd as DateTime = DirectCast(myDT.Rows(0).Item("birhdate"), DateTime) \'generic weak typing, note the typo!\n
Run Code Online (Sandbox Code Playgroud)\n\n

\n\n
Dim bd as DateTime = myPersonDt(0).Birthdate \'first row, get the name\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用强类型数据集,代码变得更加整洁;PersonDataRow 有一个 .Birthdate 属性,它从行中提取基础日期,为您转换并返回它。

\n\n

如果您有强类型数据集,请尝试永远不要进入弱类型模式,即不要这样做:

\n\n
Dim myDT = myDs.Tables("Person")\n
Run Code Online (Sandbox Code Playgroud)\n\n

因为您必须将其转换为 PersonDataTable(它已经是 PersonDataTable,但它将被装箱为普通 DataTable)才能充分利用它:

\n\n
Dim myDT = DirectCast(myDs.Tables("Person"), PersonDataTable)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
Dim myDT = myDS.Person\n
Run Code Online (Sandbox Code Playgroud)\n\n

您甚至不需要创建临时变量。直接引用 DataSet 属性通常会更干净整洁:

\n\n
ForEach x as PersonDataRow in myDS.Person\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
myDS.Tables(0).Columns("Name") \'no; Tables(0) returns a DataTable - we just dropped to base types\nmyDS.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\nmyDS.Person.NameColumn \'yes; we stayed with the named properties all the way\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n

我之前提到过 TableAdapter。它们是增强版的 DataAdapter。它们旨在在数据库和现有或新的强类型数据表之间来回推送数据。它们被键入并与一种 DataTable 配对 - PersonDataTable 有一个匹配的 PersonTableAdapter。

\n\n

当您创建 TableAdapter 时,您可以通过 DataSet 设计器上的向导来完成它,除非您将数据库表的表示形式从数据源窗口拖到 DataSet 中(在这种情况下,它是在一些默认假设下创建的)

\n\n

在向导中,您通常会键入一个选择查询,从数据库中选择列或其子集,然后向导会创建一个代表您查询内容的本地数据表。此时,没有数据库移出数据库,向导只是查看了您的SELECT id, name, age, testpassdate FROM person WHERE id = @id查询,然后“确定”,这样就可以从名为 person 的表中获取一个 guid、一个字符串、一个整数、一个日期,它是查找 id(它是一个 guid),现在我有足够的信息来制作具有这些类型的 4 个属性的 DataTable,并填充参数集合以查询使用 guid 的数据库..并且因为选择了主键列,所以我可以还生成更新和删除查询...”

\n\n

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

\n\n
\'SELECT ... FROM Person WHERE id = @id\npersonTA.Fill(myDS.Person, SOME_GUID_HERE)\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是我们首先用来创建适配器的查询的表现形式。我们根据该查询完成了向导;它进行了一些选择和更新查询并将它们加载到表适配器中。您可以通过调试应用程序并检查 .SelectCommand、.UpdateCommand、.InsertCommand 等来查看它们

\n\n

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

\n\n

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

\n\n
personTA.FillByName(myDS.Person, "John Smith")\n
Run Code Online (Sandbox Code Playgroud)\n\n

最终,您将在不同的表适配器中汇集所有不同的查询,以使您的应用程序正常运行。我的一些表适配器有 20 个或更多查询。他们从表中选择相同的数据,但标准不同。有些甚至使用连接,例如 FillChildrenByParentName:

\n\n
SELECT child.* FROM person parent INNER JOIN person child ON child.ParentID = parent.ID WHERE parent.Name = @name\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

要点:TableAdapter 具有插入/更新/删除功能(如果您在向导的高级设置中勾选了“GenerateDBDirect 方法”),但这些功能并不是真正用于直接使用,除非您要删除/更新从未下载过的数据. 调用了使用表适配器将数据保存到 DB 的函数,Update但它

\n\n
    \n
  1. 获取 DataRow、DataRow 集合或 DataTable
  2. \n
  3. 查看数据行状态(已添加、已修改、已删除等)以确定是否应运行 INSERT、UPDATE 或 DELETE
  4. \n
  5. 运行适当的 SQL
  6. \n
\n\n

我希望他们称之为Save,但它是Update- 只要记住您使用 Fill 将数据下载到数据表中即可。您修改数据表,添加数据,删除数据,当您想要保留更改时,您可以myTableAdapter.Update(theDataTable)

\n\n
\n\n

现在,对您的代码进行快速批评..

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

然后你有这些尝试

\n\n
\'Try to Query the DataTable (Intelisense tells me Variable is used before assigning a value)\n\nDim cThisGroup() As DataRow = dtControl.Select("NPID = HCHRXMEDTIP")\n
Run Code Online (Sandbox Code Playgroud)\n\n

Intelisense 这么说是因为您将 dtControl 声明为类型变量,但没有给它赋值。在这方面,Vb 并不是一种真正出色的语言,因为很容易混淆 MyDatset.PersonDataTable(MyDataSet 类型对象中的 PersonDataTable 类型)和 MyDataSet.Person(名为 PersonDataTable 的实例) Person,位于名为 MyDataSet 的 MyDataSet 类型的实例内

\n\n

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

\n\n

黄金法则,添加表单上的所有内容后重命名它们!当您将数据集拖放到表单上时,vb 会执行以下操作

\n\n
Dim MyDataSet as New MyDataSet\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
Dim myDS as new MyDataSet\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

如果你这样做了,然后写了这样的内容:

\n\n
 Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable\n= myNBDS.FO_HealthcateHighways_Control\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
Dim dtControl As NewBenefitsDataSet.FO_HealthcateHighways_ControlDataTable\n= NewBenefitsDataSet.FO_HealthcateHighways_Control\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\'So then I tried this variation (tells me Select is not a member of the Adapter)\ncThisGroup = daControlTableAdapter.Select("NPID = HCHRXMEDTIP")\n
Run Code Online (Sandbox Code Playgroud)\n\n

确实,表适配器没有名为 Select 的函数,除非您通过删除向导中建议的“FillBy”并写入“Select”来告诉它您想要一个函数。\n具有参数的填充操作的默认名称是 fillBy。我建议您始终编辑名称以包含其填充内容,就像我调用我的 FillByName 时一样。我不建议您使用 select,因为它不会帮助您保持 DataTable(本地数据缓存)、数据库(远程数据存储)和表适配器角色之间的区别。没有帮助,因为你将有一个名为 select 的子项来填充提供的 DataTable,所以它是填充而不是选择。给事物命名不反映它们所做的事情是另一种快速混淆的方法

\n\n
\n\n

关于select的说明:Select是DataTable的一个函数,它将搜索DataTable中缓存的数据。它不会访问数据库。实际上,如果您已将整个数据库表下载到数据表中,则实际上只能使用它来查找数据库中的数据。不要这样做。制作您的表适配器,使其具有一个新的 SELECT 查询,该查询选择数据库行的一小部分,将其称为 fillByX,并仅填充您需要的行

\n\n

如果这篇文章的任何部分难以理解,我们深表歉意。它是在一台旧 iPad 上编写的,它的自动更正功能非常不稳定,而且输入延迟很大,所以如果有什么地方不明白,请告诉我,我会修复它。

\n