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

Выполнение обновлений базы данных из приложений Silverlight.

Скачать исходный код - 1.03 Мб

Введение

Данная статья показывает метод обновления баз данных из приложения Silverlight 2.

Благодаря новому выпуску Silverlight 2 бета 2 стало проще обрабатывать данные базы данных и обращаться к ним. Так как приложения Silverlight не могут напрямую обращаться к локальным ресурсам, доступ к данным обеспечивается через веб-службы, такие как Основа связи Windows (WCF) или ASMX. Есть много примеров, показывающих, как считывать данные и заполнять объекты данных, но очень мало тех, которые предоставляют какие-либо средства записи изменений обратно в источник данных.

Один пример, найденный здесь, использует службы данных ADO.NET (Astoria), все еще находящиеся в стадии разработки.

Другое решение, найденное здесь, использует LINQ и делает копию исходных данных. Оно отправляет исходный и измененный наборы данных обратно на сервер во время обновления. Если вы имеете 50 строк данных, вы отправляете до 100 строк обратно. Хотя данный подход работает, отправлять так много данных невыгодно, если нужно внести лишь несколько изменений. Страница содержит видео, и на исходник, несомненно, стоит посмотреть.

Данная статья показывает метод обновления данных базы данных путем отправки обратно лишь изменений. Она предполагает некоторые базовые знания операций Silverlight 2, таких как вызов веб-сервиса и привязка данных к управляющему элементу.

Исходник был написан на C# в Visual Studio 2008 и в Silverlight 2 бета 2.

Использование кода

Приложенное исходное решение содержит пять проектов:

DataWebService

Открывает таблицы Northwindчерез WCFс помощью LINQ. Этот сервис также содержит логику, обновляющую базу данных путем внесения в нее изменений, переданных от клиента Silverlight.

UpdateChangesToLINQ

Предоставляет логику для обновления путем внесения изменений с помощью контекста данных LINQ. Он используется проектомDataWebServices.

TrackChanges

Следит за изменениями объектов. Используется в SilverlightUpdateChanges.

DemoUpdateClass

Консольное приложение, демонстрирующее обновление данных. Полезно для отладки класса UpdateChanges.

SilverlightUpdateChanges

Базовоеприложение Silverlight,отображающееDataGrid,связаннуюстаблицей Northwind Customer (клиент). Эта таблица заполняется путем вызоваDataWebService.

Перед запуском примеров измените путь в DataWebService, чтобы он указывал на включенный файл MDF базы данных Northwind. Это стандартный образец базы данных Northwind с дополнительной таблицей testtable. Данная таблица содержит большинство типов данных SQL, и полезна для тестирования различных типов, и также показывает, как сетка Silverlight обрабатывает их.

Решение имеет две основные части: отслеживание изменений, сделанных в приложении Silverlight, и передача изменений для обновления базы данных.

Отслеживание изменений

При добавлении ссылки веб-сервиса на проект Silverlight, VS 2008 генерирует классы-посредники. Эти классы основаны на классах, предоставляемых через сервис. Если сервис содержит объекты, использующие объект Customer, аналогичный объект-посредник клиент создается на стороне Silverlight. Эти классы-посредники скрытые, но вы можете просмотреть их, нажав кнопку “Показать все файлы” в Проводнике решений. Они хранятся в файле Reference.cs, одном для каждого сервиса.

В классах-посредниках структура данных реализована в интерфейсе INotifyPropertyChanged. Он требует реализации обработчика события PropertyChanged, вызываемого всякий раз, когда производится изменение свойства. По умолчанию этот обработчик события не назначен.

Следующий код содержит простой класс Widget («штучка»), реализующий событие INotifyPropertyChanged:

public partial class Widget: object, System.ComponentModel.INotifyPropertyChanged
{
    private string _ID;
    private string _Name;
    private string _Colour;

    public string ID
    {
        get
        {
            return this._ID;
        }
        set
        {
            if ((object.ReferenceEquals(this._ID, value) != true))
            {
                this._ID = value;
                this.RaisePropertyChanged("ID");
            }
        }
    }
   
    public string Name
    {
        get
        {
            return this._Name;
        }
        set
        {
            if ((object.ReferenceEquals(this._Name, value) != true))
            {
                this._Name= value;
                this.RaisePropertyChanged("Name");
            }
        }
    }

