Вы находитесь на странице: 1из 24

Работа с SqlDataAdapter и DataSet

SqlDataAdapter и DataSet
Ранее для получения данных мы использовали объект SqlDataReader, с помощью которого построчно можно
перебрать ответ от сервера базы данных. Но есть и другой способ, который демонстрирует использование
объектов SqlDataAdapter и DataSet. DataSet представляет хранилище данных, с которыми можно работать
независимо от наличия подключения, а SqlDataAdapter заполняет DataSet данными из БД.

Для получения данных через объект SqlDataAdapter необходимо организовать подключение к БД и


выполнить команду SELECT. Есть несколько способов создания SqlDataAdapter:

1 SqlDataAdapter adapter = new SqlDataAdapter();


2 SqlDataAdapter adapter = new SqlDataAdapter(command);
3 SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
4 SqlDataAdapter adapter = new SqlDataAdapter(sql, connectionString);

 Можно использовать конструктор без параметров, а команду SELECT и подключение установить


позже
 Можно передать в конструктор объект SqlCommand
 Можно в конструкторе установить sql-выражение SELECT и объект SqlConnection
 Можно в конструкторе установить sql-выражение SELECT и строку подключения

Рассмотрим, как получить данные в DataSet через SqlDataAdapter. Для работы с DataSet особенно удобно
использовать элементы управления, которые могут заполняться из внешнего источника данных, например,
DataGridView в Windows Forms. Поэтому создадим новый проект по типу Windows Forms Application.

Добавим на единственную форму в проекте элемент DataGridView и определим следующий код формы:

1 using System.Data;
2 using System.Windows.Forms;
using System.Data.SqlClient;
3
 
4 namespace AdoNetWinFormsApp
5 {
6     public partial class Form1 : Form
7     {
8         public Form1()
        {
9             InitializeComponent();
10  
11             string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated
12 Security=True";
13             string sql = "SELECT * FROM Users";
14             using (SqlConnection connection = new SqlConnection(connectionString))
            {
15                 connection.Open();
16                 // Создаем объект DataAdapter
17                 SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
18                 // Создаем объект Dataset
19                 DataSet ds = new DataSet();
                // Заполняем Dataset
20
                adapter.Fill(ds);
21                 // Отображаем данные
22                 dataGridView1.DataSource = ds.Tables[0];
23             }
24         }
    }
25
}

В конструкторе формы в DataGridView загружаются данные. Для загрузки данных создается объект
SqlDataAdapter, который принимает объект подключения и sql-выражение SELECT. Затем создается объект
DataSet и с помощью метода adapter.Fill() в него загружаются данные. Дальше происходит установка
источника данных для DataGridView:

1 dataGridView1.DataSource = ds.Tables[0];

В качестве источника устанавливается одна из таблиц в DataSet. Каждая таблица представляет объект
DataTable, и в DataSet может быть определено несколько таких таблиц. Но в данном случае при выборке в
DataSet есть только одна таблица, которую мы можем получить из коллекции Tables по индексу.
Постраничный просмотр в SqlDataAdapter
В прошлой теме была рассмотрена загрузка данных через SqlDataAdapter в DataSet с последующим
отображением в DataGridView. Однако если в базе данных очень много строк, то для более комфортной
работы может потребоваться разбить эти данные на отдельные куски или страницы. Используя
DataGridView очень легко сделать постраничный просмотр объект. Для этого определим на форме
DataGridView и две кнопки - backButton и nextButton соответственно для перехода назад и вперед.

Затем определим следующий код формы:

1 using System;
2 using System.Data;
using System.Data.SqlClient;
3 using System.Windows.Forms;
4  
5 namespace PagingApp
6 {
7     public partial class Form1 : Form
8     {
        int pageSize = 5; // размер страницы
9         int pageNumber = 0; // текущая страница
10         string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated
11 Security=True";
12         SqlDataAdapter adapter;
13         DataSet ds;
        public Form1()
14         {
15             InitializeComponent();
16              
17             dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
18             dataGridView1.AllowUserToAddRows = false;
19  
20             using (SqlConnection connection = new SqlConnection(connectionString))
            {
21
                adapter = new SqlDataAdapter(GetSql(), connection);
22  
23                 ds = new DataSet();
24                 adapter.Fill(ds, "Users");
25                 dataGridView1.DataSource = ds.Tables[0];
26                 dataGridView1.Columns["Id"].ReadOnly = true;
            }
27
        }
28         // обработчик кнопки Вперед
29         private void nextButton_Click(object sender, EventArgs e)
30         {
31             if (ds.Tables["Users"].Rows.Count < pageSize) return;
32  
33             pageNumber++;
            using (SqlConnection connection = new SqlConnection(connectionString))
34             {
35                 adapter = new SqlDataAdapter(GetSql(), connection);
36  
37                 ds.Tables["Users"].Rows.Clear();
38  
39                 adapter.Fill(ds,"Users");
40             }
        }
41         // обработчик кнопки Назад
42         private void backButton_Click(object sender, EventArgs e)
43         {
44             if (pageNumber==0) return;
            pageNumber--;
45
             
46             using (SqlConnection connection = new SqlConnection(connectionString))
47             {
48                 adapter = new SqlDataAdapter(GetSql(), connection);
49  
50                 ds.Tables["Users"].Rows.Clear();
51  
52                 adapter.Fill(ds, "Users");
            }
53         }
54  
55         private string GetSql()
56         {
57             return "SELECT * FROM Users ORDER BY Id OFFSET ((" + pageNumber + ") * " + pageSize + ")
58 "+
                "ROWS FETCH NEXT " + pageSize + "ROWS ONLY";
59         }
60     }
}

