Презентацию к данной лекции Вы можете скачать здесь.
Практически в любом приложении требуется хранить данные, которые приложение использует при работе. Приложение Silverlight должно сохранять настройки и информацию, с которой работает пользователь, игре XNA требуется хранить информацию о достижениях игрока, список рекордов и игровые настройки. Программы Windows Phone могут использовать
В изолированном хранилище можно сохранять большие объемы данных вплоть до максимально доступного объема памяти для хранения данных в телефоне. В устройствах Windows Phone имеется минимум 8 Гб встроенного хранилища, которое совместно используется программами для работы с мультимедиа файлами (музыка, изображения и видео) и всеми приложениями в устройстве.
При удалении приложения из телефона все содержимое изолированного хранилища, связанное с этим приложением, также удаляется. Когда приложение обновляется до новой версии через Marketplace, содержимое изолированного хранилища сохраняется, а если данные в хранилище должны быть обновлены до новой версии, приложение их обновляет.
В качестве примера рассмотрим создание простого приложения Silverlight, которое может сохранять текстовые заметки. Эта версия программы использует один файл, но ее можно легко расширить. Внешний вид приложения представлен на рис. 14.1.
(рис 14.1) Экран приложения для хранения текстовых заметок
Пользователь может делать
private void saveButton_Click(object sender, RoutedEventArgs e)
{
saveText("notes.txt", jotTextBox.Text);
}
Это код для кнопки сохранить. Здесь текст из текстового поля передается методу saveText вместе с именем файла, в котором этот текст нужно сохранить.
private void saveText(string filename, string text)
{
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream rawStream = isf.CreateFile(filename))
{
StreamWriter writer = new StreamWriter(rawStream);
writer.Write(text);
writer.Close();
}
}
}
Метод saveText создает поток, связанный с указанным файлом в изолированном хранилище, и записывает в него текст. Потоки работают точно так же, как и в других программах на C#. В нашем случае метод создает объект StreamWriter, которому передается текст.
Кнопка загрузить использует метод loadText для выполнения обратных действий.
private void loadButton_Click(object sender, RoutedEventArgs e)
{
string text;
if (loadText("notes.txt", out text))
{
notesTextBox.Text = text;
}
else
{
notesTextBox.Text = "Напишите здесь что-нибудь....";
}
}
Метод loadText пытается открыть файл, имя которого передается в качестве параметра, после чего пытается считать строку из того файла. Если одно из этих действий заканчивается неудачей, метод возвращает значение false, и в элемент notesTextBox записывается начальное сообщение.
Если чтение содержимого файла выполнено успешно, метод loadText возвращает значение true и записывает содержимое файла в выходной параметр, после чего этот текст заносится в элемент notesTextBox.
private bool loadText(string filename, out string result)
{
result = "";
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isf.FileExists(filename))
{
try
{
using (IsolatedStorageFileStream rawStream =
isf.OpenFile(filename, System.IO.FileMode.Open))
{
StreamReader reader = new StreamReader(rawStream);
result = reader.ReadToEnd();
reader.Close();
}
}
catch
{
return false;
}
}
else
{
return false;
}
}
return true;
}
Метод loadText возвращает значение false, если входной файл не найден, или при чтении файла возникает исключение. Метод ReadToEnd позволяет делать в программе многострочные текстовые
Часто программам нужно сохранить некоторые настройки, которые являются простыми значениями. В этом случае использовать файлы и потоки очень неудобно. Чтобы облегчить сохранение настроек, программа Windows Phone может использовать хранилище для настроек. Оно работает как
Словари являются очень полезным механизмом коллекций, который является частью структуры .NET. При создании
Словари удобно использовать для хранения пар "имя—значение". Они избавляют от необходимости написания кода для поиска необходимых значений в коллекции. Также в программе можно использовать несколько словарей.
В Windows Phone можно использовать класс IsolatedStorageSettings, который является словарем для хранения настроек в системе.
Сохранение текста в
private void saveText(string filename, string text)
{
IsolatedStorageSettings isolatedStore =
IsolatedStorageSettings.ApplicationSettings;
isolatedStore.Remove(filename);
isolatedStore.Add(filename, text);
isolatedStore.Save();
}
Эта версия метода saveText использует имя файла в качестве ключа. Он удаляет Remove вызывается для удаления элемента из Remove возвращает значение false, но в нашем примере это не имеет значения. После выполнения изменений в хранилище, необходимо вызвать метод Save для сохранения этих изменений.
Загрузка текста из изолированного хранилища настроек. Метод loadText будет считывать значения из
private bool loadText(string filename, out string result)
{
IsolatedStorageSettings isolatedStore =
IsolatedStorageSettings.ApplicationSettings;
result = "";
try
{
result = (string)isolatedStore[filename];
}
catch
{
return false;
}
return true;
}
Метод loadText выбирает запрошенный элемент из хранилища настроек. Его использование осложняется тем, что в отличие от класса Dictionary класс IsolatedStorageSettings не содержит метод ContainsKey, который используется для определения, есть ли в false, чтобы указать, что такого элемента нет.
Таким образом, приложениям доступны два способа сохранения данных в устройстве Windows Phone. Большие объемы данных можно сохранять в файловом хранилище, в котором можно создать целую файловую структуру. В качестве альтернативы можно сохранять отдельные элементы данных по имени в
Пакет инструментов для разработки приложений Windows Phone включает программу Isolated Storage Explorer, которая позволяет просматривать файлы в изолированном хранилище. Эта программа имеет интерфейс командной строки и по умолчанию устанавливается в папку C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool.
Программе Isolated Storage Explorer необходимо указать GUID приложения, который определяет необходимую область изолированного хранилища. Значение GUID можно найти в файле приложения WMAppManifest.xml:
<App xmlns="" ProductID="{3363ac33-4f45-4b21-b932-fa2084b6deb0}" Title="JotPad"
RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"
Author="JotPad author" Description="Sample description" Publisher="JotPad">
Для считывания содержимого изолированного хранилища используется команда ISETool с соответствующими параметрами:
ISETool ts xd 3363ac33-4f45-4b21-b932-fa2084b6deb0 c:\isoStore
Эта команда делает снимок изолированного хранилища эмулятора для приложения со значением GUID равным {3363ac33-4f45-4b21-b932-fa2084b6deb0} и помещает его в папку c:\isoStore на компьютере.
Описание всех параметров программы Isolated Storage Explorer можно получить, набрав в командной строке команду ISETool без параметров.
База данных — это набор данных, которые организованы и управляются компьютерной программой, которая называется
Программа может отправлять запросы к базе данных, в ответ на которые база данных возвращает результаты. При этом, сама база данных является частью решения, которое является хранилищем данных.
Рассмотрим пример создания базы данных для Интернет-магазина. В базе данных будет храниться информация о клиентах, которые размещают заказы товаров. Для хранения этой информации необходимо три таблицы.
Таблица Customer (таблица 5.1) содержит информацию о клиентах и включает четыре столбца для хранения, соответственно, идентификатора, имени, адреса и названия банка клиента. Каждая строка таблицы называется
| CustomerID | Name | Address | BankDetails |
|---|---|---|---|
| 123456 | Rob | 18 Pussycat Mews | Nut East Bank |
| 654322 | Jim | 10 Motor Drive | Big Fall Bank |
| 111111 | Ethel | 4 Funny Address | Strange Bank |
Таблица Product (таблица 5.2) содержит столбцы для хранения информации товарах: идентификатор товара, наименование, поставщик и цена.
| ProductID | ProductName | Supplier | Price |
|---|---|---|---|
| 1001 | Windows Phone 7 | Microsoft | 200 |
| 1002 | Cheese grater | Cheese Industries | 2 |
| 1003 | Boat hook | John's Dockyard | 20 |
Таблица Order (таблица 5.3) содержит информацию о заказах товаров и ссылается на
| OrderID | CustomerID | ProductID | Quantity | OrderDate | Status |
|---|---|---|---|---|---|
| 1 | 123456 | 1001 | 1 | 21/10/2010 | Shipped |
| 2 | 111111 | 1002 | 5 | 10/10/2010 | Shipped |
| 3 | 654322 | 1003 | 4 | 1/09/2010 | On order |
Идентификаторы товара и клиента позволяют избежать дублирования одних и тех же данных: по значению идентификатора можно получить информацию из соответствующей таблицы базы данных вместо того, чтобы хранить всю информацию о клиентах и заказанных товарах в самой таблице заказов.
Обратите внимание, что имена столбцов указаны без пробелов — это позволит избежать некоторых сложностей при создании запросов к базе данных.
Работа с базой данных заключается, в основном, в создании и отправке запросов к базе данных и получении и обработке результатов. Запросы к базам данных обычно составляются на языке
Объектно-ориентированные программы не работают напрямую с таблицами, записями и запросами. Программы используют объекты, которые содержат свойства и методы. Для работы с базами данных в программе необходим способ преобразования информации из табличной формы в объектную.
Для работы с информацией в базе данных чаще всего используется язык
Существенно упростить эту задачу позволяет интегрированный язык запросов LINQ. Язык LINQ фактически встроен в язык C# и добавляет особые синтаксические конструкции для составления запросов, которые используют программные объекты. Windows Phone позволяет использовать в программах базы данных, взаимодействие с которыми выполняется с помощью LINQ.
Чтобы использовать в программе код LINQ, нужно добавить в проект ссылку на библиотеки System.Data.Linq. Поскольку не во всех программах необходимо использовать базы данных, эта ссылка обычно не добавляется при создании проекта. После этого для облегчения работы с классами LINQ рекомендуется добавить директивы using:
using System.Linq; using System.Data.Linq; using System.Data.Linq.Mapping; using System.ComponentModel; using System.Collections.ObjectModel;
Теперь необходимо создать табличную структуру данных, которыми приложение будет управлять. В нашей базе данных будет три таблицы:
Customer — для хранения информации о клиентах;Order — для хранения информации о заказах;Product — для хранения информации о товарах.Создание класса для хранения информации из таблицы. Начнем с таблицы Customer. Необходимо создать класс, описывающий данные, которые должны храниться в таблице Customer. Этот класс можно использовать для создания в базе данных таблицы, содержащей информацию обо всех клиентах. Класс может быть описан так:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string BankDetails { get; set; }
}
Класс Customer содержит три строковых свойства и одно целочисленное. Все свойства доступны для чтения и Customer класса для LINQ выглядит похоже:
[Table]
public class Customer : INotifyPropertyChanged, INotifyPropertyChanging
{
// здесь описывается структура класса
}
Строка [Table] является атрибутом. Атрибуты используются для добавления к классам специальной информации, которую могут использовать программы, считывающие
Также для работы с LINQ класс должен реализовать интерфейсы INotifyPropertyChanged и INotifyPropertyChanging, т.е. должен содержать все методы, которые определены в интерфейсе. Эти интерфейсы содержат методы, которые будут использоваться, чтобы сообщить LINQ, когда данные в классе будут изменены. Этот механизм похож на привязку данных к визуальным элементам, только теперь изменение данных будет вызывать изменения в базе данных.
Каждый из этих интерфейсов содержит по одному делегату событий:
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging;
Инфраструктура LINQ связана с событием PropertyChanged, которое происходит после изменения значения свойства, а также с событием PropertyChanging, происходящим перед изменением значения.
Необходимо сделать так, чтобы класс Customer генерировал эти события при изменении значений свойств. Свойство Name может содержать следующий код:
private string nameValue;
public string Name
{
get
{
return nameValue;
}
set
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs("Name"));
}
nameValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
В этом коде секция get свойства просто возвращает значение свойства, которое содержит имя. Секция set проверяет, назначены ли событиям обработчики, и вызывает один из них перед изменением значения свойства, а другой — после. Делегаты принимают два параметра. Первый параметр является ссылкой на текущего клиента (имя которого изменяется). Второй параметр содержит название свойства, которое изменяется. LINQ использует эти параметры, чтобы выяснить, какие значения изменились, и выполняет соответствующие изменения в базе данных.
События происходят до и после изменения, чтобы LINQ мог более эффективно управлять изменениями данных. При обновлении значений свойств в объектах все изменения автоматически синхронизируются с базой данных.
Если объекты содержат много свойств, имеет смысл упростить управление событиями с помощью добавления методов, которые будут осуществлять управление:
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
Тогда код свойств существенно упростится:
private string nameValue;
public string Name
{
get
{
return nameValue;
}
set
{
NotifyPropertyChanging("Name");
nameValue = value;
NotifyPropertyChanged("Name");
}
}
Чтобы LINQ мог использовать это свойство, к нему необходимо добавить атрибут [Column]:
[Column]
public string Name
{
// здесь описывается поведение свойства Name
}
Аналогичным образом создается код свойств Address и BankDetails для хранения информации об адресе и банке клиента. Эти свойства будут отображаться на соответствующие столбцы таблицы в базе данных.
Создание первичного ключа. Поле CustomerID немного отличается от остальных. В нашем примере это целочисленное значение, которое уникально для каждого клиента. Если клиент сменит имя, информацию о нем по-прежнему можно будет найти по значению идентификатора. В описанной выше таблицы базы данных поле CustomerID будет являться первичным ключом таблицы Customer.
При создании столбца идентификатора необходимо сообщить LINQ, что этот столбец должен быть первичным ключом для таблицы. Также можно указать LINQ, что значения этого столбца уникальны для всех записей, и их нужно генерировать автоматически. Для этого к атрибуту Column для свойства CustomerID дополнительную информацию:
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int CustomerID { get; set; }
Теперь при добавлении в базу данных Customer, который будет использоваться в базе данных для связи таблицы Customer с таблицей Order. Каждая строка таблицы Order будет содержать значение идентификатора клиента, однозначино идентифицирующее клиента, который создал этот заказ.
Когда LINQ считает
Создание контекста данных LINQ. Теперь можно создать таблицу в базе данных. Для этого необходимо создать класс, наследуемый от класса DataContext, который будет управлять подключением к базе данных. Он будет содержать все таблицы для приложения.
public class SalesDB : DataContext
{
public Table<Customer> Customers;
public SalesDB(string connection)
: base(connection)
{
}
}
Класс DataContext в LINQ является базовым классом для проектирования баз данных. В его состав входят методы, которые можно использовать для управления содержимым базы данных. Конструктор наследуемого класса SalesDB просто вызывает конструктор родительского класса. Строка подключения определяет параметры для подключения к базе данных, которую нужно использовать. База данных может располагаться как на рабочем устройстве, так и на удаленном сервере в сети. В нашем случае база данных находится в файле, который хранится в изолированном хранилище в телефоне, и строка подключения должна содержать путь к этому файлу.
Создание тестовых данных. Для создания тестовых данных используем тот же подход, который мы использовали для создания тестового списка, только теперь будет создаваться база данных и таблицы:
public static void MakeTestDB(string connection)
{
string[] firstNames = new string[] { "Rob", "Jim", "Joe", "Nigel", "Sally",
"Tim" };
string[] lastsNames = new string[] { "Smith", "Jones", "Bloggs", "Miles",
"Wilkinson", "Brown" };
SalesDB newDB = new SalesDB(connection);
if (newDB.DatabaseExists())
{
newDB.DeleteDatabase();
}
newDB.CreateDatabase();
foreach (string lastName in lastsNames)
{
foreach (string firstname in firstNames)
{
string name = firstname + " " + lastName;
string address = name + "'s address";
string bank = name + "'s bank";
Customer newCustomer = new Customer();
newCustomer.Name = name;
newCustomer.Address = address;
newCustomer.BankDetails = bank;
newDB.CustomerTable.InsertOnSubmit(newCustomer);
}
}
newDB.SubmitChanges();
}
Сначала этот метод пытается подключиться к базе данных. Переменная newDB представляет подключение к базе данных в соответствии с заданной строкой подключения. Метод может использовать это подключение, чтобы отправлять запросы для управления базой данных. Если указанная база данных существует, она удаляется.
После вызова метода CreateDatabase создается новая база данных, которая содержит набор пустых таблиц. После этого создается набор тестовых значений с информацией о клиентах, которые заносятся в таблицу базы данных.
Метод InsertOnSubmit добавляет новую CustomerTable данные другого типа, компилятор выдал бы сообщение об ошибке.
Завершается метод вызовом метода SubmitChanges, который, собственно, и записывает все сделанные изменения в файл базу данных. До этих пор все изменения хранятся в памяти программы. Такой подход позволяет ускорить выполнение операций с базой данных, особенно если выполняется много операций модификации данных. Если не будет вызван метод SubmitChanges, изменения в базе данных не сохранятся.
Теперь созданную базу данных можно использовать в приложении. Контекст базы данных нужно разместить в файле программы App.xaml.cs. Там же размещается ссылка на информацию об активном клиенте:
public SalesDB ActiveDB; public Customer ActiveCustomer;
При запуске программы в переменной ActiveDB нужно создать ссылку на используемую базу данных.
ActiveDB = new SalesDB("Data Source=isostore:/Sample.sdf");
Путь к файлу имеет особый формат и идентифицирует файл в изолированном хранилище. Можно использовать в программе несколько баз данных, каждая из которых будет находиться в разных файлах. Формат файла базы данных такой же, как и формат файла стандартной базы данных
Привязка элемента ListBox к результату запроса LINQ. Теперь можно использовать эту базу данных в нашем приложении. Нужно получить данные из базы данных и вывести их на экран.
Информацию из базы данных можно получить с помощью запросов LINQ. Запрос для получения информации о клиентах может выглядеть так:
var customers = from Customer customer
in thisApp.ActiveDB.CustomerTable
select customer;
Эта команда C# создает переменную customers, которая имеет тип var. Этот тип означает, что тип переменной определяется на основе значения, которое присваивается этой переменной. При этом сохраняется строгая типизация C#, и если попытаться использовать переменную customers недопустимым способом, программа не скомпилируется.
Остальная часть команды сообщает LINQ, что нужно получить все элементы customer из свойства CustomerTable контекста базы данных ActiveDB. Эти данные возвращаются в виде списка клиентов, который может быть наблюдаемой коллекцией. Чтобы вывести на экран полученный список клиентов, можно задать этот список в качестве источника данных элементу ListBox:
customerList.ItemsSource = customers;
Теперь у нас есть полностью рабочая база данных клиентов. Остается только добавить в программу код для применения изменений в базе данных при выходе из программы. Лучший способ это сделать — добавить необходимый код в метод OnNavigatedFrom в файле MainPage.xaml.cs:
protected override void OnNavigatedFrom(
System.Windows.Navigation.NavigationEventArgs e)
{
App thisApp = Application.Current as App;
thisApp.ActiveDB.SubmitChanges();
}
Когда пользователь перейдет на другую страницу, метод найдет активную базу данных и применит все сделанные изменения.
Добавление фильтров. Если немного изменить запрос LINQ, можно выбрать
var customers = from Customer customer
in thisApp.ActiveDB.CustomerTable
where customer.Name.StartsWith("S")
select customer;
Этот запрос выбирает только тех клиентов, имя которых начинается с буквы "S". Таким образом, можно выбрать из базы данных нужные элементы. Так, в приложение можно добавить поле для поиска, если нужно найти клиентов с определенным именем.
Чтобы добавить в программу возможность управления заказами товаров, нужно создать другие таблицы и связать их по ключевым полям. Класс для таблицы Products создается аналогично классу для таблицы Customers.
Класс для таблицы Orders будет иметь более сложную структуру, поскольку эта таблица содержит ссылки на
public class Order
{
public DateTime OrderDate;
public int Quantity;
public Customer OrderCustomer;
public Product OrderProduct;
}
Свойства OrderCustomer и OrderProduct связывают заказ с клиентом и заказанным им товаром. Соответствующие им поля таблицы базы данных являются внешними ключами.
Однако, в нашей базе данных отсутствуют ссылки как таковые. Вместо явных ссылок каждая Orders содержит значения идентификаторов клиента и товара, по которым можно установить соответствие заказа с клиентом и товаром. При этом, для таблиц базы данных можно задать отношения, которые связывают соответствующие
В LINQ отношения между двумя таблицами называют ассоциацией. Она задается в классах базы данных с помощью класса EntityRef.
Связывание заказа с клиентом. В нашей программе в классе Order должна быть ссылка на класс Customer. Она создается в виде отношения между двумя таблицами. EntityRef — это специальный класс LINQ, который можно использовать для связи двух таблиц.
[Table]
public class Order : INotifyPropertyChanged, INotifyPropertyChanging
{
...
private EntityRef<Customer> orderCustomer;
[Association(IsForeignKey = true, Storage = "orderCustomer")]
public Customer OrderCustomer
{
get
{
return orderCustomer.Entity;
}
set
{
NotifyPropertyChanging("OrderCustomer");
orderCustomer.Entity = value;
NotifyPropertyChanged("OrderCustomer");
}
}
}
Класс EntityRef является своего рода ссылкой на таблицу. Теперь можно написать такой код:
Customer newCustomer = new Customer(); Order newOrder = new Order(); newOrder.OrderCustomer = newCustomer;
Этот код осуществляет связь заказа с клиентом, и
В атрибуте [Association] указывается, что Storage сообщает LINQ, какое закрытое свойство базы данных будет хранить информацию о свойстве.
Связывание клиента со всеми его заказами. Теперь программа может для заданного заказа получить информацию о клиенте, который создал этот заказ, поскольку CustomerID, которое является идентификатором клиента. Однако, была бы очень полезна возможность найти для заданного клиента все его заказы.
В базе данных таблицы Customer и Order находятся в отношении "один ко многим". В классе C# Customer для хранения заказов клиента можно использовать одну из коллекций, например, массив или список. Однако, в LINQ для представления таких отношений нужно использовать класс EntitySet. Этот класс похож на EntityRef, но он позволяет управлять целым набором элементов. Можно связать клиента с его заказами, добавив в класс Customer свойство типа EntitySet:
[Table]
public class Customer : INotifyPropertyChanged, INotifyPropertyChanging
{
[Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int CustomerID { get; set; }
private EntitySet<Order> orders = new EntitySet<Order>();
[Association(Storage = "orders", ThisKey = "CustomerID",
OtherKey = "OrderCustomerID")]
public EntitySet<Order> Orders
{
get
{
return orders;
}
set
{
orders = value;
}
}
}
Это свойство похоже на описанное ранее свойство класса EntityRef, за исключением того, что в его атрибуте Association указана дополнительная информация для описания отношения.
Параметр ThisKey задает имя свойства, которое будет использовать класс Order для поиска клиента, с которым связана CustomerID, которое является первичным ключом для класса Customer.
Параметр OtherKey задает имя свойства класса Order, которое связано ассоциацией с другой стороны, то есть для заказа можно будет найти клиента, с которым связан этот заказ.
При изменении заказа не требуется генерировать события NotifyPropertyChanged, поскольку тип EntitySet является контейнером для заказов, а не непосредственно заказом. При добавлении заказов происходит добавление элементов в EntitySet, а не замена контейнера.
Чтобы ассоциацию работала должным образом, необходимо сделать некоторые изменения в ссылке в классе Order:
[Table]
public class Order : INotifyPropertyChanged, INotifyPropertyChanging
{
...
[Column]
public int OrderCustomerID;
private EntityRef<Customer> orderCustomer = new EntityRef<Customer>();
[Association(IsForeignKey = true, Storage = "orderCustomer",
ThisKey = "OrderCustomerID")]
public Customer OrderCustomer
{
get
{
return orderCustomer.Entity;
}
set
{
NotifyPropertyChanging("OrderCustomer");
orderCustomer.Entity = value;
NotifyPropertyChanged("OrderCustomer");
if (value != null)
OrderCustomerID = value.CustomerID;
}
}
}
OrderCustomerID для хранения его значения.
В этой версии кода значение CustomerID копируется в столбец OrderCustomerID таблицы, когда клиенту назначается заказ. Теперь можно написать такой код:
Customer c = new Customer(); Order o = new Order(); o.OrderCustomer = c;
Код секции set свойства OrderCustomer копирует значение CustomerID в свойство OrderCustomerID для
Таким образом, можно легко добавить заказы клиента:
Customer c = new Customer(); Order o = new Order(); o.OrderCustomer = c; c.Orders.Add(o);
Класс EntitySet содержит метод Add, который позволяет добавлять заказы в базу данных. Для работы с содержимым набора объектов можно использовать одну из конструкций C# для перебора элементов коллекции.
Проектирование структуры баз данных для возможности заказа нескольких товаров. Теперь в структуре базы данных есть связь между клиентами и заказами. У клиента может быть несколько заказов, и каждый заказ связан с одним клиентом, который его создал.
Теперь необходимо связать с заказом товары, которые нужны клиенту. Однако, в нашей структуре базы данных каждый заказ может содержать только один тип товара, что является весьма существенным ограничением и недостатком.
Чтобы решить проблему, необходимо добавить новую таблицу OrderItem, в которой будет храниться информация обо всех товарах каждого заказа. Каждый элемент OrderItem будет связан с определенным заказом. Соответственно, каждый заказ будет содержать набор этих элементов. Таблица будет иметь структуру, представленную в таблице 5.4.
| OrderItemID | OrderID | ProductID | Quantity |
|---|---|---|---|
| 1 | 56 | 1001 | 1 |
| 2 | 56 | 1002 | 5 |
| 3 | 12343 | 1003 | 4 |
Соответственно, обновленную структуру базы данных можно представить в виде диаграммы классов, представленной на рис. 14.2. Каждый класс отображается на соответствующую таблицу в базе данных.
(рис 14.2) Диаграмма классов, соответствующих таблицам базы данных приложения
Таблицы связаны отношениями "один ко многим" и используют первичные ключи. В классах программы это отношения реализуются в виде ассоциаций по аналогии с рассмотренным в текущем разделе примером.
Для получения необходимой информации из базы данных можно использовать мощные механизмы запросов LINQ. Например, можно найти все заказы, которые размещенные в указанную дату:
DateTime searchDate = new DateTime(2011, 8, 19);
var orders = from Order order in activeDB.OrderTable
where order.OrderDate == searchDate
select order;
Этот запрос возвращает из таблицы заказов активной базы данных все заказы, созданные 19 августа 2011 г., и заносит их в переменную orders, тип которой трактуется как "запрос LINQ". При этом, эта строка программы только создает запрос, но еще не выполняет никаких операций для получения данных. Информация будет считана из базы данных при попытке обращения к результатам этого запроса:
int totalSales = 0;
foreach (Order order in orders)
{
foreach (OrderItem item in order.OrderItems)
{
totalSales += item.OrderItemProduct.Price * item.Quantity;
}
}
Этот код получает информацию о заказах, которые будут получены запросом orders, и вычисляет полную стоимость всех заказанных товаров. Первый цикл foreach перебирает по одному заказу за каждый проход цикла, и в этот момент LINQ начинает получать информацию из базы данных.
Если выполнить в программе весь этот код еще раз, то LINQ снова начнет считывать информацию из базы данных, что может замедлить работу программы. Если необходимо обработать одни и те же данные несколько раз, лучше сохранить результат запроса в списке:
List<Order> DateOrders = orders.ToList<Order>();
Этот код создает в переменной DateOrders список, который содержит результат запроса. Этот список можно использовать в программе без повторного считывания одних и тех же записей из базы данных. При вызове метода ToList выполняется запрос LINQ и возвращается список результатов.
LINQ позволяет создавать запросы для получения данных из нескольких таблиц с использованием операции
var allOrders = from Customer c in newDB.CustomerTable
join Order o in newDB.OrderTable
on c.CustomerID equals o.OrderCustomerID
select new { c.Name, o.OrderDescription };
Этот запрос LINQ содержит ключевые слова from, join и select, которые встроены в язык C#. Первая часть запроса находит всех клиентов в таблице Customer. Затем к результату запроса присоединяются заказы из таблицы Order в соответствии с условием равенства значений идентификаторов клиента в этих таблицах. После этого создается новый тип данных, который содержит результаты, указанные в секции select. В нашем случае будет создан список объектов, содержащих информацию об имени клиента и описании заказа.
На основе результатов этого запроса можно сформировать список заказов клиентов:
List<String> OrderDescriptions = new List<String>();
foreach (var orderDesc in allOrders)
{
OrderDescriptions.Add(orderDesc.Name + " заказал: " + orderDesc.OrderDescription);
}
Как и в предыдущем примере, результаты считываются из базы данных непосредственно перед их использованием в цикле. Ключевое слово var указывает на то, что тип переменной orderDesc будет определен при присвоении ей значения. Для этой переменной нельзя указать конкретный тип, поскольку он создается LINQ при выполнении запроса и будет известен только во время выполнения этого запроса.
При необходимости можно удалить ненужные объекты из базы данных. Например, можно удалить ненужный заказ, используя метод DeleteOnSubmit:
ActiveDB.OrderItemTable.DeleteOnSubmit(item);
Также есть метод DeleteAllOnSubmit, который может удалить заданную коллекцию объектов.
Следует обратить внимание на удаление объектов, которые связаны отношениями с другими объектами. Если попытаться удалить объект, который содержит другие объекты, то база данных сгенерирует исключение. Сначала нужно удалить все дочерние объекты, и только после этого можно удалить родительский.
Isolated Storage Explorer позволяет программистам просматривать содержимое изолированного хранилища разрабатываемого приложения.[Table] и [Column]. Также могут использоваться дополнительные параметры для определения первичных ключей.EntityRef, который содержит ссылку на EntitySet используется для того чтобы свойство одного объекта, связанного с таблицей базы данных, могло ссылаться на несколько записей другой таблицы. К этим элементам добавляется атрибут [Association], который описывает отношения между таблицами. Упражнение 1. Использование изолированного хранилища
В предыдущем упражнении мы создавали простое приложение TimeTracker, которое сохраняло информацию о времени встреч с клиентами. Предыдущая версия программы при запуске генерировала набор тестовых данных. Необходимо использовать хранилище файлов, чтобы приложение сохраняло информацию о встречах.
Загрузка сохраненных данных
Программист внес в программу изменения, однако, тестировщик обнаружил, что программа не сохраняет изменения данных. Необходимо выявить причину этой проблемы.
CustomerManager в папке Lab5 Customer Time Logger Storage. Этот проект содержит версию программы, в которой обнаружена ошибка.App. В конце этого метода находится код, который должен загружать данные из изолированного хранилища. Попробуйте определить причину возникающей проблемы.ActiveCustomerList = Customers.MakeTestCustomers();
Customer Manager Values и найдите методы LoadCustomers и SaveCustomers. LoadCustomers возвращает список клиентов или значение null, если список не может быть считан. Если метод возвращает null, программа должна создать набор тестовых данных. Замените указанный в шаге 10 код на следующий:
// загрузить список клиентов из файла
ActiveCustomerList = LoadCustomers(ListFilename);
// если загрузить список не удалось, создать тестовые данные
if (ActiveCustomerList == null)
ActiveCustomerList = Customers.MakeTestCustomers();
Пропадание информации о встрече
Тестировщик нашел в программе еще одну проблему: программа некорректно сохраняет информацию о встрече. Необходимо исследовать эту проблему.
Программа создает объекты для работы с потоками StreamReader и StreamWriter для загрузки и сохранения элементов в изолированном хранилище. Каждый объект может сохранить свое состояние в поток и загрузить информацию из потока. Ниже приведен код методов для сохранения информации в поток и для загрузки информации из потока:
public void SaveToStream(StreamWriter output)
{
output.WriteLine(CustomerList.Count);
foreach (Customer c in CustomerList)
{
c.SaveToStream(output);
}
}
public Customers(StreamReader input)
{
CustomerList = new List<Customer>();
int noOfCustomers = int.Parse(input.ReadLine());
for (int i = 0; i < noOfCustomers; i++)
{
CustomerList.Add(new Customer(input));
}
}
Этот код выглядит правильно. Необходимо проверить класс Customer, правильно ли он использует эти методы.
Customers и просмотрите метод SaveToStream:
public void SaveToStream(StreamWriter output)
{
output.WriteLine(Name);
output.WriteLine(Address);
output.WriteLine(ID);
}
Customer:
public Customer(StreamReader input)
{
Name = input.ReadLine();
Address = input.ReadLine();
ID = int.Parse(input.ReadLine());
int noOfSessions = int.Parse(input.ReadLine());
}
CustomerList для хранения информации о клиентах. Измените методы для сохранения и загрузки, чтобы можно было сохранять информацию о нескольких встречах:
public void SaveToStream(StreamWriter output)
{
output.WriteLine(Name);
output.WriteLine(Address);
output.WriteLine(ID);
output.WriteLine(Sessions.Count);
foreach (Session session in Sessions)
{
session.SaveToStream(output);
}
}
public Customer(StreamReader input)
{
Name = input.ReadLine();
Address = input.ReadLine();
ID = int.Parse(input.ReadLine());
int noOfSessions = int.Parse(input.ReadLine());
for (int i = 0; i < noOfSessions; i++)
{
Sessions.Add(new Session(input));
}
}
Упражнение 2. Использование базы данных
В программу, созданную в предыдущем упражнении, внесли изменения, чтобы она могла работать с базой данных. Однако, программа компилируется, но не запускается. Необходимо исправить ошибку.
Запустите программу. Программа сгенерирует тестовые данные и попытается их использовать. После этого будет сгенерировано исключение, в котором база данных сообщает, что проблема связана с внешним ключом. Это касается связей между классами Session и Customer. Сообщение об ошибке информирует о том, что невозможно вставить
При создании связи встречи с клиентом запись в таблице встреч должна содержать значение первичного ключа, которое идентифицирует клиента. Однако, если этот ключ еще не был задан, база данных не может добавить запись. При создании связи между этими двумя таблицами необходимо:
customerID для встречи, чтобы идентифицировать клиента для этой встречи.Код, который выполняет эти действия, выглядит следующим образом:
int sessionLength = sessionRand.Next(5,120); string sessionDesc = "Встреча " + i.ToString(); Session newSession = new Session(); newSession.Description = sessionDesc; newSession.LengthInMins = sessionLength; newSession.SessionCustomer = c; newDB.SessionTable.InsertOnSubmit(newSession); c.Sessions.Add(newSession);
Тестирование хранения данных
В настоящее время при каждом запуске программа создает новую базу данных. Измените программу так, чтобы она использовала существующую базу данных и создавала новую, если база данных не существует. Обратите внимание, что новая база данных создается при выполнении следующей строки кода в файле App.xaml.cs:
CustomerDB.MakeTestDB("Data Source=isostore:/Sample.sdf");
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.