    public string Colour
    {
        get
        {
            return this._Colour;
        }
        set
        {
            if ((object.ReferenceEquals(this._Colour, value) != true))
            {
                this._Colour= value;
                this.RaisePropertyChanged("Colour");
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        System.ComponentModel.PropertyChangedEventHandler
                              propertyChanged = this.PropertyChanged;
        if ((propertyChanged != null))
        propertyChanged(this, new
                System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

Преимущество использования данного обработчика событий в том, что он будет запускаться, когда выполняется любое изменение в свойстве, независимо от того, выполнено оно через код или через элемент управления данными. Чтобы подключить обработчик события, назначьте метод обработчика события объекту. Следующий код показывает, как назначить обработчик события объекту Widget. Всякий раз, когда меняется свойство Widget, вызывается обработчик события:

public void TestHandler()
{
    //
    Widget testWidget = new Widget();
    testWidget.ID = "100";
    testWidget.PropertyChanged += new
      System.ComponentModel.PropertyChangedEventHandler(this.Notifychanges);
    testWidget.Name = "Big Widget";
    testWidget.Colour = "Green";
}
public void Notifychanges(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    PropertyInfo p;
    p = sender.GetType().GetProperty(e.PropertyName);
    //получаем значение измененного свойства
    string changeValue = p.GetValue(sender, null).ToString();
   
    System.Windows.Browser.HtmlPage.Window.Alert("Changing " +
                   e.PropertyName + " to " + changeValue);
}

Включенная библиотека классов TrackChanges работает путем отслеживания изменений объекта с помощью данного обработчика событий. Изменения любого свойства объекта вызывают срабатывание события. Изменения сохраняются в объекте Dictionary (словарь).

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

Имя таблицы должно совпадать с атрибутом TableName, назначенным в источнике данных LINQ веб-сервиса (дополнительную информацию смотрите ниже). Массив ключей чувствителен к регистру, он должен совпадать с регистром имени свойства объекта.

В следующем примере один экземпляр Customer создается и назначается объекту ChangeTracking:

Customer testCustomer = new Customer();
//нужно назначить идентификатор для отслеживания, чтобы знать, что отслеживать..
testCustomer.CustomerID = "10000";
//начинаем отслеживать изменения
ChangeTracking customerTrack = new ChangeTracking("dbo.Customers",
               new string[] { "CustomerID" }, testCustomer);

testCustomer.CompanyName = "Test Company";
testCustomer.ContactName = "Fred Smith";
testCustomer.ContactTitle = "Consultant";

Типовой проект SilverlightUpdateChanges загружает все записи из таблицы Northwind Клиенты и из testtable в решетки данных. Любые сделанные изменения фиксируются в базе данных при нажатии кнопки „Сохранить изменения”.
Код ниже иллюстрирует часть проекта SilverlightUpdateChanges. После загрузки формы решетки клиента и тестовой таблицы заполняются путем вызова методов веб-сервиса. Они возвращают список всех записей из этих таблиц. Создается экземпляр класса ChangeTrack, передающий имя таблицы, массив ключей и объект для отслеживания изменений. Создание этого класса связывает обработчик события NotifyChanges для каждого объекта с обработчиком события внутри класса.

TrackChanges.ChangeTracking customerTracking;
TrackChanges.ChangeTracking testTracking;

void Page_Loaded(object sender, RoutedEventArgs e)
{
    //создаем экземпляр веб-сервиса и получаем клиентов
    NorthwindSvc.NorthwindSvcClient nwindClient =
                       new NorthwindSvc.NorthwindSvcClient();

//добавляем обработчик события для асинхронного вызова GetCustomers
//и вызываем GetCustomersAsync, чтобы заполнить элементы
    nwindClient.GetCustomersCompleted += new
      EventHandler<SilverlightUpdateChanges.NorthwindSvc.
      GetCustomersCompletedEventArgs>(client_GetAllCustomersCompleted);
    nwindClient.GetCustomersAsync();

    nwindClient.GetTestItemsCompleted += new
      EventHandler<GetTestItemsCompletedEventArgs>(
      nwindClient_GetTestItemsCompleted);
    nwindClient.GetTestItemsAsync();
}

void nwindClient_GetTestItemsCompleted(object sender,
                 GetTestItemsCompletedEventArgs e)
{
  //получаем список тестовых элементов
  List<testtable> testList = e.Result.ToList();

  //начинаем отслеживать любые изменения в testList
  testTracking = new ChangeTracking("dbo.testtable",
                 new string[] { "id" }, testList);

  grdTestItems.ItemsSource = testList;
  grdTestItems.Columns[0].IsReadOnly = true;
}


void client_GetAllCustomersCompleted(object sender,
     SilverlightUpdateChanges.NorthwindSvc.GetCustomersCompletedEventArgs e)
{
  //получаем список клиентов
  List<Customer> customerList = e.Result.ToList();

  //начинаем отслеживать любые изменения в customerList
  customerTracking = new ChangeTracking("dbo.Customers",
                     new string[] { "CustomerID" }, customerList);
  grdCustomers.ItemsSource = customerList;
}

Все изменения сохраняются в объекте Dictionary в классе ChangeTracking. Ключ словаря содержит ключ к записи и изменяемое поле/свойство. Эти значения разделены разделителем. Разделитель по умолчанию – вертикальная черта ('|'). Если свойство CustomerName таблицы Northwind Customers изменяется для записи компании 'Вокруг рога', ключ выглядит так: AROUT|CustomerName (имя клиента). Измененное значение сохраняется в соответствующем свойстве Value словаря.

Обновление изменений

Изменения данных SilverlightUpdateChanges могут быть зафиксированы в базе данных путем вызова метода SubmitChangesAsync веб-сервиса DataWebServices. Данный метод требует объект словаря изменений и имя таблицы.
Проект DataWebServices использует класс UpdateChanges для передачи изменений в базе данных. Конструктор UpdateChanges не требует аргументов. Класс UpdateChanges предоставляет метод SubmitChanges, передающий изменения, которые нужно выполнить. SubmitChanges требует объект LINQ DatabaseContext (контекст базы данных) для данных, которые вы хотите изменить, имя таблицы, и объект словаря изменений.

Аргумент имени таблицы должен совпадать с атрибутом имени таблицы, назначенным классу LINQ. Вы можете просматривать атрибуты, посмотрев на определение класса в файле DBML LINQ. Оно выглядит примерно так:

[Table(Name="dbo.Customers")]
Public partial class Customer : INotifyPropertyChanging,
                                INotifyPropertyChanged

Вы должны включить схему, если она является атрибутом. Следующий фрагмент кода вызывает метод SubmitChanges, передающий словарь изменений для обновления таблицы dbo.Customers:

NorthwindDataContext northwindDB = new NorthwindDataContext(connectionString);
//создаем новый объект UpdateChanges (обновить изменения) и передаем изменения
//в базу данных клиенты с помощью провайдера northwindDB LINQ Northiwind
UpdateChanges testChanges = new UpdateChanges();
return (testChanges.SubmitChanges(northwindDB,
        "dbo.Customers", changesDictionary));

Метод SubmitChanges выполнит оператор UPDATE (обновить) по отношению к каждой измененной записи. Он выполнит только один оператор UPDATE на каждую запись. Если вы сделали более одного изменения в данной записи, он объединит обновления в одном операторе.

SubmitChanges возвращает строку, показывающую, сколько успешных обновлений было сделано, и сколько операторов было выполнено.

Класс UpdateChanges также предоставляет следующие свойства:

 Свойство

Описание

ErrorList

Список исключений из любых ошибок обновления.

SuccessCounter

Число успешных операторов обновления.

ExecutionCounter

Число выполненных операторов обновления.

Delimiter

Разделитель ключей. По умолчанию вертикальная черта '|'.

Проблемы

Есть несколько плюсов и минусов использования указанных классов в их текущей форме.

Плюсы:
•    Отправляют обратно только изменения
•    Быстрое выполнение базы данных и минимальный трафик базы данных при использовании прямых операторов UPDATE

Минусы
•    Не предоставляют механизма оптимистической блокировки
•    Работают только в отношении сервера SQL
•    Сейчас только обновления – нет удалений или добавлений

Удаление записи было бы довольно легким. Вставка также возможна, но нужно будет реализовать алгоритм возврата ключей новых значений. Добавление оптимистической блокировки потребовало бы возврата нескольких или всех полей.

Рассматривался вариант создания строки SQL на стороне Silverlight и отправки ее обратно веб-сервису. Он решил бы несколько из этих проблем. Но проблемы безопасности велики; возможность выполнять любые операторы SQL в отношении вашей базы данных слишком опасна. Процесс шифровки и схема защищенной идентификации могут решить некоторые из этих проблем.

Другая проблема – разделитель ключей, сейчас представленный вертикальной чертой |. Если любые измененные свойства содержат данное значение, операция обновления не выполнится. Это легко исправить, поменяв это значение на более редкое (классы отслеживания и обновления могут изменить его через свойство Delimiter). Если имя свойства не совпадает с именем соответствующего поля таблицы, то это не будет работать правильно. Данный недостаток устраняется созданием метода сопоставления.

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

Хитрости

В работе с Silverlight 2 есть несколько хитростей. Одна – стандартный стартовый проект. Можно предположить, что это был бы проект Silverlight, но в действительности это веб-сервис. Если проект Silverlight сделан стартовым, вы можете получить ошибки связи при запуске проекта в режиме отладки (нажатием F5). Но запуск не в режиме отладки (Ctrl-F5) работает.

Другая – временами появляющееся предупреждение:

Предупреждение специального инструмента: Не удается загрузить один или более из запрошенных типов.
Найдите свойство LoaderExceptions для дополнительной информации.

G:\Data\VS\VS2008\Silverlight\SilverlightUpdateChanges\SilverlightUpdateChanges\
   Service References\NorthwindSvc\Reference.svcmap.

Кажется, оно не влияет на выполнение, но является странным, и может быть связано с бета-статусом Silverlight. Оно исчезает при перезапуске Visual Studio.

Заключение

В статье было дано решение для обновления баз данных из Silverlight. Текущая функциональность требует улучшения путем решения некоторых из поднятых ранее проблем. Любые отклики, включая критику(конструктивную),приветствуются.