Переменная pageSize определяет количество строк на одной странице, а переменная pageNumber будет
хранить номер текущей просматриваемой страницы.

Запрос к базе данных будет создаваться с помощью функции GetSql().

В обработчике кнопки Вперед увеличивается текущая страница на единицу и производится запрос. В


обработчике кнопки Назад уменьшается счетчик текущей страницы.
SqlCommandBuilder и сохранение изменений DataSet в
базе данных
Получив данные в DataSet, мы можем производить с ними различными операции: удалять, изменять,
добавлять новые записи. Однако все делаемые нами изменения автоматически не будут сохраняться в БД.
Для этого нам еще надо вызвать метод Update объекта SqlDataAdapter, который заполнял DataSet.

Для модификации данных в БД в соответствии с изменениями в DataSet SqlDataAdapter использует


команды InsertCommand, UpdateCommand иDeleteCommand. Мы можем сами определить для этих команд
sql-выражения, либо мы можем воспользоваться классом SqlCommandBuilder, который позволяет
автоматически сгенерировать нужные выражения. Используем SqlCommandBuilder:

1 static string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated


2 Security=True";
static void Main(string[] args)
3 {
4     string sql = "SELECT * FROM Users";
5     using (SqlConnection connection = new SqlConnection(connectionString))
6     {
7         connection.Open();
        SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
8         DataSet ds = new DataSet();
9         adapter.Fill(ds);
10  
11         DataTable dt = ds.Tables[0];
12         // добавим новую строку
13         DataRow newRow = dt.NewRow();
        newRow["Name"] = "Alice";
14         newRow["Age"] = 24;
15         dt.Rows.Add(newRow);
16  
17         // создаем объект SqlCommandBuilder
18         SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
19         adapter.Update(ds);
        // альтернативный способ - обновление только одной таблицы
20
        //adapter.Update(dt);
21         // заново получаем данные из бд
22         // очищаем полностью DataSet
23         ds.Clear();
24         // перезагружаем данные
        adapter.Fill(ds);
25
26  
        foreach (DataColumn column in dt.Columns)
27             Console.Write("\t{0}", column.ColumnName);
28         Console.WriteLine();
29         // перебор всех строк таблицы
30         foreach (DataRow row in dt.Rows)
31         {
            // получаем все ячейки строки
32             var cells = row.ItemArray;
33             foreach (object cell in cells)
34                 Console.Write("\t{0}", cell);
35             Console.WriteLine();
36         }
    }
37     Console.Read();
38 }
39
Здесь после загрузки данных создается новая строка, которая затем добавляется в DataTable. При вызове у
адаптера метода Update()происходит анализ изменений, которые произошли. И после этого выполняется
соответствующая команда. В данном случае так как идет добавление новой строки, то будет выполняться
команда InsertCommand. Однако в данном коде мы нигде явным образом не задаем эту команду, за нас все
автоматически делает SqlCommandBuilder. Для применения этого класса достаточно вызвать его
конструктор, в который передается нужный адаптер:

1 SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);

Причем больше нигде в коде вы этот объект не вызываем.

При необходимости мы можем получить sql-выражения используемых команд:

1 Console.WriteLine(commandBuilder.GetUpdateCommand().CommandText);
2 Console.WriteLine(commandBuilder.GetInsertCommand().CommandText);
3 Console.WriteLine(commandBuilder.GetDeleteCommand().CommandText);

В моем случае команда обновления будет выглядеть так:

UPDATE [Users] SET [Name]=@p1, [Age]=@p2 WHERE (([Id]=@p3) AND ([Name]=@p4) AND
1 ([Age]=@p5))

Команда вставки:

1 INSERT INTO [Users] ([Name],[Age]) VALUES (@p1, @p2)

Команда удаления:

1 DELETE FROM [Users] WHERE (([Id]=@p1) AND ([Name]=@p2) AND ([Age]=@p3))


Обновление БД из DataSet вручную
Хотя в предыдущей теме объект SqlCommandBuilder позволял нам автоматически создать все нужные
выражения для обновления данных в БД из DataSet, но все же этот способ имеет свои недостатки.
Например, взглянем на следующий кусочек кода, который использовался в прошлой теме:

1 SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);


2 adapter.Update(ds);
3 ds.Clear();
4 adapter.Fill(ds);

