Да, вы немного сбиты с толку, но очень легко понять почему, и я хорошо помню, когда несколько лет назад находился в том же положении
DataSet - это набор объектов DataTable. Он также содержит и управляет DataRelations
. DataTable - это коллекция объектов DataRow и коллекция объектов DataColumn.
DataRow - это коллекция значений, строк, целых чисел, даты и т. Д. c. DataColumns служат для ограничения определенного значения в DataRow типом. Вы можете иметь DataRow и запрашивать его для данных, на которые ссылается какой-либо DataColumn
———-
Ничто из этого не имеет ничего общего с базой данных, такой как SQL Сервер или Доступ (или Foxpro;)). У баз данных есть таблицы, у таблиц есть строки, у строк есть ячейки, у ячеек есть значения, столбцы набраны и т. Д. c ... Таким образом, это выглядит как DataSet, а база данных - это одно и то же / похожее, но важно помнить, что они Вы просто смоделированы по одним и тем же понятиям и имеете схожие имена для битов, которые заставляют их работать
Таким образом, инфраструктура DataSet / DataTable / DataRow / DataColumn имитирует расположение баз данных, но вы должны быть абсолютно ясны в уме : они не базы данных. Вы можете создать DataSet с DataTables и DataRelations, загрузить таблицы с данными и сохранить / загрузить с диска, даже не глядя на базу данных, такую как SQL Server. DataSet - это локальный кеш данных, набор объектов, целью которых является локальное хранение данных в вашем приложении и допускающее поведение, похожее на базу данных, не потому, что они пытаются быть базами данных, а потому, что поведение допускает разумное моделирование связанных данные, и, имитируя базу данных, это означает, что они создают подходящий локальный кеш частей данных вашей базы данных - вы можете иметь как набор данных, так и базу данных, и загрузить некоторую часть данных из базы данных в набор данных, отредактировать ее, отправить его обратно, добавить новые данные в ds, отправить это тоже ..
Это разумная идея разбить любое приложение на слои, имея слой, который имеет дело только с сохранением данных и их моделированием. DataSets / Tables / et c достигают этого, потому что у вас может быть набор таблиц с именами, которые даже не совпадают с БД, но информация о том, как отобразить mone на другое, - все это доступно для вашего человека. может иметь столбец FamilyName, который отображается на столбец фамилии в БД. Несколько лет спустя кто-то решает перейти на новый сервер БД, переименовать столбцы и т.д. c, вы измените отображение в наборе данных так, чтобы FamilyName теперь ссылался на столбец с именем LastName. Ваш код продолжает использовать FamilyName, и когда набор когда-то отображал его в столбце db Surname, он теперь использует LastName. Вот почему мы накладываем вещи; чтобы свести к минимуму изменения, которые должны быть только внутри этого слоя
––––
Теперь в структуре существует устройство, называемое DataAdapter - оно знает, как использовать DbCommand / DbConnection для заполнения DataTable из таблицы базы данных. Это абстракция над DataReader (который не знает о DataSets / Tables) ... если вы обращались к своей БД с помощью DataReader, вам пришлось бы вставлять данные в вашу DataTable самостоятельно в al oop, что очень утомительно. Думайте о DataReader как о самом низком из самых низких; немного похоже на желание написать 3D-игру и рисовать на мониторе самостоятельно, а не использовать что-то вроде OpenGL. Они имеют свое применение, но в основном это если вы хотите быстрый доступ к данным только для чтения и вы не хотите сохранять результат. Например, представьте, что вы генерируете CSV на лету из таблицы базы данных и немедленно записываете данные CSV в сетевое соединение. Вам не нужно кэшировать всю таблицу 1 Гб из БД и загружать серверную память, прежде чем создавать CSV и отправлять его; Вы делаете это построчно в нескольких килобайтах памяти.
Код 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 сокращает выбор и обновление кода до нескольких строк, но он все еще работает очень обобщенно c:
Dim dt as New DataTable
Dim da as New DataAdapter("SELECT...","connstr")
da.Fill(dt) 'it'll autocreate columns etc
По generi c Я имею в виду, что ваши данные в таблице хранятся как базовый объект; даже не так сложно, как быть строкой. Вы называете это:
myDataTable.Rows(0).Item("Name")
Это объект. Это строка внутри объекта, но чтобы использовать ее как строку, вы должны ее привести. И вам нужно получить к нему доступ, используя строку «Имя». Intellisense не поможет вам, если вы опечатаете его; вы просто получите cra sh, сказав, что «Naem» не является членом этой таблицы данных ». Так что все это на самом деле очень низкий уровень, и мы получаем доступ к строкам, как к массивам, должны помнить, что имя находится в позиции 1, или использовать строку с именем столбца и приведением:
myPerson.Name = DirectCast(myDataTable.Rows(0).Item("Name") as String)
myPerson.BirthDate = DirectCast(myDataTable.Rows(0).Item(2) as DateTime)
Что за головная боль; это не совсем то, на что мы подписались с современным безопасным типом языка, таким как VB / C# et c, код уродлив как грех, и мы не получаем никакой помощи с кодом от VS; Intellisense не годится для предложения имен столбцов, если они в строках
Итак ... Супер, наборы данных и др. - это сложные кеши данных, но это похоже на шаг назад, когда все является объектом и доступ к нему осуществляется давая строку. Мы могли бы написать несколько шаблонов, расширив 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 вместе. Мы могли бы потратить несколько дней на написание всей этой шаблонной таблицы для каждого проекта и никогда не иметь грязного кода в другом месте
Если бы над этим был слой, который написал бы всю эту скучную шаблонную информацию для нас ..
——————
Введите конструктор DataSet, который вы назвали XSD. Это визуальное устройство, встроенное в Visual Studio, которое может подключаться к базе данных и помогает вам создавать собственные DataTables со всеми этими образцами, которые уже сделаны, и создает вещи под названием TableAdapters (которые являются оболочкой для базового c DataAdapter) для pu sh данные туда и обратно между БД и набором данных.
Так вот кикер; эти DataTabkes, которые делает дизайнер DataSet, не являются базовыми c низкоуровневыми, как я обсуждал ранее; у них есть эти дополнительные свойства и функции, которые делают вещи на более высоком уровне управления данными. Каждый столбец в таблице имеет свойство, которое преобразует и возвращает значение из базовой строки. Вместо того, чтобы иметь DataTable, который является коллекцией DataRow, которая содержит множество объектов, которые вы должны преобразовать обратно в строки и даты и т. Д. c, у вас будет PersonDataTable, который имеет PersonDataRows и PersonDataRows имеют свойства, которые Intellisense может читать, например Имя и дата рождения. И все это написано с помощью нескольких щелчков мыши и выбора некоторых имен, типов данных и т. Д. c и того, что происходит, когда значения равны нулю. Тогда ваш код может go от:
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
С сильно типизированными наборами данных код подгоняется; есть свойство .Birthdate PersonDataRow, которое извлекает базовую дату из строки, приводит ее к вам и возвращает.
Если у вас строго типизированный набор данных, старайтесь никогда не переходить в слабо типизированный режим, ie не делайте этого:
Dim myDT = myDs.Tables("Person")
Поскольку вам придется привести его к PersonDataTable (это уже PersonDataTable, но он будет упакован в виде простого DataTable), чтобы максимально использовать его:
Dim myDT = DirectCast(myDs.Tables("Person"), PersonDataTable)
Начинать бесполезно, бесполезно, потому что ... вы догадались ... есть свойство строго типизированного DataSet, которое будет возвращать PersonDataTable как полностью правильный тип PersonDataTable:
Dim myDT = myDS.Person
Вам даже не нужно делать переменную temp ... обычно она будет чище и аккуратнее чтобы просто ссылаться на свойство DataSet напрямую:
ForEach x as PersonDataRow in myDS.Person
В том же духе, не обращайтесь к. Свойство Rows - возвращает коллекцию DataRow, а не коллекцию PersonDataRow. Каждый раз, когда ваш Intellisense сообщает вам, что свойство, к которому вы обращаетесь, возвращает простой DataTable, DataRow и c вместо этого ищут именованное свойство, которое возвращает строгий тип:
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 ранее. Это DataAdapter на стероидах. Они предназначены для передачи данных sh туда и обратно между базой данных и существующей или новой строго типизированной DataTable. Они набираются и соединяются с одним видом DataTable - PersonDataTable имеет соответствующий PersonTableAdapter.
Когда вы создаете TableAdapter, вы делаете это с помощью мастера в конструкторе DataSet, если вы не перетаскивали представление таблицы базы данных из окна источников данных в DataSet (в этом случае это делается с некоторыми допущения по умолчанию)
В мастере вы обычно вводите запрос на выборку, который выбирает столбцы или их подмножество из базы данных, и мастер создает локальный DataTable, представляющий то, что вы запросили. На данный момент ни одна база данных не вышла из базы данных, мастер просто посмотрел на ваш запрос SELECT id, name, age, testpassdate FROM person WHERE id = @id
и сказал: «Хорошо, это guid, строка, int, дата, из таблицы с именем person, она ищет для идентификатора, который является guid, теперь у меня достаточно информации, чтобы создать DataTable с 4 свойствами этих типов, и заполнить коллекцию параметров для запроса базы данных, использующей guid. и поскольку выбран столбец первичного ключа, я также могу сгенерировать обновление и удаление запросов ... "
Таким образом, вы получаете PersonTableAdapter, который оборачивает внутренний DataAdapter, он использует PersonDataTable для операций заполнения и обновления, и вы можете использовать его в своем коде, например так:
'SELECT ... FROM Person WHERE id = @id
personTA.Fill(myDS.Person, SOME_GUID_HERE)
Это проявление запроса, который мы использовали для создания адаптера. Мы закончили работу мастера на основе этого запроса; он сделал несколько запросов для выбора и обновления и загрузил их в табличный адаптер. Вы можете увидеть их, отладив ваше приложение и проверив .SelectCommand, .UpdateCommand, .InsertCommand и т. Д. c
Так что, если вы хотите запрашивать людей из БД по имени? Это делается только по идентификатору.
Щелкните правой кнопкой мыши TableAdapter в DataSet, добавьте запрос SELECT whatever FROM person WHERE name =@name
и скажите мастеру, что хотите назвать его FillByName. В коде do:
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
, но она
- Принимает DataRow, коллекцию DataRow или DataTable
- Looks в состоянии datarow (добавлено, изменено, удалено и т. д. c), чтобы выяснить, нужно ли запускать INSERT, UPDATE или DELETE
- Запускает соответствующие SQL
I wi sh они назвали это 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, который является типом PersonDataTable в типе объекта MyDataSet, и MyDataSet.Person, который является экземпляром PersonDataTable с именем Person, внутри экземпляр типа MyDataSet с именем MyDataSet
Смущен? Это вина исключительно vb за то, что мы позволили нам создавать экземпляры объектов, в которых переменная имеет то же имя, что и тип, а затем ошибка windows формирует команды для создания новых экземпляров набора данных в форме с тем же именем, что и тип. Другие языки, такие как c#, не позволяют создавать экземпляр типа с тем же именем, что и тип
Золотое правило, переименовывайте все свои вещи в форму после добавления! Когда вы отбрасываете набор данных в форму, vb делает
Dim MyDataSet as New MyDataSet
Переименовывает имя набора данных в форме так, чтобы оно называлось myDS et c, так что 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» в мастере и написав "Выбрать". Имя по умолчанию для операции заполнения, имеющей параметры, - fillBy. Я рекомендую вам всегда редактировать имя, чтобы оно включало то, чем оно заполняется, например, когда я вызывал мой FillByName. Я не рекомендую вам использовать select, потому что он не поможет вам поддерживать умственное различие между DataTable (кеш локальных данных), базой данных (хранилище удаленных данных) и ролью табличного адаптера ... и не будет помогите, потому что у вас будет подпрограмма select, которая заполняет предоставленную DataTable, поэтому она заполняется без выбора. Вызов вещи, которые не отражают то, что они делают, является еще одним быстрым путаницей
Примечание по выбору: Select - это функция DataTable, которая будет искать данные, кэшированные в DataTable. Это не попадает в базу данных. Вы можете реально использовать его только для поиска данных в БД, если вы загрузили всю таблицу БД в DataTable ... не делайте этого. Создайте свой табличный адаптер, чтобы получить новый запрос SELECT, который выбирает небольшую часть строк БД, назовите его fillByX и заполните только те строки, которые вам нужны
Извинения, если какая-либо часть этого поста трудна для понимания. Он был написан на старом iPad, который имеет действительно нестабильное автозамену и огромную задержку ввода, поэтому, если что-то не имеет смысла, дайте мне знать, и я исправлю это