Цель работы: освоить технологию работы с локальными базами данных на Windows Phone
В этой лабораторной работе мы рассмотрим приложение, описанное в материале "Как создать MVVM-приложение для Windows Phone, работающее с локальной базой данных" (http://msdn.microsoft.com/library/windowsphone/develop/hh286405%28v=vs.105%29.aspx). Загрузить проект приложения можно здесь: "Пример работы с локальной базой данных" (http://code.msdn.microsoft.com/Local-Database-Sample-57b1614c).
В проекте приложения, рис. 35.1., присутствует несколько страниц, это главная страница – MainPage.xaml, и страница, которая используется для добавления новых объектов (задач, в контексте приложения) в базу данных. Это – страница NewTaskPage.xaml.
(рис 35.1) Проект приложения, иллюстрирующего работу с базами данных
В папке Model расположен файл ToDoDataModel.cs. Это, в терминологии MVVM, модель данных, с которой работает приложение. Модель представления описана в файле ToDoViewModel.cs, который расположен в папке ViewModel.
Рассмотрим особенности реализации данного приложения. Стоит отметить, что ключевым в работе с базами данных для Windows Phone, является класс System.Data.Linq.DataContext (http://msdn.microsoft.com/en-us/library/windowsphone/develop/system.data.linq.datacontext%28v=vs.105%29.aspx). Он служит посредником между базой данных и Windows Phone-приложением. Не стоит путать его со свойством DataContext объекта FrameworkElement (http://msdn.microsoft.com/en-us/library/windowsphone/develop/system.windows.frameworkelement.datacontext%28v=vs.105%29.aspx), последний используется в системе привязки данных, здесь же мы говорим о взаимодействии с базой данных.
SDF-файлы локальных баз данных, используемых приложениями для Windows Phone, располагаются в локальных папках приложений, а значит, база данных приложения доступна только ему. На Windows Phone не предусмотрено чего-то вроде постоянно исполняющегося сервера баз данных – работа с базой осуществляется только в процессе приложения. Для работы с базами данных используется Linq to SQL, Transact-SQL не поддерживается.
Контекст данных, который используется для вывода информации на главной странице приложения, устанавливается на статическую переменную ViewModel типа ToDoViewModel, объявленную в классе App. В этом же классе, в конструкторе (Листинг 35.1.), готовится строка соединения c базой данных(connection string). Она указывает на локальный файл базы данных. Схема "isostore:" указывает на локальную папку приложения, где и будет храниться файл базы данных. Структура строки соединения представляет собой пару вида "ключ=значение".
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
// Директивы
using LocalDatabaseSample.Model;
using LocalDatabaseSample.ViewModel;
namespace sdkLocalDatabaseCS
{
public partial class App : Application
{
// Статическая переменная ViewMode для использования в приложении.
private static ToDoViewModel viewModel;
public static ToDoViewModel ViewModel
{
get { return viewModel; }
}
/// <summary>
/// Конструктор объекта Application.
/// </summary>
public App()
{
// Укажем строку соединения с локальной базой данных.
string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
// Если база данных не существует – создадим её.
using (ToDoDataContext db = new ToDoDataContext(DBConnectionString))
{
if (db.DatabaseExists() == false)
{
// Создание локальной базы данных.
db.CreateDatabase();
// Предварительное заполнение категорий базы данных.
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Home" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Work" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Hobbies" });
// Сохранение категорий.
db.SubmitChanges();
}
}
// Создание объекта viewModel с использованием базы данных.
viewModel = new ToDoViewModel(DBConnectionString);
// Выполнение запроса к базе данных и загрузка коллекций, используемых при привязке данных.
viewModel.LoadCollectionsFromDatabase();
}
При первом запуске приложения файла базы данных не существует, поэтому, если соответствующая проверка на это указывает, выполняется создание базы данных по указанной строке соединения. Здесь используется объект типа ToDoDataContext, то есть – класс, являющийся моделью данных приложения. Этот класс, как мы увидим ниже, он унаследован от класса System.Data.Linq.DataContext, то есть, он может пользоваться его возможностями, среди которых – и создание базы данных. В данном случае метод CreateDatabase() – это метода родительского класса. После создания базы данных выполняется её первоначальное заполнение. Это реализовано с помощью метода InsertOnSubmit() объекта InsertOnSubmit. Данный объект – System.Data.Linq.Table – то есть – таблица базы данных, определен в классе ToDoDataContext.
После первоначального заполнения база данных сохраняется.
Если база данных уже существовала (или если она была уже создана, и та и другая ситуации обрабатываются одинаково), выполняется создание новой модели представления viewModel типа ToDoViewModel с указанием строки соединения с базой. Эта закрытая статическая переменная, в итоге, используется, через открытую переменную ViewModel, в качестве контекста данных главной страницы приложения.
На завершающем этапе инициализации происходит вызов метода модели представления LoadCollectionsFromDataBase. Здесь выполняются запросы к базе для заполнения коллекций, определенных в модели представления и используемых при привязке данных.
Главная страница приложения MainPage.xaml и страница NewTaskPage.xaml, взаимодействуют с моделью представления, во-первых, с помощью системы привязки данных, во-вторых – с помощью вызовов некоторых методов модели представления из кода. В частности, страница NewTaskPage вызывает метод модели представления AddToDoItem, передавая ему данные о новом элементе, который нужно создать, страница MainPage вызывает метод DeleteToDoItem.
Рассмотрим код файла ToDoViewModel.cs, Листинг 35.2.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
// Директива для работы с моделью данных.
using LocalDatabaseSample.Model;
namespace LocalDatabaseSample.ViewModel
{
public class ToDoViewModel : INotifyPropertyChanged
{
// Контекст данных LINQ to SQL для доступа к локальной базе данных.
private ToDoDataContext toDoDB;
// Конструктор класса, создание объекта контекста данных.
public ToDoViewModel(string toDoDBConnectionString)
{
toDoDB = new ToDoDataContext(toDoDBConnectionString);
}
// Коллекция, отображающая все элементы.
private ObservableCollection<ToDoItem> _allToDoItems;
public ObservableCollection<ToDoItem> AllToDoItems
{
get { return _allToDoItems; }
set
{
_allToDoItems = value;
NotifyPropertyChanged("AllToDoItems");
}
}
// Коллекция, отображающая элементы категории home.
private ObservableCollection<ToDoItem> _homeToDoItems;
public ObservableCollection<ToDoItem> HomeToDoItems
{
get { return _homeToDoItems; }
set
{
_homeToDoItems = value;
NotifyPropertyChanged("HomeToDoItems");
}
}
// Коллекция, отображающая элементы категории work.
private ObservableCollection<ToDoItem> _workToDoItems;
public ObservableCollection<ToDoItem> WorkToDoItems
{
get { return _workToDoItems; }
set
{
_workToDoItems = value;
NotifyPropertyChanged("WorkToDoItems");
}
}
// Коллекция, отображающая элементы категории hobbies.
private ObservableCollection<ToDoItem> _hobbiesToDoItems;
public ObservableCollection<ToDoItem> HobbiesToDoItems
{
get { return _hobbiesToDoItems; }
set
{
_hobbiesToDoItems = value;
NotifyPropertyChanged("HobbiesToDoItems");
}
}
// Список всех категорий, который используется страницей, выполняющей добавление задач.
private List<ToDoCategory> _categoriesList;
public List<ToDoCategory> CategoriesList
{
get { return _categoriesList; }
set
{
_categoriesList = value;
NotifyPropertyChanged("CategoriesList");
}
}
// Запись изменений в базу данных.
public void SaveChangesToDB()
{
toDoDB.SubmitChanges();
}
// Запрос к базе данных и загрузка коллекций и списка, которые используются главной страницей
public void LoadCollectionsFromDatabase()
{
// Запрос на получение всех элементов из базы данных.
var toDoItemsInDB = from ToDoItem todo in toDoDB.Items
select todo;
// Заполнение списка, содержащего все элементы базы.
AllToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
// Запрос на получение всех категорий из базы данных.
var toDoCategoriesInDB = from ToDoCategory category in toDoDB.Categories
select category;
// Запрос к базе данных с загрузкой всех элементов в соответствующие им категории
foreach (ToDoCategory category in toDoCategoriesInDB)
{
switch (category.Name)
{
case "Home":
HomeToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
case "Work":
WorkToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
case "Hobbies":
HobbiesToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
default:
break;
}
}
// Загрузка списка всех категорий.
CategoriesList = toDoDB.Categories.ToList();
}
// Добавление нового элемента в базу данных и в коллекцию.
public void AddToDoItem(ToDoItem newToDoItem)
{
// Добавление нового элемента в контекст данных.
toDoDB.Items.InsertOnSubmit(newToDoItem);
// Сохранение изменений в базе данных.
toDoDB.SubmitChanges();
// Добавление элемента в коллекцию, которая используется для отображения всех элементов
AllToDoItems.Add(newToDoItem);
// Добавление элемента в коллекцию, соответствующую его категории.
switch (newToDoItem.Category.Name)
{
case "Home":
HomeToDoItems.Add(newToDoItem);
break;
case "Work":
WorkToDoItems.Add(newToDoItem);
break;
case "Hobbies":
HobbiesToDoItems.Add(newToDoItem);
break;
default:
break;
}
}
// Удаление элемента из базы данных и из коллекции.
public void DeleteToDoItem(ToDoItem toDoForDelete)
{
// Удаление элемента из коллекции, которая используется для отображения всех элементов.
AllToDoItems.Remove(toDoForDelete);
// Удаление элемента из контекста данных.
toDoDB.Items.DeleteOnSubmit(toDoForDelete);
// Удаление элемента из соответствующей ему категории.
switch (toDoForDelete.Category.Name)
{
case "Home":
HomeToDoItems.Remove(toDoForDelete);
break;
case "Work":
WorkToDoItems.Remove(toDoForDelete);
break;
case "Hobbies":
HobbiesToDoItems.Remove(toDoForDelete);
break;
default:
break;
}
// Сохранение элементов в базе данных.
toDoDB.SubmitChanges();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Генерирует уведомления для системы связывания данных об изменении свойства
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
В конструкторе класса создаётся новый объект ToDoDataContext с использованием строки соединения, которая передаётся в этот конструктор при создании объекта ToDoViewModel в классе App.
Далее, выполняется загрузка данных из базы (LoadCollectionsFromDataBase()) и запись их в соответствующие коллекции. Коллекции используются при привязке данных для отображения списков элементов.
Здесь же определены методы, которые используются для добавления новых данных в базу данных (AddToDoItem), и удаления (DeleteToDoItem). При модификации состава данных в базе данных производится и соответствующая модификация коллекций, которые используются для отображения данных в интерфейсе.
В файле ToDoDataContext.cs имеется класс ToDoDataContext, он является наследником DataContext, при создании экземпляра класса передаёт родительскому классу строку соединения и задаёт два открытых поля типа Table<>, используя классы, представляющие собой объектные представления таблиц (классы сущностей) базы данных, описанные в этом же файле. А именно, речь идёт о классах ToDoItem и ToDoCategory, объявленных с атрибутом [Table]. Фактически, эти классы, во-первых, содержат описания структуры таблиц (полей таблицы, или столбцов, объявленных с атрибутом [Column]) и логику, необходимую для реализации интерфейсов INotifyPropertyChanged и INotifyPropertyChanging.
В Листинге 35.3 приведен код файла ToDoDataContext.cs
using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace LocalDatabaseSample.Model
{
public class ToDoDataContext : DataContext
{
// Передача строки соединения базовому классу.
public ToDoDataContext(string connectionString)
: base(connectionString)
{ }
// Указание таблицы для элементов.
public Table<ToDoItem> Items;
// Указание таблицы для категорий.
public Table<ToDoCategory> Categories;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Определяет ID: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity",
CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get { return _toDoItemId; }
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
// Определяет имя элемента: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private string _itemName;
[Column]
public string ItemName
{
get { return _itemName; }
set
{
if (_itemName != value)
{
NotifyPropertyChanging("ItemName");
_itemName = value;
NotifyPropertyChanged("ItemName");
}
}
}
// Определяет значение завершения: закрытое поле, общедоступное поле,
и столбец в таблице базы данных.
private bool _isComplete;
[Column]
public bool IsComplete
{
get { return _isComplete; }
set
{
if (_isComplete != value)
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
}
// Столбец для определения производительности обновления.
[Column(IsVersion = true)]
private Binary _version;
// Колонка Internal для связанного значения ID ToDoCategory
[Column]
internal int _categoryId;
// Ссылка на сущность, для идентификации таблицы ToDoCategory "хранилища"
private EntityRef<ToDoCategory> _category;
// Ассоциация, для описания взаимосвязи между данным ключом и таблицей "хранилища"
[Association(Storage = "_category",
ThisKey = "_categoryId", OtherKey = "Id", IsForeignKey = true)]
public ToDoCategory Category
{
get { return _category.Entity; }
set
{
NotifyPropertyChanging("Category");
_category.Entity = value;
if (value != null)
{
_categoryId = value.Id;
}
NotifyPropertyChanging("Category");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Используется для выдачи уведомлений о том, что свойство изменено
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Используется для выдачи уведомлений о том, что свойство может измениться
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
[Table]
public class ToDoCategory : INotifyPropertyChanged, INotifyPropertyChanging
{
// Определяет ID: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private int _id;
[Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
public int Id
{
get { return _id; }
set
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanged("Id");
}
}
// Определяет имя категории: закрытое поле, общедоступное поле, и столбец в таблице базы данных
private string _name;
[Column]
public string Name
{
get { return _name; }
set
{
NotifyPropertyChanging("Name");
_name = value;
NotifyPropertyChanged("Name");
}
}
// Столбец для определения производительности обновления.
[Column(IsVersion = true)]
private Binary _version;
// Определяет набор сущностей для той стороны взаимодействия,
которая имеет отношение к коллекции
private EntitySet<ToDoItem> _todos;
[Association(Storage = "_todos", OtherKey = "_categoryId",
ThisKey = "Id")]
public EntitySet<ToDoItem> ToDos
{
get { return this._todos; }
set { this._todos.Assign(value); }
}
// Назначение обработчиков для операций добавления и удаления, соответственно
public ToDoCategory()
{
_todos = new EntitySet<ToDoItem>(
new Action<ToDoItem>(this.attach_ToDo),
new Action<ToDoItem>(this.detach_ToDo)
);
}
// Вызывается в ходе операции добавления
private void attach_ToDo(ToDoItem toDo)
{
NotifyPropertyChanging("ToDoItem");
toDo.Category = this;
}
// Вызывается в ходе операции удаления
private void detach_ToDo(ToDoItem toDo)
{
NotifyPropertyChanging("ToDoItem");
toDo.Category = null;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Используется для выдачи уведомлений о том, что свойство изменено
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Используется для выдачи уведомлений о том, что свойство может измениться
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
}
Подробные сведения о разработке приложений, использующих базы данных, можно найти в материале "Локальная база данных для Windows Phone" (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202860%28v=vs.105%29.aspx).
Для того, чтобы изучить пример разработки простой локальной базы данных для Windows Phone, без использовании шаблона проектирования MVVM, обратитесь к материалу "Как создать простое приложение для Windows Phone, работающее с базой данных" (http://msdn.microsoft.com/ru-RU/library/windowsphone/develop/hh202876%28v=vs.105%29.aspx).
Для того, чтобы найти дополнительные материалы по Linq to SQL, обратитесь к ресурсу "Linq to SQL" (http://msdn.microsoft.com/ru-ru/library/bb386976.aspx).
Приложения для Windows Phone 8 могут хранить реляционные данные в локальных базах данных, с помощью механизмов Linq to SQL обеспечивается объектное представление баз данных в приложениях, что позволяет организовать удобную работу с ними.
Если приложение, созданием которого вы занимаетесь, подразумевает работу с данными, имеющими сложную структуру, рассмотрите возможность использования локальной базы данных для работы с ними. Кроме того, если приложение рассчитано на работу с локальной базой данных, это упростит, при необходимости, возможность перехода на использование облачной базы данных, работающей на платформе Windows Azure с использованием локальной базы данных в качестве локального кэша. Подготовьте отчёт по данному этапу работы, приведите в нём анализ данных, которые использует приложение и сделайте выводы о том, поможет ли использование базы данных улучшить структуру вашего приложения.
Для того, чтобы спроектировать базу данных для приложения вам могут понадобиться дополнительные знания, для того, чтобы их получить, вы можете обратиться к учебным курсам, посвященным базам данных на портале Intuit.ru (например, к курсу "Введение в реляционные базы данных", http://www.intuit.ru/studies/courses/74/74/info.
К данной лабораторной работе подготовлено видеоприложение.
Цель работы: освоить технологию работы с локальными базами данных на Windows Phone
В этой лабораторной работе мы рассмотрим приложение, описанное в материале "Как создать MVVM-приложение для Windows Phone, работающее с локальной базой данных" (http://msdn.microsoft.com/library/windowsphone/develop/hh286405%28v=vs.105%29.aspx). Загрузить проект приложения можно здесь: "Пример работы с локальной базой данных" (http://code.msdn.microsoft.com/Local-Database-Sample-57b1614c).
В проекте приложения, рис. 35.1., присутствует несколько страниц, это главная страница – MainPage.xaml, и страница, которая используется для добавления новых объектов (задач, в контексте приложения) в базу данных. Это – страница NewTaskPage.xaml.
(рис 35.1) Проект приложения, иллюстрирующего работу с базами данных
В папке Model расположен файл ToDoDataModel.cs. Это, в терминологии MVVM, модель данных, с которой работает приложение. Модель представления описана в файле ToDoViewModel.cs, который расположен в папке ViewModel.
Рассмотрим особенности реализации данного приложения. Стоит отметить, что ключевым в работе с базами данных для Windows Phone, является класс System.Data.Linq.DataContext (http://msdn.microsoft.com/en-us/library/windowsphone/develop/system.data.linq.datacontext%28v=vs.105%29.aspx). Он служит посредником между базой данных и Windows Phone-приложением. Не стоит путать его со свойством DataContext объекта FrameworkElement (http://msdn.microsoft.com/en-us/library/windowsphone/develop/system.windows.frameworkelement.datacontext%28v=vs.105%29.aspx), последний используется в системе привязки данных, здесь же мы говорим о взаимодействии с базой данных.
SDF-файлы локальных баз данных, используемых приложениями для Windows Phone, располагаются в локальных папках приложений, а значит, база данных приложения доступна только ему. На Windows Phone не предусмотрено чего-то вроде постоянно исполняющегося сервера баз данных – работа с базой осуществляется только в процессе приложения. Для работы с базами данных используется Linq to SQL, Transact-SQL не поддерживается.
Контекст данных, который используется для вывода информации на главной странице приложения, устанавливается на статическую переменную ViewModel типа ToDoViewModel, объявленную в классе App. В этом же классе, в конструкторе (Листинг 35.1.), готовится строка соединения c базой данных(connection string). Она указывает на локальный файл базы данных. Схема "isostore:" указывает на локальную папку приложения, где и будет храниться файл базы данных. Структура строки соединения представляет собой пару вида "ключ=значение".
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
// Директивы
using LocalDatabaseSample.Model;
using LocalDatabaseSample.ViewModel;
namespace sdkLocalDatabaseCS
{
public partial class App : Application
{
// Статическая переменная ViewMode для использования в приложении.
private static ToDoViewModel viewModel;
public static ToDoViewModel ViewModel
{
get { return viewModel; }
}
/// <summary>
/// Конструктор объекта Application.
/// </summary>
public App()
{
// Укажем строку соединения с локальной базой данных.
string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
// Если база данных не существует – создадим её.
using (ToDoDataContext db = new ToDoDataContext(DBConnectionString))
{
if (db.DatabaseExists() == false)
{
// Создание локальной базы данных.
db.CreateDatabase();
// Предварительное заполнение категорий базы данных.
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Home" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Work" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Hobbies" });
// Сохранение категорий.
db.SubmitChanges();
}
}
// Создание объекта viewModel с использованием базы данных.
viewModel = new ToDoViewModel(DBConnectionString);
// Выполнение запроса к базе данных и загрузка коллекций, используемых при привязке данных.
viewModel.LoadCollectionsFromDatabase();
}
При первом запуске приложения файла базы данных не существует, поэтому, если соответствующая проверка на это указывает, выполняется создание базы данных по указанной строке соединения. Здесь используется объект типа ToDoDataContext, то есть – класс, являющийся моделью данных приложения. Этот класс, как мы увидим ниже, он унаследован от класса System.Data.Linq.DataContext, то есть, он может пользоваться его возможностями, среди которых – и создание базы данных. В данном случае метод CreateDatabase() – это метода родительского класса. После создания базы данных выполняется её первоначальное заполнение. Это реализовано с помощью метода InsertOnSubmit() объекта InsertOnSubmit. Данный объект – System.Data.Linq.Table – то есть – таблица базы данных, определен в классе ToDoDataContext.
После первоначального заполнения база данных сохраняется.
Если база данных уже существовала (или если она была уже создана, и та и другая ситуации обрабатываются одинаково), выполняется создание новой модели представления viewModel типа ToDoViewModel с указанием строки соединения с базой. Эта закрытая статическая переменная, в итоге, используется, через открытую переменную ViewModel, в качестве контекста данных главной страницы приложения.
На завершающем этапе инициализации происходит вызов метода модели представления LoadCollectionsFromDataBase. Здесь выполняются запросы к базе для заполнения коллекций, определенных в модели представления и используемых при привязке данных.
Главная страница приложения MainPage.xaml и страница NewTaskPage.xaml, взаимодействуют с моделью представления, во-первых, с помощью системы привязки данных, во-вторых – с помощью вызовов некоторых методов модели представления из кода. В частности, страница NewTaskPage вызывает метод модели представления AddToDoItem, передавая ему данные о новом элементе, который нужно создать, страница MainPage вызывает метод DeleteToDoItem.
Рассмотрим код файла ToDoViewModel.cs, Листинг 35.2.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
// Директива для работы с моделью данных.
using LocalDatabaseSample.Model;
namespace LocalDatabaseSample.ViewModel
{
public class ToDoViewModel : INotifyPropertyChanged
{
// Контекст данных LINQ to SQL для доступа к локальной базе данных.
private ToDoDataContext toDoDB;
// Конструктор класса, создание объекта контекста данных.
public ToDoViewModel(string toDoDBConnectionString)
{
toDoDB = new ToDoDataContext(toDoDBConnectionString);
}
// Коллекция, отображающая все элементы.
private ObservableCollection<ToDoItem> _allToDoItems;
public ObservableCollection<ToDoItem> AllToDoItems
{
get { return _allToDoItems; }
set
{
_allToDoItems = value;
NotifyPropertyChanged("AllToDoItems");
}
}
// Коллекция, отображающая элементы категории home.
private ObservableCollection<ToDoItem> _homeToDoItems;
public ObservableCollection<ToDoItem> HomeToDoItems
{
get { return _homeToDoItems; }
set
{
_homeToDoItems = value;
NotifyPropertyChanged("HomeToDoItems");
}
}
// Коллекция, отображающая элементы категории work.
private ObservableCollection<ToDoItem> _workToDoItems;
public ObservableCollection<ToDoItem> WorkToDoItems
{
get { return _workToDoItems; }
set
{
_workToDoItems = value;
NotifyPropertyChanged("WorkToDoItems");
}
}
// Коллекция, отображающая элементы категории hobbies.
private ObservableCollection<ToDoItem> _hobbiesToDoItems;
public ObservableCollection<ToDoItem> HobbiesToDoItems
{
get { return _hobbiesToDoItems; }
set
{
_hobbiesToDoItems = value;
NotifyPropertyChanged("HobbiesToDoItems");
}
}
// Список всех категорий, который используется страницей, выполняющей добавление задач.
private List<ToDoCategory> _categoriesList;
public List<ToDoCategory> CategoriesList
{
get { return _categoriesList; }
set
{
_categoriesList = value;
NotifyPropertyChanged("CategoriesList");
}
}
// Запись изменений в базу данных.
public void SaveChangesToDB()
{
toDoDB.SubmitChanges();
}
// Запрос к базе данных и загрузка коллекций и списка, которые используются главной страницей
public void LoadCollectionsFromDatabase()
{
// Запрос на получение всех элементов из базы данных.
var toDoItemsInDB = from ToDoItem todo in toDoDB.Items
select todo;
// Заполнение списка, содержащего все элементы базы.
AllToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
// Запрос на получение всех категорий из базы данных.
var toDoCategoriesInDB = from ToDoCategory category in toDoDB.Categories
select category;
// Запрос к базе данных с загрузкой всех элементов в соответствующие им категории
foreach (ToDoCategory category in toDoCategoriesInDB)
{
switch (category.Name)
{
case "Home":
HomeToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
case "Work":
WorkToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
case "Hobbies":
HobbiesToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
break;
default:
break;
}
}
// Загрузка списка всех категорий.
CategoriesList = toDoDB.Categories.ToList();
}
// Добавление нового элемента в базу данных и в коллекцию.
public void AddToDoItem(ToDoItem newToDoItem)
{
// Добавление нового элемента в контекст данных.
toDoDB.Items.InsertOnSubmit(newToDoItem);
// Сохранение изменений в базе данных.
toDoDB.SubmitChanges();
// Добавление элемента в коллекцию, которая используется для отображения всех элементов
AllToDoItems.Add(newToDoItem);
// Добавление элемента в коллекцию, соответствующую его категории.
switch (newToDoItem.Category.Name)
{
case "Home":
HomeToDoItems.Add(newToDoItem);
break;
case "Work":
WorkToDoItems.Add(newToDoItem);
break;
case "Hobbies":
HobbiesToDoItems.Add(newToDoItem);
break;
default:
break;
}
}
// Удаление элемента из базы данных и из коллекции.
public void DeleteToDoItem(ToDoItem toDoForDelete)
{
// Удаление элемента из коллекции, которая используется для отображения всех элементов.
AllToDoItems.Remove(toDoForDelete);
// Удаление элемента из контекста данных.
toDoDB.Items.DeleteOnSubmit(toDoForDelete);
// Удаление элемента из соответствующей ему категории.
switch (toDoForDelete.Category.Name)
{
case "Home":
HomeToDoItems.Remove(toDoForDelete);
break;
case "Work":
WorkToDoItems.Remove(toDoForDelete);
break;
case "Hobbies":
HobbiesToDoItems.Remove(toDoForDelete);
break;
default:
break;
}
// Сохранение элементов в базе данных.
toDoDB.SubmitChanges();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Генерирует уведомления для системы связывания данных об изменении свойства
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
В конструкторе класса создаётся новый объект ToDoDataContext с использованием строки соединения, которая передаётся в этот конструктор при создании объекта ToDoViewModel в классе App.
Далее, выполняется загрузка данных из базы (LoadCollectionsFromDataBase()) и запись их в соответствующие коллекции. Коллекции используются при привязке данных для отображения списков элементов.
Здесь же определены методы, которые используются для добавления новых данных в базу данных (AddToDoItem), и удаления (DeleteToDoItem). При модификации состава данных в базе данных производится и соответствующая модификация коллекций, которые используются для отображения данных в интерфейсе.
В файле ToDoDataContext.cs имеется класс ToDoDataContext, он является наследником DataContext, при создании экземпляра класса передаёт родительскому классу строку соединения и задаёт два открытых поля типа Table<>, используя классы, представляющие собой объектные представления таблиц (классы сущностей) базы данных, описанные в этом же файле. А именно, речь идёт о классах ToDoItem и ToDoCategory, объявленных с атрибутом [Table]. Фактически, эти классы, во-первых, содержат описания структуры таблиц (полей таблицы, или столбцов, объявленных с атрибутом [Column]) и логику, необходимую для реализации интерфейсов INotifyPropertyChanged и INotifyPropertyChanging.
В Листинге 35.3 приведен код файла ToDoDataContext.cs
using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace LocalDatabaseSample.Model
{
public class ToDoDataContext : DataContext
{
// Передача строки соединения базовому классу.
public ToDoDataContext(string connectionString)
: base(connectionString)
{ }
// Указание таблицы для элементов.
public Table<ToDoItem> Items;
// Указание таблицы для категорий.
public Table<ToDoCategory> Categories;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Определяет ID: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity",
CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get { return _toDoItemId; }
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
// Определяет имя элемента: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private string _itemName;
[Column]
public string ItemName
{
get { return _itemName; }
set
{
if (_itemName != value)
{
NotifyPropertyChanging("ItemName");
_itemName = value;
NotifyPropertyChanged("ItemName");
}
}
}
// Определяет значение завершения: закрытое поле, общедоступное поле,
и столбец в таблице базы данных.
private bool _isComplete;
[Column]
public bool IsComplete
{
get { return _isComplete; }
set
{
if (_isComplete != value)
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
}
// Столбец для определения производительности обновления.
[Column(IsVersion = true)]
private Binary _version;
// Колонка Internal для связанного значения ID ToDoCategory
[Column]
internal int _categoryId;
// Ссылка на сущность, для идентификации таблицы ToDoCategory "хранилища"
private EntityRef<ToDoCategory> _category;
// Ассоциация, для описания взаимосвязи между данным ключом и таблицей "хранилища"
[Association(Storage = "_category",
ThisKey = "_categoryId", OtherKey = "Id", IsForeignKey = true)]
public ToDoCategory Category
{
get { return _category.Entity; }
set
{
NotifyPropertyChanging("Category");
_category.Entity = value;
if (value != null)
{
_categoryId = value.Id;
}
NotifyPropertyChanging("Category");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Используется для выдачи уведомлений о том, что свойство изменено
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Используется для выдачи уведомлений о том, что свойство может измениться
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
[Table]
public class ToDoCategory : INotifyPropertyChanged, INotifyPropertyChanging
{
// Определяет ID: закрытое поле, общедоступное поле, и столбец в таблице базы данных.
private int _id;
[Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
public int Id
{
get { return _id; }
set
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanged("Id");
}
}
// Определяет имя категории: закрытое поле, общедоступное поле, и столбец в таблице базы данных
private string _name;
[Column]
public string Name
{
get { return _name; }
set
{
NotifyPropertyChanging("Name");
_name = value;
NotifyPropertyChanged("Name");
}
}
// Столбец для определения производительности обновления.
[Column(IsVersion = true)]
private Binary _version;
// Определяет набор сущностей для той стороны взаимодействия,
которая имеет отношение к коллекции
private EntitySet<ToDoItem> _todos;
[Association(Storage = "_todos", OtherKey = "_categoryId",
ThisKey = "Id")]
public EntitySet<ToDoItem> ToDos
{
get { return this._todos; }
set { this._todos.Assign(value); }
}
// Назначение обработчиков для операций добавления и удаления, соответственно
public ToDoCategory()
{
_todos = new EntitySet<ToDoItem>(
new Action<ToDoItem>(this.attach_ToDo),
new Action<ToDoItem>(this.detach_ToDo)
);
}
// Вызывается в ходе операции добавления
private void attach_ToDo(ToDoItem toDo)
{
NotifyPropertyChanging("ToDoItem");
toDo.Category = this;
}
// Вызывается в ходе операции удаления
private void detach_ToDo(ToDoItem toDo)
{
NotifyPropertyChanging("ToDoItem");
toDo.Category = null;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Используется для выдачи уведомлений о том, что свойство изменено
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Используется для выдачи уведомлений о том, что свойство может измениться
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
}
Подробные сведения о разработке приложений, использующих базы данных, можно найти в материале "Локальная база данных для Windows Phone" (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202860%28v=vs.105%29.aspx).
Для того, чтобы изучить пример разработки простой локальной базы данных для Windows Phone, без использовании шаблона проектирования MVVM, обратитесь к материалу "Как создать простое приложение для Windows Phone, работающее с базой данных" (http://msdn.microsoft.com/ru-RU/library/windowsphone/develop/hh202876%28v=vs.105%29.aspx).
Для того, чтобы найти дополнительные материалы по Linq to SQL, обратитесь к ресурсу "Linq to SQL" (http://msdn.microsoft.com/ru-ru/library/bb386976.aspx).
Приложения для Windows Phone 8 могут хранить реляционные данные в локальных базах данных, с помощью механизмов Linq to SQL обеспечивается объектное представление баз данных в приложениях, что позволяет организовать удобную работу с ними.
Если приложение, созданием которого вы занимаетесь, подразумевает работу с данными, имеющими сложную структуру, рассмотрите возможность использования локальной базы данных для работы с ними. Кроме того, если приложение рассчитано на работу с локальной базой данных, это упростит, при необходимости, возможность перехода на использование облачной базы данных, работающей на платформе Windows Azure с использованием локальной базы данных в качестве локального кэша. Подготовьте отчёт по данному этапу работы, приведите в нём анализ данных, которые использует приложение и сделайте выводы о том, поможет ли использование базы данных улучшить структуру вашего приложения.
Для того, чтобы спроектировать базу данных для приложения вам могут понадобиться дополнительные знания, для того, чтобы их получить, вы можете обратиться к учебным курсам, посвященным базам данных на портале Intuit.ru (например, к курсу "Введение в реляционные базы данных", http://www.intuit.ru/studies/courses/74/74/info.
К данной лабораторной работе подготовлено видеоприложение.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.