После обновления происходит очистка DataSet и перезагрузка данных, что снижает производительность
приложения. Хотя в DataTable у нас уже добавлена строка с новыми данными, и в ней не хватает только id -
значения, которое генерируется самой базой данных при добавлении. Без id нам трудно будет управлять
данными, мы не сможем их автоматически через тот же SqlCommandBuilder обновлять или удалять.
Поэтому в идеале хотелось бы избежать и перезагрузки данных и в то же время получить id новой записи
при выполнении метода adapter.Update. И более гибкий способ состоит в том, что мы сами вручную
определяем все те выражения, которые будут выполняться.

Итак, добавим в базу данных следующую хранимую процедуру:

1
CREATE PROCEDURE [dbo].[sp_CreateUser]
2     @name nvarchar(50),
3     @age int,
4     @Id int out
5 AS
6     INSERT INTO Users (Name, Age)
    VALUES (@name, @age)
7
  
8
    SET @Id=SCOPE_IDENTITY()
9 GO
10

В качестве входных параметров она принимает имя и возраст пользователя и возвращает его id.

Теперь изменим код из прошлой темы:

1 static string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated


2 Security=True";
static void Main(string[] args)
3 {
4     string sql = "SELECT * FROM Users";
5     using (SqlConnection connection = new SqlConnection(connectionString))
6     {
7         connection.Open();
        SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
8         SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
9  
10         // устанавливаем команду на вставку
11         adapter.InsertCommand = new SqlCommand("sp_CreateUser", connection);
12         // это будет зранимая процедура
13         adapter.InsertCommand.CommandType = CommandType.StoredProcedure;
        // добавляем параметр для name
14         adapter.InsertCommand.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar, 50,
15 "Name"));
16         // добавляем параметр для age
17         adapter.InsertCommand.Parameters.Add(new SqlParameter("@age", SqlDbType.Int, 0, "Age"));
18         // добавляем выходной параметр для id
        SqlParameter parameter = adapter.InsertCommand.Parameters.Add("@Id", SqlDbType.Int, 0, "Id");
19         parameter.Direction = ParameterDirection.Output;
20          
21         DataSet ds = new DataSet();
22         adapter.Fill(ds);
23  
24         DataTable dt = ds.Tables[0];
25         // добавим новую строку
        DataRow newRow = dt.NewRow();
26         newRow["Name"] = "Kris";
27         newRow["Age"] = 26;
28         dt.Rows.Add(newRow);
29  
30         adapter.Update(ds);
31         ds.AcceptChanges();
        foreach (DataColumn column in dt.Columns)
32             Console.Write("\t{0}", column.ColumnName);
33         Console.WriteLine();
34         // перебор всех строк таблицы
35         foreach (DataRow row in dt.Rows)
36         {
            // получаем все ячейки строки
37             var cells = row.ItemArray;
38             foreach (object cell in cells)
39                 Console.Write("\t{0}", cell);
40             Console.WriteLine();
41         }
    }
42     Console.Read();
}

Здесь также используется SqlCommandBuilder, но теперь из-за переустановки


свойства adapter.InsertCommand выполнение добавления нового объекта будет переопределено.

При определении выходного параметра мы указываем, что он будет называться "@Id" и будет возвращать
значение для столбца Id в DataTable:

1 adapter.InsertCommand.Parameters.Add("@Id", SqlDbType.Int, 0, "Id");

В итоге при выполнении метода adapter.Update() id автоматически попадет в DataTable. При этом операции
по обновлению и удалению мы можем использовать те же, что предоставляет SqlCommandBuilder.
И после обновления базы данных с помощью метода AcceptChanges() объекта DataSet производится
принятие всех изменений в DataSet ко всем измененным строкам.
Все операции с БД в графическом приложении
Ранее мы рассмотрели, как удобно загружать данные в приложении Windows Forms в элемент DataGridView
через DataSet. Теперь определим полнофункциональную форму, через которую мы сможем производить все
стандартные CRUD операции в базе данных.

Итак, определим форму, на которой будет элемент DataGridView и три кнопки для добавления, удаления и
сохранения изменений. Форма в итоге будет выглядеть примерно следующим образом:

Код формы будет выглядеть следующим образом:

1 using System;
2 using System.Data;
using System.Windows.Forms;
3 using System.Data.SqlClient;
4  
5 namespace AdoNetWinFormsApp
6 {
7     public partial class Form1 : Form
8     {
        DataSet ds;
9         SqlDataAdapter adapter;
10         SqlCommandBuilder commandBuilder;
11         string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated
12 Security=True";
13         string sql = "SELECT * FROM Users";
14  
        public Form1()
15         {
16             InitializeComponent();
17  
18             dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
19             dataGridView1.AllowUserToAddRows = false;
20  
21             using (SqlConnection connection = new SqlConnection(connectionString))
            {
22
                connection.Open();
23                 adapter = new SqlDataAdapter(sql, connection);
24  
25                 ds = new DataSet();
                adapter.Fill(ds);
26
                dataGridView1.DataSource = ds.Tables[0];
27                 // делаем недоступным столбец id для изменения
28                 dataGridView1.Columns["Id"].ReadOnly = true;
29             }
30              
31         }
32         // кнопка добавления
        private void addButton_Click(object sender, EventArgs e)
33         {
34             DataRow row = ds.Tables[0].NewRow(); // добавляем новую строку в DataTable
35             ds.Tables[0].Rows.Add(row);
36         }
37         // кнопка удаления
        private void deleteButton_Click(object sender, EventArgs e)
38         {
39             // удаляем выделенные строки из dataGridView1
40             foreach(DataGridViewRow row in dataGridView1.SelectedRows)
41             {
42                 dataGridView1.Rows.Remove(row);
            }  
43         }
44         // кнопка сохранения
45         private void saveButton_Click(object sender, EventArgs e)
46         {
47             using (SqlConnection connection = new SqlConnection(connectionString))
            {
48                 connection.Open();
49                 adapter = new SqlDataAdapter(sql, connection);
50                 commandBuilder = new SqlCommandBuilder(adapter);
51                 adapter.InsertCommand = new SqlCommand("sp_CreateUser", connection);
52                 adapter.InsertCommand.CommandType = CommandType.StoredProcedure;
                adapter.InsertCommand.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar, 50,
53 "Name"));
54                 adapter.InsertCommand.Parameters.Add(new SqlParameter("@age", SqlDbType.Int, 0, "Age"));
55  
56                 SqlParameter parameter = adapter.InsertCommand.Parameters.Add("@Id", SqlDbType.Int, 0,
57 "Id");
58                 parameter.Direction = ParameterDirection.Output;
59  
                adapter.Update(ds);
60             }
61         }
62     }
63 }

Здесь для добавления объекта мы будем обращаться к хранимой процедуре sp_CreateUser, которая была
добавлена в базу данных в прошлой теме.

В конструкторе данные загружаются в DataSet, первая таблица которого устанавливается в качестве


источника данных для dataGridView1:

1 dataGridView1.DataSource = ds.Tables[0];

Также в конструкторе устанавливается полное выделение строки и запрет на ручное добавление новых
строк:
1 dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
2 dataGridView1.AllowUserToAddRows = false;

В обработчике кнопки добавления создается новая строка, которая добавляется в таблицу объекта DataSet. И
так как мы ранее установили привязку к источнику данных, то автоматически новая строка также будет
добавляться и в dataGridView1:

1 private void addButton_Click(object sender, EventArgs e)


2 {
3     DataRow row = ds.Tables[0].NewRow(); // добавляем новую строку в DataTable
4     ds.Tables[0].Rows.Add(row);
}
5

В обработчике кнопки удаления удаляются выделенные строки в dataGridView1. Опять же в силу привязки к
источнику данных будет также происходить удаление и из таблицы в DataSet:

1 private void deleteButton_Click(object sender, EventArgs e)


2 {
3     foreach(DataGridViewRow row in dataGridView1.SelectedRows)
4     {
5         dataGridView1.Rows.Remove(row);
    }  
6
}
7

Для обновления на не нужна никакая кнопка, так как мы можем нажать на любую ячейку таблицы (кроме
заблокированного для изменения столбца Id) и изменить в ней данные. Однако сами по себе добавление
новой строки, удаление строк, изменение ячеек ни как автоматически не отразятся на базе данных. И чтобы
бд синхронизировалась, пользователю надо будет нажать на кнопку сохранения, обработчик которой
выглядит следующим образом:

1
2 private void saveButton_Click(object sender, EventArgs e)
{
3     using (SqlConnection connection = new SqlConnection(connectionString))
4     {
5         connection.Open();
6         adapter = new SqlDataAdapter(sql, connection);
7         commandBuilder = new SqlCommandBuilder(adapter);
        adapter.InsertCommand = new SqlCommand("sp_CreateUser", connection);
8
        adapter.InsertCommand.CommandType = CommandType.StoredProcedure;
9         adapter.InsertCommand.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar, 50,
10 "Name"));
11         adapter.InsertCommand.Parameters.Add(new SqlParameter("@age", SqlDbType.Int, 0, "Age"));
12  
13         SqlParameter parameter = adapter.InsertCommand.Parameters.Add("@Id", SqlDbType.Int, 0, "Id");
        parameter.Direction = ParameterDirection.Output;
14
15  
        adapter.Update(ds);
16     }
17 }
18

Как в прошлой теме здесь устанавливается у адаптера команда на добавление InsertCommand и затем


вызывается метод Update(). В итоге мы можем добавить несколько строк, удалить, изменить, и потом один
раз мы нажмем на кнопку, и все изменения будут применены к базе данных.
DataSet и DataTable
После получения данных из базы данных через SqlDataAdapter в DataSet мы можем локально работать с
этими данными вне зависимости от наличия подключения. Более того если нам даже и не надо использовать
никакую базу данных, но при этом мы хотим иметь удобный функционал для работы с данными в виде
наборов таблиц, то мы также можем воспользоваться классом DataSet.

Объект DataSet содержит таблицы, которые представлены типом DataTable. Таблица, в свою очередь,
состоит из столбцов и строк. Каждый столбец представляет объект DataColumn, а строка - объект DataRow.
Все данные строки хранятся в свойстве ItemArray, который представляет массив объектов - значений
отдельных ячеек строки. Например, получим все таблицы и выведем их содержимое:

