Автономные данные
До сих пор во всех рассмотренных примерах использовались средства ADO.NET, основанные на соединениях. Когда применяется этот подход, данные исключают какие-либо операции с ними в хранилище в момент их извлечения. Ваш код полностью отслеживает все действия пользователя, хранение информации и определение того, когда новая команда должна быть сгенерирована и выполнена.
В отношении работы с объектом DataSet технология ADO.NET предлагает совершенно другую философию. Когда вы подключаетесь к базе данных, DataSet наполняется копией информации, прочитанной из базы. Если вы изменяете информацию в DataSet, соответствующая информация в таблице базы данных не меняется. Это значит, что вы можете легко обрабатывать и манипулировать данными, ни о чем не беспокоясь, потому что при этом ценное соединение с базой не задействовано. При необходимости можно повторно подключиться к исходному источнику данных и применить все внесенные изменения в DataSet за одну пакетную операцию.
Естественно, это решение не лишено недостатков, таких как последствия параллелизма. В зависимости от того, как спроектировано приложение, общий пакет изменений может быть отправлен немедленно. Но единственная ошибка (вроде попытки обновить запись, которую одновременно обновляет другой пользователь) может разрушить весь процесс обновления. При хорошо продуманном кодировании можно защитить свое приложение от этих проблем, однако потребуются дополнительные усилия.
С другой стороны, иногда может понадобиться использовать автономную (disconnected) модель доступа ADO.NET и DataSet. Ниже перечислены сценарии, в которых DataSet использовать легче, чем DataReader:
Когда нужен удобный пакет для отправки данных другому компоненту (например, если вы разделяете информацию с другими компонентами или поставляете их клиентам через веб-службу).
Когда нужен удобный формат файла для сериализации данных на диск (объект DataSet включает встроенную функциональность, позволяющую сохранять его в файл XML).
Когда нужно организовать навигацию в двух направлениях по большому объему данных. Например, вы можете использовать DataSet для поддержки спискового элемента управления с разбиением на страницы, который отображает информацию по частям. В противоположность этому DataReader обеспечивает перемещение только в одном направлении — вперед.
Когда нужно выполнять навигацию по нескольким разным таблицам. Объект DataSet может сохранить в себе все эти таблицы и информацию об отношениях между ними, позволяя создавать страницы «главная-детальная» без необходимости запроса к базе данных более одного раза.
Когда нужно использовать привязку данных к элементам управления пользовательского интерфейса. Для привязки данных можно применять и DataReader, но поскольку DataReader использует лишь однонаправленный курсор, невозможно привязать данные к множеству элементов управления. Кроме того, в отличие от DataSet, он не имеет возможности применять пользовательские критерии сортировки и фильтрации.
Когда данными необходимо манипулировать как XML-разметкой.
Когда требуется обеспечивать пакетные обновления. Например, можно создать веб-службу, которая позволяет клиенту загружать DataTable, заполненный строками, выполнять в нем множественные изменения и позднее отправлять их источнику данных. В такой момент веб-служба может проводить все изменения в единственной операции (если предположить, что никаких конфликтов не обнаружено).
Веб-приложения и DataSet
Общее заблуждение заключается в том, что объект DataSet необходим для обеспечения масштабируемости веб-приложения. Теперь, когда вы понимаете архитектуру обработки запросов ASP.NET, возможно, вы видите, что это не совсем так. Веб-приложения выполняются в течение каких-то секунд, а это значит, что даже если веб-приложение использует прямой доступ на основе курсоров, время жизни соединения настолько кратко, что не может сильно повлиять на масштабируемость, кроме как для очень сильно нагруженных веб-приложений.
Фактически, применение DataSet гораздо более оправдано в распределенных приложениях с многофункциональными Windows-клиентами. В таком сценарии клиенты могут извлекать DataSet с сервера (возможно, через веб-службу), работать с этими объектами DataSet в течение длительного времени и повторно подключаться к системе только в том случае, если они нуждаются в обновлении источника данных проведенными изменениями. Это позволяет системе обслуживать гораздо большее количество одновременно работающих пользователей, чем в случае, когда каждый клиент поддерживает прямое, длительно существующее соединение. Это также позволяет эффективно разделять ресурсы за счет кэширования данных на сервере и использовать пулы соединений между клиентскими запросами.
Объект DataSet также выступает в роли чистого пакета информации для многофункциональных клиентских приложений, которые лишь время от времени подключаются к системе.
Например, рассмотрим туристическое агентство, которому нужно вводить информацию о заказах или просматривать информацию о продажах путевок на портативном компьютере. Используя DataSet, приложение на портативном компьютере пользователя может хранить автономные данные локально и сериализировать их в XML-файл. Это позволит торговому агенту составлять новые заказы, используя кэшированные данные, даже когда подключение к Интернету не доступно. Новые данные могут быть отправлены позднее, когда пользователь сможет подключиться к системе.
Итак, что же остается веб-приложениям ASP.NET? По сути, есть два выбора. Можно использовать объект DataSet либо применять прямые команды, исключающие необходимость в DataSet. Говоря в общем, когда необходимо добавлять, вставлять или обновлять записи, без DataSet можно обойтись. Однако полностью избежать применения DataSet не удастся.
При извлечении записей, скорее всего, захочется использовать DataSet, т.к. он поддерживает набор совершенно необходимых средств. В частности, DataSet позволяет легко передавать блок данных от компонента базы данных на веб-страницу. Объект DataSet также поддерживает привязку данных, что дает возможность отображать информацию в многофункциональных элементах управления данными, таких как GridView. По этой причине большинство веб-приложений извлекают данные в DataSet, а обновления производят через прямые команды.
Веб-службы представляют собой единственный реальный сценарий применения веб-приложений, в котором может быть принято решение производить пакетное обновление через DataSet. В этом случае многофункциональное клиентское приложение загружает данные в DataSet, редактирует их и позднее отправляет DataSet для фиксации внесенных изменений.
Интеграция с XML
Объект DataSet также представляет встроенную сериализацию в XML. Вам даже не нужно знать об этом, чтобы воспользоваться ее преимуществами вроде возможности простой серилизации DataSet в файл или передачи DataSet другому приложению через веб-службу. А лучше всего то, что это средство позволяет совместно использовать данные с клиентами, написанными на других языках программирования и работающими под управлением других операционных систем. Однако реализация такого решения не проста (и часто DataSet — не лучший подход), поскольку имеется лишь минимальная возможность настройки структуры XML-разметки, которую производит DataSet.
DataSet
— основа автономного доступа к данным. DataSet содержит в себе два важных ингредиента: коллекцию из нуля или более таблиц (доступных через свойство Tables) и коллекцию из нуля или более отношений, которые можно применять для связывания таблиц между собой (представленных свойством Relationships).
На рисунке ниже показана базовая структура DataSet:

