Обновление базы данных

49

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Проведение обновлений в базе данных посредством LINQ to SQL столь же просто, как изменение свойств объекта, вызов метода SubmitChanges объекта DataContext и последующая обработка возможных конфликтов параллельного доступа. Пусть вопрос обработки конфликтов параллельного доступа вас не пугает — существует несколько вариантов обработки подобных конфликтов, и ни один из них не является слишком болезненным.

Конечно, все это так просто только в случае правильно написанных сущностных классов, корректно отображаемых на базу данных, и согласованности графа.

Обновление ассоциированных классов

В соответствии с проектным решением LINQ to SQL позволяет обновлять любую сторону ассоциированных классов, чтобы удалить отношение между ними. Можно обновить ссылку родительского объекта на один из его дочерних объектов либо обновить ссылку дочернего объекта на родителя. Очевидно, что ссылки на каждом конце отношения должны обновляться, но при этом вам нужно обновить только одну или другую сторону.

О поддержке согласованности объектной модели при обновлении одной из сторон заботится не LINQ to SQL; за это отвечает сущностный класс.

Однако если поручить создание сущностных классов утилите SQLMetal и средству Object Relational Designer, то они это должным образом обработают.

Обновление ссылки на родителя у дочернего объекта

Поскольку можно обновить любую из сторон отношения, выберем вариант с обновлением ссылки на родителя дочернего объекта. В качестве примера давайте рассмотрим пример, как можно было бы изменить сотрудника, которому поручается заказ, в базе данных Northwind. Этот пример более сложен, чем многие другие, поэтому он снабжается дополнительными пояснениями:

// Используйте свою строку подключения
            Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                                           AttachDbFilename=C:\Northwind.mdf;
                                           Integrated Security=True");

            Order order = (from o in db.Orders
                           where o.EmployeeID == 5
                           orderby o.OrderDate descending
                           select o).First<Order>();
                           
            // Сохранить текущего сотрудника, чтобы восстановить его в конце
            Employee origEmployee = order.Employee;

В приведенном коде после получения DataContext запрашивается самый последний заказ сотрудника с EmployeeID, равным 5, за счет упорядочивания заказов этого лица по убыванию дат и вызова операции First. Это даст самый свежий заказ. Сохранение ссылки на исходного сотрудника, которому был поручен этот заказ, позволит восстановить его в конце примера. Эта ссылка сохраняется в переменной по имени origEmployee.

string s = "Перед изменением сотрудника: \n";
s += String.Format("OrderID = {0} : OrderDate = {1} : EmployeeID = {2}",
                order.OrderID, order.OrderDate, order.Employee.EmployeeID);

Затем сохраняется сообщение, уведомляющее, что сотрудник для извлеченного заказа еще не изменен, за которым следует идентификатор заказа и сотрудника. Должно быть видно, что заказ принят сотрудником с идентификатором 5, поскольку именно его заказ был запрошен.

Employee emp = (from e in db.Employees
                            where e.EmployeeID == 9
                            select e).Single<Employee>();

            // Назначить для заказа нового сотрудника
            order.Employee = emp;
            db.SubmitChanges();

Затем запрашивается другой сотрудник, с EmployeeID, равным 9; этот сотрудник будет назначен ответственным за ранее извлеченный заказ. После этого изменение сохраняется с помощью вызова метода SubmitChanges.

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

Order order2 = (from o in emp.Orders
                            where o.OrderID == order.OrderID
                            select o).First<Order>();

В приведенном коде запрашивается измененный заказ по его OrderID в коллекции Orders нового заказчика. Если он будет найден, это докажет, что отношение между сотрудником и заказом было обновлено на обоих концах.

s += String.Format("\nПосле изменения сотрудника: \nOrderID = {0} : OrderDate = {1} : EmployeeID = {2}",
                order2.OrderID, order2.OrderDate, order2.Employee.EmployeeID);
                
MessageBox.Show(s);

Здесь просто выводится сообщение, говорящее о том, что ниже будет отображен заказ после проведенного изменения — передачи его новому сотруднику emp. Затем выводится информация о заказе. Теперь можно видеть, что EmployeeID сотрудника, которому он передан, равен 9. Ранее он был равен 5.

//Теперь необходимо отменить изменения, 
// чтобы пример можно было запускать многократно
order.Employee = origEmployee;
db.SubmitChanges();

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

Изменение отношения присвоением нового родителя

Как видите, сотрудником, которому был назначен заказ перед проведением изменения, был тот, у которого EmployeeID равен 5. После замены назначенного на заказ сотрудника его EmployeeID равен 9. Это доказывает, что заказ был изменен в отношении и со стороны сотрудника.

В данном примере была обновлена ссылка на родителя в дочернем объекте, где дочерним выступает заказ, а родителем — сотрудник. Но есть и другой подход, посредством которого можно было получить тот же результат. Можно было бы обновить ссылку на дочерний объект в родительском объекте.

Обновление ссылки на дочерний объект в родительском объекте

Другой подход к изменению отношения между двумя объектами заключается в удалении дочернего объекта из коллекции EntitySet<T> родительского объекта и добавление его в коллекцию EntitySet<T> другого родительского объекта. В следующем примере удаляется заказ из коллекции заказов сотрудника. Поскольку этот пример проще, чем предыдущий, детальные пояснения приводиться не будут, но существенные отличия в коде выделяются полужирным:

Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                                           AttachDbFilename=C:\Northwind.mdf;
                                           Integrated Security=True");

            Order order = (from o in db.Orders
                           where o.EmployeeID == 5
                           orderby o.OrderDate descending
                           select o).First();

            // Сохранить текущего сотрудника, чтобы восстановить его в конце
            Employee origEmployee = order.Employee;

            string s = "Перед изменением сотрудника: \n";
            s += String.Format("OrderID = {0} : OrderDate = {1} : EmployeeID = {2}\n",
                order.OrderID, order.OrderDate, order.Employee.EmployeeID);

            Employee emp = (from e in db.Employees
                            where e.EmployeeID == 9
                            select e).Single();

            // Удалить заказ из Orders исходного сотрудника
            origEmployee.Orders.Remove(order);

            // Добавить его к заказам нового сотрудника
            emp.Orders.Add(order);
            db.SubmitChanges();

            s += String.Format("\nПосле изменения сотрудника: \nOrderID = {0} : OrderDate = {1} : EmployeeID = {2}",
                order.OrderID, order.OrderDate, order.Employee.EmployeeID);

            MessageBox.Show(s);

В этом коде извлекается самый последний заказ для сотрудника с EmployeeID, равным 5, который затем сохраняется в переменной origEmployee, чтобы восстановить в конце примера. Перед сменой сотрудника информация о заказе сохраняется в переменной s. После этого извлекается сотрудник с EmployeeID, равным 9, и ссылка на него сохраняется в переменной emp. До этого момента код совпадает с предыдущим.

Затем заказ удаляется из коллекции заказов исходного сотрудника и добавляется в коллекцию заказов нового сотрудника. После этого вызовом метода SubmitChanges изменения в базе данных сохраняются. Далее после проведенных изменений информация о заказе выводится на экран. И, наконец, заказ восстанавливается в исходном состоянии, чтобы пример можно было запускать многократно.

Результат работы этого кода аналогичен предыдущему.

Пройди тесты
Лучший чат для C# программистов