1
2
3 static void Main(string[] args)
4 {
    string sql = "SELECT * FROM Users";
5     string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated
6 Security=True";
7     using (SqlConnection connection = new SqlConnection(connectionString))
8     {
9         connection.Open();
        SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
10
 
11         DataSet ds = new DataSet();
12         adapter.Fill(ds);
13         // перебор всех таблиц
14         foreach (DataTable dt in ds.Tables)
15         {
            Console.WriteLine(dt.TableName); // название таблицы
16
            // перебор всех столбцов
17             foreach(DataColumn column in dt.Columns)
18                 Console.Write("\t{0}", column.ColumnName);
19             Console.WriteLine();
20             // перебор всех строк таблицы
            foreach (DataRow row in dt.Rows)
21
            {
22                 // получаем все ячейки строки
23                 var cells = row.ItemArray;
24                 foreach (object cell in cells)
25                     Console.Write("\t{0}", cell);
26                 Console.WriteLine();
            }
27         }
28     }
29 }
30
31
Теперь рассмотрим, как мы можем работать с объектами DataSet и DataTable без какой-либо базы данных.
Например, создадим вручную в DataSet несколько таблиц и определим их структуру:

1 static void Main(string[] args)


2 {
    DataSet bookStore = new DataSet("BookStore");
3     DataTable booksTable = new DataTable("Books");
4     // добавляем таблицу в dataset
5     bookStore.Tables.Add(booksTable);
6  
7     // создаем столбцы для таблицы Books
8     DataColumn idColumn = new DataColumn("Id", Type.GetType("System.Int32"));
    idColumn.Unique = true; // столбец будет иметь уникальное значение
9     idColumn.AllowDBNull = false; // не может принимать null
10     idColumn.AutoIncrement = true; // будет автоинкрементироваться
11     idColumn.AutoIncrementSeed = 1; // начальное значение
12     idColumn.AutoIncrementStep = 1; // приращении при добавлении новой строки
13  
14     DataColumn nameColumn = new DataColumn("Name", Type.GetType("System.String"));
    DataColumn priceColumn = new DataColumn("Price", Type.GetType("System.Decimal"));
15     priceColumn.DefaultValue = 100; // значение по умолчанию
16     DataColumn discountColumn = new DataColumn("Discount", Type.GetType("System.Decimal"));
17     discountColumn.Expression = "Price * 0.2";
18  
19     booksTable.Columns.Add(idColumn);
20     booksTable.Columns.Add(nameColumn);
    booksTable.Columns.Add(priceColumn);
21
    booksTable.Columns.Add(discountColumn);
22     // определяем первичный ключ таблицы books
23     booksTable.PrimaryKey = new DataColumn[] { booksTable.Columns["Id"] };
24  
25     DataRow row = booksTable.NewRow();
26     row.ItemArray = new object[] { null, "Война и мир", 200 };
    booksTable.Rows.Add(row); // добавляем первую строку
27
    booksTable.Rows.Add(new object[] { null, "Отцы и дети", 170 }); // добавляем вторую строку
28  
29     Console.Write("\tИд \tНазвание \tЦена \tСкидка");
30     Console.WriteLine();
31     foreach (DataRow r in booksTable.Rows)
32     {
33         foreach (var cell in r.ItemArray)
            Console.Write("\t{0}", cell);
34
35
36
37         Console.WriteLine();
38     }
39     Console.Read();      
40 }
41
42
43

Разберем весь код. Сначала создаются объекты DataSet и DataTable, в конструктор которых передается
название. Затем создается четыре столбца. Каждый столбец в конструкторе принимает два параметра: имя
столбца и его тип.

1 DataColumn idColumn = new DataColumn("Id", Type.GetType("System.Int32"));

Причем для столбца Id устанавливается, что значения этого столбца должны иметь уникальное значение, не
должны принимать null, и их значение при добавлении нового объекта будет инкрементироваться на
единицу. То есть фактически это стандартный столбец Id, как в большинстве баз данных.

Далее создается еще три столбца, при этом для столбца Discount устанавливается свойство Expression,
указывающее на выражение, которое будет использоваться для вычисления значения столбца:

1 discountColumn.Expression = "Price * 0.2";

То есть в данном случае значение столбца Discount равно значению столбца Price, помноженного на 0.2.

Затем устанавливается первичный ключ для таблицы с помощью свойства PrimaryKey:

1 booksTable.PrimaryKey = new DataColumn[] { booksTable.Columns["Id"] };

В роли первичного ключа выступает столбец Id. Но мы также можем использовать набор различных
столбцов для создания составного ключа.

После определения схемы таблицы в нее добавляются две строки:

1 DataRow row = booksTable.NewRow();


2 row.ItemArray = new object[] { null, "Война и мир", 200 };
3 booksTable.Rows.Add(row); // добавляем первую строку
4 booksTable.Rows.Add(new object[] {null, "Отцы и дети", 170 });