Иногда разработчики-новички в ADO.NET допускают ошибку, предполагая, что DataSet должен содержать всю информацию определенной таблицы источника данных. Это не так. Из соображений производительности обычно DataSet используется для работы с небольшим подмножеством от общего объема информации источника данных. К тому же таблицы в DataSet не обязаны отображаться непосредственно на таблицы источника данных. Одна таблица может содержать результаты запроса по одной таблице или же результаты запроса JOIN, который комбинирует данные из более чем одной связанной таблицы.
Как видно на рисунке, каждая запись коллекции DataSet.Tables — это DataTable. Объект DataTable содержит собственные коллекции — коллекцию Columns объектов DataColumn (описывающие имя и тип данных каждого поля) и коллекцию Rows объектов DataRow (содержащих действительные данные каждой записи).
Каждая Запись в DataTable представлена объектом DataRow. Каждый объект DataRow представляет одиночную запись в таблице, которая была извлечена из источника данных. DataRow является контейнером для действительных значений полей. Получить доступ к ним можно по имени поля, как в случае myRow[«FieldName»] . Всегда помните, что данные в источнике вообще никак не затрагиваются, когда вы работаете с объектами DataSet. Вместо этого все изменения проводятся локально в объекте DataSet, расположенном в памяти. DataSet никогда не полагается на какое-либо соединение с источником данных.
Объект DataSet также имеет методы, которые могут писать и читать данные и схемы XML, а также методы для быстрой очистки и дублирования данных. Эти методы кратко описаны в таблице ниже:
Возвращают строку данных (в разметке XML) или информацию схемы для DataSet. Информация схемы — это структурированная информация вроде количества таблиц, их имен, столбцов, типов данных и установленных отношений
Сохраняют данные и схемы, представленные DataSet а файле или потоке формата XML
Создают таблицы в DataSet на основе существующего документа XML или документа схемы XML. Источником XML может быть файл или любой другой поток
Очищает все данные таблиц. Однако этот метод оставляет нетронутой информацию о схеме и отношениях
Возвращает точный дубликат DataSet с тем же набором таблиц, отношений и данных
Возвращает DataSet с той же структурой (таблицами и отношениями), но без данных
Принимает другой DataSet, DataTable или коллекцию объектов DataRow и объединяет с текущим объектом DataSet, добавляя новые таблицы и объединяя данные в существующих
Класс DataAdapter
Чтобы извлечь записи из базы и использовать их для наполнения таблицы в DataSet, нужно использовать другой объект ADO.NET — DataAdapter. Объект DataAdapter является специфичным для поставщика, поэтому для каждого поставщика предусмотрены разные классы DataAdapter (SqlDataAdapter, OracleDataAdapter и т.д.).
DataAdapter служит мостом между одним DataTable в DataSet и источником данных. Он включает все доступные команды для выполнения запросов и обновления источника данных. Чтобы позволить DataAdapter редактировать, удалять и добавлять строки, потребуется указать объекты Command для свойств UpdateCommand, DeleteCommand и InsertCommand объекта DataAdapter. Чтобы применить DataAdapter для наполнения DataSet, необходимо установить SelectCommand.
Класс DataAdapter предоставляет три ключевых метода, которые перечислены в таблице ниже:
Добавляет DataTable к DataSet за счет выполнения запроса в SelectCommand. Если запрос возвращает множественные результирующие наборы, этот метод добавит множество объектов DataTable за раз. Этот метод можно также использовать для добавления данных к существующему объекту DataTable
Добавляет DataTable к DataSet за счет выполнения запроса в SelectCommand и извлечения только информации о схеме. Этот метод не добавляет никаких данных к DataTable. Вместо этого он просто предварительно конфигурирует DataTable с помощью детальной информации об именах столбцов, типах данных, первичных ключах и ограничениях уникальности
Проверяет все изменения отдельного объекта DataTable и применяет пакет этих изменений к источнику данных за счет выполнения соответствующих операций InsertComrnand, UpdateCommand и DeleteCommand
На рисунке ниже показано, как DataAdapter и его объекты Command работают вместе с источником данных и объектом DataSet:

Наполнение объекта DataSet
В следующем примере будет показано, как извлекать данные из таблицы SQL Server и использовать их для наполнения объекта DataTable, принадлежащего DataSet. Вы также увидите, как отображать данные программно, организуя цикл по записям с отображением их одна за другой. Вся логика находится в обработчике события Page.Load:
Давайте разберем этот пример. Сначала код создает соединение и определяет текст запроса SQL. На следующем шаге создается новый экземпляр класса SqlDataAdapter, который извлечет список сотрудников. Хотя каждый объект DataAdapter поддерживает объекты Command, только один из них (а именно — SelectCommand) необходим для наполнения DataSet. Чтобы еще более облегчить жизнь, можете создать необходимый объект Command и присвоить его свойству DataAdapter.SelectCommand за один прием. Для этого потребуется только применить объект Connection и строку запроса в конструкторе DataAdapter, что и сделано в примере.
После этого необходимо создать новый пустой объект DataSet и с помощью метода DataAdapter.Fill() выполнить запрос с помещением результата в новый объект DataTable этого DataSet. В этот момент можно также указать имя таблицы. Если этого не сделать, автоматически будет использовано имя по умолчанию (наподобие Table). В нашем примере имя таблицы соответствует имени исходной таблицы базы данных, хотя это и не обязательно.
Обратите внимание, что в коде не открывается соединение за счет вызова Connection.Open(). Вместо этого DataAdapter открывает и закрывает связанное с ним соединение автоматически, когда вызывается метод Fill(). В результате единственная строка кода, которую нужно рассмотреть для помещения в блок с обработкой исключений — вызов DataAdapter.Fill(). В качестве альтернативы соединение можно также открывать и закрывать «вручную». Если соединение открыто на момент вызова Fill(), то DataAdapter использует это соединение и не станет закрывать его автоматически. Такой подход удобен, если нужно быстро выполнить множество последовательных операций с источником данных и избежать дополнительных накладных расходов, связанных с повторным открытием и закрытием соединения.
Последний шаг — отображение содержимого DataSet. Быстрее всего это можно сделать с применением того же приема, что был продемонстрирован в предыдущей статье, и построить строку HTML, обращаясь к каждой записи. Естественно, модель ASP.NET спроектирована так, чтобы избавить от написания низкоуровневой HTML-разметки. Поэтому гораздо лучше привязать данные DataSet к элементу управления, связанному с данными, который автоматически сгенерирует необходимую HTML-разметку на основе шаблона. Такие элементы управления мы рассмотрим позже.
Работа с множественными таблицами и отношениями
В следующем примере демонстрируется более интересное применение DataSet, которое в дополнение к предоставлению автономных данных использует отношения между таблицами. В примере показано, как извлекать некоторые записи из таблиц Categories и Products базы данных Northwind, а также как создавать отношения между ними для организации простой навигации от записи о категории к ее дочерним записям о товарах, чтобы создать простой отчет:
На первом шаге осуществляется инициализация объектов ADO.NET и объявление двух SQL-запросов (для извлечения категорий и товаров). Далее код выполняет оба запроса, добавляя две таблицы к DataSet. Обратите внимание, что соединение открывается явно вначале и закрывается после двух операций, обеспечивая тем самым наилучшую производительность.
В этом примере один и тот же объект DataAdapter применяется для наполнения обеих таблиц. Это совершенно законный прием, к тому же он имеет смысл в настоящем сценарии, поскольку не нужно повторно использовать DataAdapter для обновления источника данных. Однако если бы DataAdapter применялся как для извлечения данных, так и для отправки изменений, то, скорее всего, для каждой из таблиц был бы предусмотрен отдельный DataAdapter, что позволило бы выполнять соответствующие команды вставки, обновления и удаления для каждой таблицы.
В коде мы имеем DataSet с двумя таблицами. В базе Northwind эти две таблицы связаны отношением по полю CategoryID. Оно является первичным ключом таблицы Categories и внешним ключом таблицы Products. К сожалению, в ADO.NET не предусмотрено никакого способа прочитать информацию об отношениях между таблицами в источнике данных, чтобы применить ее к DataSet автоматически. Поэтому для представления этого отношения приходится вручную создавать DataRelation.
Отношение создается путем определения объекта DataReiation и добавления его к коллекции DataSet.Relations. При создании DataRelation конструктору передаются три аргумента: имя отношения, DataColumn первичного ключа родительской таблицы и DataColumn внешнего ключа дочерней таблицы. Для этого понадобится следующий код:
Здесь присутствует интересная часть. Внутри блока можно обратиться к связанным записям о товарах для текущей категории, вызвав метод DataRow.GetChildRows(). Этот метод ищет данные — соответствующие строки в памяти, в связанном объекте DataTable. Получив массив записей о товарах, можно пройтись по ним с помощью вложенного цикла foreach. Это намного проще, чем код, который понадобился бы для поиска этой информации в отдельном объекте или для выполнения множества запросов с традиционным доступом через соединение.
Запустив эту страницу, вы увидите вывод, показанный на рисунке:

Вопрос, который часто задают программисты-новички в ADO.NET звучит так: когда нужно использовать запросы JOIN, а когда — объекты Data Relation? Наиболее важное условие для правильного ответа — собираетесь ли вы обновлять извлеченные данные? Если да, то применение отдельных таблиц и объекта DataRelation всегда обеспечивает большую гибкость. Если нет, то можно применять любой подход, хотя запрос JOIN и может оказаться более эффективным, потому что включает лишь одно обращение к базе по сети, в то время как вариант с DataRelation требует два, чтобы наполнить отдельные таблицы.
Когда вы добавляете отношение между таблицами в DataSet, то при этом ограничены правилами ссылочной целостности. Например, вы не можете удалить родительскую запись, если существуют связанные с ней дочерние строки, и не можете создать дочернюю запись, ссылающуюся на несуществующую родительскую. Это может стать причиной проблем, если DataSet содержит лишь частичные данные.
Например, если имеется полный список клиентских заказов, но только часть списка клиентов, может случиться так, что заказ будет ссылаться на заказчика, которого не существует, потому что запись о нем в DataSet отсутствует. Один из способов обойти эту проблему — создать DataRelation без соответствующих ограничений. Чтобы сделать это, используйте конструктор DataRelation, который принимает булевский параметр createConstraints, и установите его значение в false, как показано ниже:
Другой подход предусматривает отключение всякого рода проверок ограничений (в том числе проверки уникальных значений) за счет установки свойства DataSet.EnableConstraints в false перед добавлением отношения.
Поиск определенных строк
Класс DataTable предлагает удобный метод Select(), позволяющий искать строки на основе SQL-выражения. Выражение, которое используется с методом Select(), играет ту же роль, что и конструкция WHERE в операторе SELECT, но имеет дело с данными в памяти, находящимися в объекте DataTable (так что никаких операций с базой данных не производится).
Например, следующий код извлекает все товары, которые помечены как продающиеся со скидкой:
В этом примере в методе Select() используется достаточно простая строка фильтра. Однако вы вольны применять более сложные операции и комбинации различных критериев. Более подробную информацию можно найти в руководстве MSDN по библиотеке классов, в описании свойства DataColumn.Expression.
Метод Select() имеет одно потенциальное ограничение — он не принимает параметризованных условий. В результате он открыт для атак внедрением SQL. Ясно, что такие атаки, которые может предпринять злоумышленник в этой ситуации, достаточно ограничены, потому что нет способа получить доступ к реальному источнику данных или выполнить дополнительные команды. Однако тщательно написанные значения могут заставить ваше приложение вернуть дополнительную информацию из таблицы. Если фильтрующее выражение содержит значение, передаваемое пользователем, итерацию по DataTable для нахождения требуемых строк нужно выполнять вручную, не пользуясь методом Select().
DataSet
Ранее для получения данных мы использовали объект SqlDataReader, с помощью которого построчно можно перебрать ответ от сервера базы данных. Но есть и другой способ, который демонстрирует использование объектов DataSet .
Класс DataSet из пространства имен System.Data представляет хранилище или кэш данных в памяти, извлеченных из источника данных. Объект DataSet содержит таблицы, которые представлены типом DataTable . Таблица DataTable , в свою очередь, состоит из столбцов и строк. Каждый столбец представляет объект DataColumn , а строка — объект DataRow .
Для наполнения объекта DataSet данными из источника данных применяется класс System.Data.Common.DataAdapter (который реализует интерфейс System.Data.IDataAdapter ), а точнее его реализации, предназначенные для работы с определенной СУБД.
Для заполнения объекта DataSet у объекта DataAdapter вызывается метод Fill() , в который в качестве параметра передается заполняемый объект DataSet.
Метод Fill() неявно открывает объект подключения, который хранится в свойстве Connection объекта DataAdapter , если это подключение не открыто. Если метод Fill() открывает подключение, то после получения данных метод Fill закрывает это подключение.
Если подключение до вызова метода Fill() ранее уже было открыто, например, с помощью метода Open()/OpenAsync() , метод Fill просто использует это подключение.
SqlDataAdapter
Для работы с БД SQL Server пакет Microsoft.Data.SqlClient как раз предоставляет реализацию DataAdapter в виде класса SqlDataAdapter .
Для получения данных через объект SqlDataAdapter необходимо организовать подключение к БД и выполнить команду SELECT. Есть несколько способов создания SqlDataAdapter:
Для работы SqlDataAdapter необходим объект SqlConnection, либо строка подключения и выполняемое SQL-выражение «SELECT».
Пример наполнения DataSet с помощью SqlDataAdapter:
В этом случае в DataSet окажутся данные, извлеченные с помощью выражения SELECT * FROM Users .
DataTable, столбцы и строки
Объект DataSet состоит из набора объектов DataTable . Для доступа к таблицам в DataSet определено свойство Tables , которое представляет объект DataTableCollection .
Объект DataTable , в свою очередь, состоит из набора объектов DataColumn — описания столбцов таблицы, которые определяют схему таблицы и которые можно получить через свойство Columns .
Кроме столбцов класс DataTable определяет свойство Rows , которое представляет тип DataRowCollection и которое хранит строки таблицы. Каждая строка представляет класс DataRow .
Для обращения к отдельным ячейкам строки DataRow предлагает ряд свойств, из которых следует выделить следующие:
Item[Int32] : возвращает или устанавливает значение ячейки по ее индексу. Возвращает значение типа object .
Item[String] : возвращает или устанавливает значение ячейки по имени столбца. Возвращает значение типа object .
ItemArray : возвращает или устанавливает значения всех ячеек строки в виде объекта object[]
Теперь используем все выше рассмотренные типы, чтобы получить данные таблицы и вывести их содержимое на консоль:
Консольный вывод в моем случае:
Хотя DataSet представляет самодостаточный класс, с которым можно работать вне зависимости от применяемой технологии на .NET, однако чаще всего он применяется именно для связи элементов управления для отображения данных c БД, например, элемента DataGridView в Windows Forms.
Зачем нужен обьект SqlDataAdapter
сколько не читал мсдн так и не понял зачем при заполнении datagridview прописывать еще и SqlDataAdapter а DataTable недостаточно? Может вопрос глупый но я хочу понять а не просто писать код, что бы работало.
![]()
Для начала обратимся к документации. Ссылки привожу на русские страницы, но перевод там автоматический, и мне не нравится, поэтому цитата будет оригинальная.
Provides a way of reading a forward-only stream of rows from a SQL Server database.
Represents a set of data commands and a database connection that are used to fill the DataSet and update a SQL Server database.
По сути оба класса предоставляют доступ к данным БД, но немного по-разному.
SqlDataReader предоставляет строго последовательный доступ и читает данные небольшими порциями, что позволяет работать даже с огромными таблицами без риска получить исключение по таймауту. Но на этом его достоинства заканчиваются, больше он ничего не умеет.
SqlDataAdapter предоставляет богатый интерфейс для автоматического заполнения DataTable и DataSet , а также предоставляет средства синхронизации данных между DataSet и БД, но при этом, на практике, довольно капризно относится к слишком большим наборам данных, т.к. пытается получить их целиком. Если набор данных превышает некоторый объем (зависит от настроек соединения, задержек в сети, загруженности сервера и т.д.) то адаптеру не хватит времени на его получение и будет выброшено исключение.
Универсального рецепта что использовать тут нет, так же как при работе с файлами. Где-то выгоднее использовать ридер, где-то адаптер.
DataTable и DataSet , согласно документации и практическому применению, представляют кэш данных БД в памяти клиента. DataSet состоит из набора DataTable и дополнительно может содержать подробности реляционной модели кэшированных данных.
DataTable , в частности, может использоваться как DataSource для контролов вроде GridView, ListView и д.р. предназначенных для отображения массивов данных.
Ado.net DataAdapter C# Example
This Ado.net SqlDataAdapter Tutorial in C# for Beginners will guide you to learn Ado.net DataAdapter with some real-time examples. Please feel free to ask question, I will keep updating this tutorial with answer of your query
Ado.net Data Adapter works as a bridge between a DataSet and data source, using DataAdapter we can fill dataset, datatable etc. DataAdapter is basically a class that represents a set of SQL commands with a database connection property, the fill method is used for updating data source
There are different types of DataAdapter for different datasource in Ado.net, SqlDataAdapter, OleDbDataAdapter, OracleDataAdapter etc. but all DataAdapter class are inherited from same base class DbDataAdapter, IDbDataAdapter So the implementation remain same for all DataAdapter class.
let’s look at the following DataAdapter classes
SqlDataAdapter in Ado.Net
When we want to work with SQL Client we use SqlDataAdapter class, below is the definition of SqlDataAdapter class inherited from DbDataAdapter
Similarly we have OleDbDataAdapter, which is also inherited from same base class like SqlDataAdapter, so from implementation point of view there won’t be much difference between both classes, if you want to work with Oledb client then use OleDbDataAdapter, below is the OleDbDataAdapter class definition
- Fill(DataSet / DataTable) : It fill data from data source to dataset or datatable object
- Update(DataSet / DataTable / DataRow) : this method will update data in dataset or datatable object, also can update data in datasource
Here are few lines of code with required namespace, we fill a dataset and a datatable using SqlDataAdapter in below example
Here is the complete example of how to fill dataset using SQL Data Adapter, and read data from dataset object.
We also can use stored procedure with SqlDataAdapter, in below example you can see how to pass parameter in StoredProcedure using SqlCommand object, finally set SqlDataAdapter Select Command.
I have a simple stored procedure, which has one mandatory parameter StuId, we want to fetch the data where student id match.
This is actually considered as good practice, instead of writing SQL query in code you can call the StoredProcedure, so next time you want to fetch another column, you don’t need to compile the code again, just make the changes in StoredProcedure, and code will remain clean.
Another advantage of using StoredProcedure instead of select statement is, that we can get multiple result set with just one call.
Suppose, we have two select statements in stored procedure, we can have even more.
The same above dataset loaded using SqlDataAdapter can get you multiple result sets like example below.