Используйте SQL-запросы для отображения данных master-detail в C#
В этом примере SQL-запросы запрашивают данные master-detail только тогда, когда это необходимо. В течение довольно долгого времени .NET-ish способ манипулировать данными из базы данных состоял в том, чтобы загрузить его в DataSet, хранящийся в памяти, а затем работать с ним там. Вы можете отображать данные, привязывать их к элементам управления, изменять значения и, в противном случае, использовать их. Когда вы сделаете какие-либо изменения, вы сохраните изменения обратно в базу данных. (Совсем недавно вы можете использовать инфраструктуру сущности для работы с данными, как если бы она хранилась в объектах вместо базы данных, но способ, которым данные извлекаются и изменения перемещаются обратно в базу данных, аналогичны.)
Этот подход работает хорошо, если вам нужно работать только с небольшими объемами данных, и если несколько пользователей не пытаются работать с одними и теми же данными одновременно. Однако, если у вас есть огромная база данных? Например, если база данных содержит биллинговые записи для 1 миллиона клиентов. Или что, если два пользователя загружают и изменяют одни и те же данные? Как узнать, какие изменения необходимо сохранить в базе данных? В этих случаях загрузка всех данных в DataSet работает не очень хорошо.
Данные мастер-детали также объединяют проблему с подходом .NET-ish, потому что для этого требуется, чтобы вы загружали все данные из основных и подробных таблиц, даже если вы никогда не будете использовать большую часть подробных данных. р>
В этом примере демонстрируется метод, который я использовал в проектах, где могут возникать такие ситуации. Идея состоит в том, чтобы использовать SQL-запросы для загрузки только тех данных, которые вам нужны. Затем вы показываете его непосредственно в элементах управления формы. Вероятно, вы можете использовать привязку данных, чтобы упростить отображение, но приведенная здесь методика достаточно проста и упрощает точное определение того, что происходит.
При запуске программы он использует следующий код для создания подключения к базе данных и сборки списка студентов из таблицы Адреса базы данных.
private OleDbConnection Conn; // Создаем список студентов. private void Form1_Load(object sender, EventArgs e) { // Подготовьте соединение. string connect_string = "Provider=Microsoft.ACE.OLEDB.12.0;" + "Data Source=Students.mdb;" + "Mode=Share Deny None"; Conn = new OleDbConnection(connect_string); // Список студентов. ListStudents(); }
На уровне класса код определяет объект OleDbConnection. Обработчик события Load формы инициализирует этот объект строкой соединения. Затем он вызывает следующий ListStudents метод, чтобы составить список студентов.
// Список студентов в cboStudents. private void ListStudents() { string query = "SELECT StudentId, LastName, FirstName " + "FROM Addresses " + "ORDER BY LastName, FirstName"; OleDbCommand cmd = new OleDbCommand(query, Conn); Conn.Open(); using (OleDbDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { Student student = new Student( (int)reader.GetValue(0), (string)reader.GetValue(1), (string)reader.GetValue(2)); cboStudents.Items.Add(student); } } Conn.Close(); }
Этот метод создает объект OleDbCommand для выполнения запроса, который выбирает StudentId, FirstName и LastName полей из таблицы Адреса. Он открывает соединение с базой данных, выполняет команду и проходит через результаты. Для каждой возвращенной записи метод создает объект Student и добавляет его в ComboBox с именем cboStudents.
В следующем коде показан класс Student.
public class Student { public long StudentId; public string FirstName, LastName; public Student(long student_id, string first_name, string last_name) { StudentId = student_id; FirstName = first_name; LastName = last_name; } // Справка ComboBox отображает имя студента. public override string ToString() { return LastName + ", " + FirstName; } }
Этот класс просто содержит значения студента StudentId, FirstName и LastName. Он предоставляет конструктор, облегчающий инициализацию нового объекта. Он также переопределяет метод ToString, поэтому ListBox или ComboBox будет отображать последние и первые имена объекта Student. р>
Это конец кода, который загружает данные основного ученика при запуске программы. Когда вы выбираете ученика из ComboBox, следующий код отображает подробные данные ученика.
// Отображение данных для выбранного ученика. private void cboStudents_SelectedIndexChanged( object sender, EventArgs e) { // Получить выбранного ученика. Student student = cboStudents.SelectedItem as Student; // Получить остальные данные адреса студента. string address_query = "SELECT Street, City, State, Zip " + "FROM Addresses WHERE StudentId=" + student.StudentId; OleDbCommand address_cmd = new OleDbCommand(address_query, Conn); Conn.Open(); using (OleDbDataReader reader = address_cmd.ExecuteReader()) { // Получить первую (и, надеюсь, последнюю) строку. if (reader.Read()) { txtStreet.Text = (string)reader.GetValue(0); txtCity.Text = (string)reader.GetValue(1); txtState.Text = (string)reader.GetValue(2); txtZip.Text = (string)reader.GetValue(3); } } // Получаем тестовые оценки учащегося. lstScores.Items.Clear(); string scores_query = "SELECT TestNumber, Score " + "FROM TestScores WHERE StudentId=" + student.StudentId + " " + "ORDER BY TestNumber"; OleDbCommand scores_cmd = new OleDbCommand(scores_query, Conn); int test_total = 0; int num_scores = 0; using (OleDbDataReader reader = scores_cmd.ExecuteReader()) { // Получить следующую строку. while (reader.Read()) { lstScores.Items.Add("Test " + (int)reader.GetValue(0) + ": " + (int)reader.GetValue(1)); test_total += (int)reader.GetValue(1); num_scores++; } } Conn.Close(); // Отображение вычисленного среднего значения. if (num_scores == 0) txtAverage.Text = "0"; else txtAverage.Text = (test_total / num_scores).ToString("0.0"); }
Этот код получает объект Student, выбранный в ComboBox. Он создает запрос для получения данных в таблице Students для этого Student объекта StudentId. (Вы можете избежать этого запроса, выбрав эти данные в исходном запросе при запуске программы и сохранении данных в объекте Student.) Программа выполняет запрос и отображает данные адреса студента в текстовых полях. р>
Затем программа создает запрос для получения данных в таблице TestScores для Student объекта StudentId. Программа выполняет запрос и добавляет данные тестовой оценки в lstScores ListBox. Он также сохраняет общее количество баллов и количество записей TestScores.
После того, как он отображает результаты тестов, программа вычисляет средний балл студента и отображает его в txtAverage TextBox.
Вот и все. Эта программа сохраняет память по решению DataSet в двух местах. Во-первых, когда он запускается, программа загружает только имена учеников и идентификаторы. Решение DataSet загрузило бы все данные Addresses. Если в базе данных содержались лоты (тысячи) учащихся, а записи Адреса были большими, DataSet будет использовать гораздо больше памяти.
Во-вторых, метод DataSet также загружает всю таблицу TestScores при запуске программы. Если в базе данных тысячи учеников с десятком или более баллов для тестирования, это потребует большой памяти. Этот пример загружает только тестовые баллы для выбранного вами ученика в любой момент времени.
Одним из преимуществ подхода DataSet является то, что он выполняет весь свой выбор данных в начале, поэтому данные загружаются и готовятся позже, когда вам это нужно. ( DataSet также достаточно умен, чтобы не сохранять данные, которые не были изменены, поэтому он не тратит время на загрузку данных, если вы не вносите никаких изменений.)
Этот пример извлекает данные ученика позже, когда вы выбираете этого ученика. Это требует большего количества поездок в базу данных и, как правило, медленнее, чем одна большая поездка туда и обратно. Однако, если у вас большая база данных, вам, вероятно, не нужно будет использовать большую часть данных. Выбор заключается в выборе всего всего сразу или выборе крошечной части базы данных в нескольких запросах.
Подход с несколькими запросами также выполняет свои запросы, когда пользователь выбирает ученика. Есть небольшая задержка, но пользователь этого не замечает. (Фактически, в первый раз, когда вы выбираете ученика, наблюдается заметная задержка. Я думаю, что база данных тратит время на создание плана выполнения запроса и кэширует план для последующего использования. Также может происходить кэширование данных здесь. кто-нибудь имеет более глубокое понимание этого, пожалуйста, напишите комментарий.)
В этом примере вы не можете изменять данные и сохранять изменения. Я покажу, как вы можете это сделать в более позднем посте.