Значения в метод booksTable.Rows.Add можно передать как напрямую в виде массива объектов, так и в виде
объекта DataRow. При этом нам надо передать ровно столько значений, сколько в таблице столбцов. Однако
поскольку первый столбец Id устанавливается через автоинкремент, мы можем передать значение null - оно
все равно будет игнорироваться. Также мы можем опустить последний параметр для столбца Discount, так
как его значение вычисляется с помощью выражения "Price * 0.2". Более того мы даже можем опустить
значение для третьего столбца Price, так как у него установлено свойство DefaultValue, которое
устанавливает значение по умолчанию, если значение отсутствует:

1 booksTable.Rows.Add(new object[] {null, "Отцы и дети"});

И в конце идет перебор строк таблицы.

Кроме добавления мы можем производить и другие операции со строками. Например, мы можем получить
строку по индексу:

1 DataRow row = booksTable.Rows[0]; // первая строка

Получив строку по индексу, можно изменить ее ячейки:

1 booksTable.Rows[0][2] = 300; //третьей ячейке первой строки присваивается значение 300

И также можно удалять строку:

1 booksTable.Rows.RemoveAt(1); // удаление второй строки по индексу


2 // другой сопосб удаления
3 DataRow row = booksTable.Rows[0];
4 booksTable.Rows.Remove(row);

Используя метод Select() объекта DataTable мы легко можем найти строки, которые соответствуют


определенному критерию. Например, получим строки, в которых цена больше 120:

1 var selectedBooks = booksTable.Select("Price > 120");


2 foreach (var b in selectedBooks)
3     Console.WriteLine("{0} - {1}", b["Name"], b["Price"]);
Отношения между таблицами в DataSet
DataSet может содержать множество таблиц, которые могут быть связаны различными отношениями.
Например, пусть у нас в dataset будет определена таблица производителей смартфонов и таблица самих
смартфонов, которая связана с первой:

1 static void Main(string[] args)


2 {
3     DataSet ds = new DataSet("Store");
4  
5     // таблица компаний
    DataTable companiesTable = new DataTable("Companies");
6
    // два столбца таблицы Companies
7     DataColumn compIdColumn = new DataColumn("Id", Type.GetType("System.Int32"));
8     compIdColumn.Unique = true;
9     compIdColumn.AllowDBNull = false;
10     compIdColumn.AutoIncrement = true;
11     compIdColumn.AutoIncrementSeed = 1;
12     compIdColumn.AutoIncrementStep = 1;
13  
14     DataColumn compNameColumn = new DataColumn("Name", Type.GetType("System.String"));
15     // добавляем столбцы
16     companiesTable.Columns.Add(compIdColumn);
17     companiesTable.Columns.Add(compNameColumn);
    // добавляем таблицу в dataset
18
    ds.Tables.Add(companiesTable);
19  
20     // вторая таблица - смартфонов компаний
21     DataTable phonesTable = new DataTable("Phones");
22     DataColumn phoneIdColumn = new DataColumn("Id", Type.GetType("System.Int32"));
23     phoneIdColumn.Unique = true;
24     phoneIdColumn.AllowDBNull = false;
25     phoneIdColumn.AutoIncrement = true;
26     phoneIdColumn.AutoIncrementSeed = 1;
27     phoneIdColumn.AutoIncrementStep = 1;
28  
29     DataColumn phoneNameColumn = new DataColumn("Name", Type.GetType("System.String"));
    DataColumn phonePriceColumn = new DataColumn("Price", Type.GetType("System.Decimal"));
30
    // столбец-внешний ключ
31     DataColumn phoneCompanyColumn = new DataColumn("CompanyId", Type.GetType("System.Int32"));
32     // добавляем столбцы в таблицу смартфонов
33     phonesTable.Columns.Add(phoneIdColumn);
34     phonesTable.Columns.Add(phoneNameColumn);
35     phonesTable.Columns.Add(phonePriceColumn);
36     phonesTable.Columns.Add(phoneCompanyColumn);
37     // добавляем таблицу смартфонов
38     ds.Tables.Add(phonesTable);
39  
40     // установка отношений между таблицами
41     ds.Relations.Add("PhonesCompanies", companiesTable.Columns["Id"],
phonesTable.Columns["CompanyId"]);
42
 
43
    // Добавим ряд данных
44     DataRow apple = companiesTable.NewRow();
45     apple.ItemArray = new object[] { null, "Apple" };
46     companiesTable.Rows.Add(apple);
47     DataRow samsung = companiesTable.NewRow();
48     samsung.ItemArray = new object[] { null, "Samsung" };
49     companiesTable.Rows.Add(samsung);
50
51
52
 
53     DataRow iphone5 = phonesTable.NewRow();
54     iphone5.ItemArray = new object[] { null, "iPhone 5", 400, apple["Id"] };
55     phonesTable.Rows.Add(iphone5);
56  
57     DataRow iphone6s = phonesTable.NewRow();
58     iphone6s.ItemArray = new object[] { null, "iPhone 6S", 600, apple["Id"] };
59     phonesTable.Rows.Add(iphone6s);
60  
61     DataRow galaxy6 = phonesTable.NewRow();
62     galaxy6.ItemArray = new object[] { null, "Samsung Galaxy S6", 500, samsung["Id"] };
63     phonesTable.Rows.Add(galaxy6);
64  
    DataRow galaxyace2 = phonesTable.NewRow();
65
    galaxyace2.ItemArray = new object[] { null, "Samsung Galaxy Ace 2", 200, samsung["Id"] };
66     phonesTable.Rows.Add(galaxyace2);
67  
68     // выведем все смартфоны компании Apple
69     DataRow[] rows = apple.GetChildRows(ds.Relations["PhonesCompanies"]);
70     Console.WriteLine("Продукция компании Apple");
71     Console.WriteLine("Id \tСмартфон \tЦена");
72              
73     foreach (DataRow r in rows)
74     {
75         Console.WriteLine("{0} \t{1} \t{2}", r["Id"], r["Name"], r["Price"]);
76     }
77     Console.Read();
}
78
79
80

Ключевым моментом здесь является установка отношения между таблицами:

ds.Relations.Add("PhonesCompanies", companiesTable.Columns["Id"],
1 phonesTable.Columns["CompanyId"]);

Первым параметром здесь идет название отношения, по которому потом на него можно будет ссылаться.
Вторым параметром идет главный столбец - id компании в таблице компаний. Третий параметр -
подчиненный столбец - id компании в таблице смартфонов. Подчиненный столбец зависит от главного.
Вместо этих параметров мы можем передать объект DataRelation, который также будет иметь данные
значения.

Затем используя отношение, мы можем получить для одной строки из таблицы компаний связанные строки
из таблицы смартфонов:
1 DataRow[] rows = apple.GetChildRows(ds.Relations["PhonesCompanies"]);

Внешние ключи

Кроме простого отношения между таблицами мы также можем задать внешние ключи, как в обычных базах
данных. Для этого непосредственно перед строкой:

ds.Relations.Add("PhonesCompanies", companiesTable.Columns["Id"],
1 phonesTable.Columns["CompanyId"]);

Добавим следующий код:

ForeignKeyConstraint foreignKey = new ForeignKeyConstraint(companiesTable.Columns["Id"],


1 phonesTable.Columns["CompanyId"])
2 {
3     ConstraintName = "PhonesCompaniesForeignKey",
4     DeleteRule = Rule.SetNull,

5     UpdateRule = Rule.Cascade

6 };

7 // добавляем внешний ключ в dataset

8 ds.Tables["Phones"].Constraints.Add(foreignKey);

9 // применяем внешний ключ

10 ds.EnforceConstraints = true;

11  
ds.Relations.Add("PhonesCompanies", companiesTable.Columns["Id"],
12
phonesTable.Columns["CompanyId"]);

Внешний ключ представляет объект ForeignKeyConstraint, в конструктор которого передается два


параметра: главный столбец и зависимый столбец. В данном случае это те же самые столбцы.

Также у него мы можем установить ряд свойств. Через свойство ConstraintName устанавливается название


внешнего ключа. СвойстваDeleteRule и UpdateRule определют поведение при удалении или обновлении
данных соответственно в главной таблице (то есть в данном случае в таблице компаний). Эти свойства
принимают одно из значений перечисления Rule:

 Cascade: происходит автоматическое изменение или удаление строк в подчиненной таблице

 None: строки в подчиненной таблице не изменяются

 SetDefault: происходит установка значений по умолчанию для связанных строк (которое


определяется через DataColumn.DefaultValue)

 SetNull: для связанных строк устанавливается значение DBNull

С помощью метода ds.Tables["Phones"].Constraints.Add(foreignKey);  внешний ключ добавляется к таблице


смартфонов. И затем через выражение ds.EnforceConstraints = true; происходит применение внешнего ключа.
И если мы удалим, например, одну из строк из таблицы компаний, то в таблице смартфонов все строки,
которые связаны с удаленной, в столбце CompanyId будут иметь значение null.
LINQ to DataSet
Одним из преимуществ использования DataSet является то, что мы можем использовать для работы с
данными в DataSet технологию LINQ.

Например, выведем из таблицы объекты по какому-нибудь условию:

1
2
3
4 DataSet ds = new DataSet("Store");
5 // таблица компаний
DataTable companiesTable = new DataTable("Companies");
6 DataColumn compIdColumn = new DataColumn("Id", Type.GetType("System.Int32"));
7 DataColumn compNameColumn = new DataColumn("Name", Type.GetType("System.String"));
8 // добавляем столбцы
9 companiesTable.Columns.Add(compIdColumn);
10 companiesTable.Columns.Add(compNameColumn);
// добавляем таблицу в dataset
11 ds.Tables.Add(companiesTable);
12  
13 // вторая таблица - смартфонов компаний
14 DataTable phonesTable = new DataTable("Phones");
15 DataColumn phoneIdColumn = new DataColumn("Id", Type.GetType("System.Int32"));
16 DataColumn phoneNameColumn = new DataColumn("Name", Type.GetType("System.String"));
DataColumn phonePriceColumn = new DataColumn("Price", Type.GetType("System.Decimal"));
17 DataColumn phoneCompanyColumn = new DataColumn("CompanyId", Type.GetType("System.Int32"));
18 // добавляем столбцы в таблицу смартфонов
19 phonesTable.Columns.Add(phoneIdColumn);
20 phonesTable.Columns.Add(phoneNameColumn);
21 phonesTable.Columns.Add(phonePriceColumn);
phonesTable.Columns.Add(phoneCompanyColumn);
22
// добавляем таблицу смартфонов
23 ds.Tables.Add(phonesTable);
24  
25 // Добавим ряд данных
26 companiesTable.Rows.Add(new object[] { 1, "Apple" });
27 companiesTable.Rows.Add(new object[] { 2, "Samsung" });
28  
29 phonesTable.Rows.Add(new object[] { 1, "iPhone 5", 400, 1 });
phonesTable.Rows.Add(new object[] { 2, "iPhone 6S", 600, 1});
30 phonesTable.Rows.Add(new object[] { 3, "Samsung Galaxy S6", 500, 2 });
31 phonesTable.Rows.Add(new object[] { 4, "Samsung Galaxy Ace 2", 200, 2});
32  
33 var query = from phone in ds.Tables["Phones"].AsEnumerable()
34             from company in ds.Tables["Companies"].AsEnumerable()
35             where (int)phone["CompanyId"] == (int)company["Id"]
            where (decimal)phone["Price"] >200
36             select new { Model = phone["Name"], Price = phone["Price"], Company = company["Name"] };
37              
38 foreach (var phone in query)
39     Console.WriteLine("{0} ({1}) - {2}", phone.Model, phone.Company, phone.Price);
40
41
42

Результат работы
В данном случае отображаются все смартфоны стоимостью больше 200 единиц. Чтобы проводить операции
LINQ над таблицами, их надо привести к объекту IEnumerable: ds.Tables["Phones"].AsEnumerable(). По
имени столбцов мы можем получить значения в соответствующих ячейках строк: phone["Price"].

Чтобы избежать необходимости преобразований в запросах, мы можем использовать типизированный


метод Field<T>(), в который передается название столбца, для которого надо получить значение:

1 var query = from phone in ds.Tables["Phones"].AsEnumerable()


2             from company in ds.Tables["Companies"].AsEnumerable()
            where phone.Field<Int32>("CompanyId") == company.Field<Int32>("Id")
3             where phone.Field<Decimal>("Price") > 200
4             select new { Model = phone.Field<string>("Name"), Price = phone["Price"], Company =
5 company["Name"] };
DataSet и XML
Встроенные возможности DataSet позволяют производить некоторые операции с xml-документами, в
частности, сохранять данные из dataset в xml и, наоборот, загружать из xml в dataset. Подобные возможности
позволяют при необходимости использовать dataset в качестве посредника между базой данных и xml-
файлами.

Сохранение данных из dataset в xml осуществляется с помощью метода WriteXml. Например, загрузим


данные из БД в DataSet и затем сохраним их в xml:

1
string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=usersdb;Integrated
2 Security=True";
3 string sql = "SELECT * FROM Users";
4 using (SqlConnection connection = new SqlConnection(connectionString))
5 {
6     connection.Open();
    SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
7
 
8     DataSet ds = new DataSet("Users");
9     DataTable dt = new DataTable("User");
10     ds.Tables.Add(dt);
11     adapter.Fill(ds.Tables["User"]);
12  
13     ds.WriteXml("usersdb.xml");
    Console.WriteLine("Данные сохранены в файл");
14 }
15

В метод WriteXml() передается имя файла, который после записи будет выглядеть примерно следующим
образом:

1 <?xml version="1.0" standalone="yes"?>


2 <Users>
  <User>
3     <Id>2</Id>
4     <Name>Eugene</Name>
5     <Age>31</Age>
6   </User>
7   <User>
    <Id>3</Id>
8     <Name>Tom</Name>
9     <Age>24</Age>
10   </User>
11   <User>
12     <Id>4</Id>
    <Name>Ben</Name>
13     <Age>43</Age>
14   </User>
15   <User>
16     <Id>5</Id>
17     <Name>Sam</Name>
    <Age>56</Age>
18
  </User>
19   <User>
20     <Id>6</Id>
21     <Name>Tom</Name>
22     <Age>26</Age>
  </User>
23
24
25
26
27   <User>
    <Id>7</Id>
28     <Name>Alex</Name>
29     <Age>41</Age>
30   </User>
31   <User>
32     <Id>8</Id>
    <Name>Adam</Name>
33
    <Age>36</Age>
34   </User>
35 </Users>
36
37
38

Для считывания данных из xml применяется метод ReadXml():

1
2 DataSet ds = new DataSet();
3 ds.ReadXml("usersdb.xml");
4 // выбираем первую таблицу
DataTable dt = ds.Tables[0];
5
 
6 foreach (DataColumn column in dt.Columns)
7     Console.Write("\t{0}", column.ColumnName);
8 Console.WriteLine();
9 // перебор всех строк таблицы
10 foreach (DataRow row in dt.Rows)
{
11
    var cells = row.ItemArray;
12     foreach (object cell in row.ItemArray)
13         Console.Write("\t{0}", cell);
14     Console.WriteLine();
15 }
16