Текст книги "Программирование мобильных устройств на платформе .NET Compact Framework"
Автор книги: Иво Салмре
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 48 (всего у книги 69 страниц)
Перенос типизированных объектов ADO.NET DataSet на мобильные устройства
В Visual Studio .NET предусмотрена поддержка преобразования описаний схемы данных в исходный код типизированных объектов DataSet на стадии проектирования. К сожалению, не все из присутствующих в этом исходном коде типов, свойств и методов поддерживаются в .NET Compact Framework.
Если вы возьмете исходный код типизированного объекта DataSet, сгенерированный для настольного компьютера, и перенесете его путем копирования в проект .NET Compact Framework, то при компиляции этого проекта получите несколько сообщений об ошибках. Эти ошибки компиляции можно разбить на три категории:
1. Некоторые ошибки этого типа обусловлены наличием содержащих метаданные атрибутов классов и функций, которые не поддерживаются в .NET Compact Framework; эти метаданные можно эффективно исключить из кода, поставив перед ними символы комментария.
Например, атрибут [System.ComponentModel.ToolboxItem(true)] не поддерживается, и его можно поместить в комментарий.
2. Некоторые ошибки этого типа обусловлены отсутствием типов исключений; вместо таких исключений можно генерировать более общие исключения.
Например, поскольку в .NET Compact Framework не определено исключение new StrongTypingException(), то вместо него можно использовать исключение new Exception().
3. Некоторые ошибки этого типа обусловлены автоматически сгенерированным кодом, в котором используется функциональность, не поддерживаемая в .NET Compact Framework; функции, использующие эту функциональность, можно исключить с помощью символов комментария.
Скомпилировать в .NET Compact Framework код типизированных объектов DataSet, предназначенный для настольных компьютеров, не составляет особого труда; это даст вам великолепную возможность позаимствовать некоторые полезные концепции типизированных объектов DataSet и использовать их в своем мобильном приложении.
Вместо того чтобы отталкиваться от кода для настольного компьютера и отбрасывать ненужные части, гораздо надежнее справиться с задачей поддержки доступа к типизированным объектам DataSet, создавая собственные типизированные классы DataSet, DataTable и DataRow с нуля путем порождения строго типизированных классов из этих основных типов ADO.NET и импортирования лишь отдельных частей кода, автоматически сгенерированного для настольного проекта, в соответствии с необходимостью. Гораздо надежнее самостоятельно создать указанную поддержку с нуля и добавить лишь тот код, в котором есть необходимость, чем исходить из кода для настольных компьютеров и пытаться исключать из него отдельные фрагменты до тех пор, пока он не заработает. Вы получите более понятный, лучше разработанный и более легкий в сопровождении код, если создадите его с самого начала. Код для настольных компьютеров следует рассматривать лишь как полезное учебное пособие и не копировать его фрагменты непосредственно в код мобильного приложения.
Меры по обеспечению максимальной производительности при работе с объектами ADO.NET DataSet
При работе с объектами DataSet очень важно не забывать об эффективности кода. Почти с одинаковой легкостью можно написать как эффективный код, так и код, производительность которого будет крайне низка. Распространенной ошибкой разработчиков, приводящей к низкой производительности кода, является поиск таблиц и столбцов по их строковым именам, а не при помощи более эффективных механизмов индексирования. Этот момент приобретает еще большее значение при доступе к полям строк данных, поскольку эта операция часто выполняется в итерационных циклах, включающих значительное количество строк. Обращение к отдельным элементам при осуществлении такого доступа может осуществляться тремя способами, перечисленными ниже в порядке, соответствующем увеличению производительности:
1. Поиск полей с использованием строковых имен. Например: myRow["myColumnName"]; этот способ является самым медленным, поскольку для нахождения нужного поля строка имени искомого столбца должна сравниваться со всеми имеющимися именами столбцов.
2. Поиск полей с использованием целочисленных индексов. Например: myRow[2]; Ввиду использования целых чисел этот способ представляет собой определенное улучшение по сравнению с поиском по строковым именам. Чтобы использовать этот механизм, ваш код должен организовать предварительный просмотр и кэширование целочисленных индексов столбцов
3. Поиск полей, с использованием объектов столбцов. Например: myRow[myColumnObject], где myColumnObject представляет объект типа System.Data.DataColumn. Этот способ обеспечивает значительное улучшение производительности по сравнению с обоими предыдущими механизмами. Чтобы использовать этот механизм, ваш код должен кэшировать объект столбца (column object), который представляет интересующее вас поле.
В листинге 14.3 приведен код, который позволяет тестировать производительность трех различных подходов, описанных выше. Этот код имитирует типичную задачу обработки данных, включающую поиск и изменение записей.
Вычисления соответствуют описанному ниже сценарию.
Агенты транспортной компании, работающие на выезде, используют мобильные устройства для внесения изменений в информацию о маршруте следования клиентов. В силу внезапного изменения погодных условий, например из-за снегопада, возникает необходимость во внесении изменений в маршрут следования группы пассажиров, дожидающихся отправки на вокзале или в аэропорте или находящихся в данный момент в движущемся поезде или на борту самолета. Требуется обновить информацию о пунктах пересадки пассажиров и пересмотреть маршруты следования. Простейший способ решения этой задачи состоит в том, чтобы вооружить сотрудников транспортной компании мобильными устройствами, в которых содержится информация о маршрутах движения транспортных средств. При наличии соответствующего мобильного приложения несколько агентов могут выйти к клиентам и решить их проблемы, избавляя их от необходимости стоять в очереди, причем во многих случаях можно успеть полностью оформить все необходимые проездные документы еще до окончания текущего рейса поезда или самолета. В мобильных устройствах содержится загруженный список клиентов и подробная информация о маршрутах их следования. Чтобы ускорить процесс поиска соответствующих записей и уменьшить вероятность ошибок, мобильные устройства оборудованы устройствами для считывания номера кредитной карточки пассажира, который используется в качестве ключа для проведения соответствующего поиска. После того как запись о клиенте будет найдена, сведения о его маршруте могут быть обновлены с учетом новой информации.
В нашем тестовом коде применяется упрощенная версия этого сценария, и в таблице данных будут содержаться только имя клиента, дата рейса и номер кредитной карточки. Разумеется, в действительности приходится осуществлять доступ к гораздо более многочисленным данным, но в нашем упрощенном варианте все основные составляющие задачи учтены, есть данные, хранящиеся на устройстве, и мы должны иметь возможность производить среди них поиск и вносить изменения. Поскольку для реальных сценариев характерно выполнение значительно большего числа операций поиска, их производительность может быть оценена путем экстраполяции результатов выполнения нашего тестового приложения.
В табл. 14 1 представлены результаты выполнения теста на физическом устройстве Pocket PC. Как и следовало ожидать, поиск по текстовому содержимому (столбец А) оказался самым медленным и потребовал 32,82 секунды. Поиск с использованием целочисленного индекса (столбец Б) привел к вполне ощутимому улучшению результатов на 8% и потребовал 30,28 секунд. Поиск с использованием объектов DataColumn (столбец В) принес 28%-ное улучшение по сравнению с текстовым поиском. Выигрыш довольно значительный и явно свидетельствует в пользу кэширования объектов DataColumn при выполнении циклических операций поиска данных в таблицах данных.
Таблица 14.1 Производительность тестового приложения при выполнении 500 итераций с использованием 201 строки данных на физическом устройстве Pocket PC
| 1 | 32,538 | 30,09 | 23,554 |
| 2 | 33,063 | 30,387 | 23,491 |
| 3 | 32,87 | 30,372 | 23,582 |
| Среднее | 32,82 | 30,28 | 23,54 |
| Относительная производительность | 100% | 92% | 72% |
Приведенный в листинге 14.3 код необходимо включить в форму в проекте Pocket PC. Для создания и выполнения приложения потребуется выполнить следующие действия.
1. Запустите Visual Studio .NET (2003 или более позднюю версию) и выберите в качестве типа приложения C# Smart Device Application.
2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)
3. Добавьте в форму элемент управления Button. Присвойте ему имя buttonRunTest.
4. Дважды щелкните на элементе управления Button в окне конструктора форм. В автоматически сгенерированной и подключенной функции обработчика событий введите код функции buttonRunTest_Click() из листинга 14.3.
5. Введите весь оставшийся код в тот же класс.
6. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения в верхней правой части формы появится кнопка OK, с помощью которой вы легко сможете закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения.
7. Запустите приложение, нажав клавишу
Листинг 14.3. Сравнение производительности различных вариантов доступа к данным с использованием объектов DataSet
System.Data.DataSet m_myDataSet; //Объект Dataset для теста
//Индексы столбцов и таблицы, подлежащие кэшированию
private bool m_indexesLookedUp = false;
private const int INVALID_INDEX = -1;
private int m_IndexOfTestColumn_CreditCard = INVALID_INDEX;
private int m_IndexOfTestColumn_TravelDate = INVALID_INDEX;
private int m_IndexOfTestTable = INVALID_INDEX;
//Столбцы данных и таблица, подлежащие кэшированию
System.Data.DataColumn m_TestColumn_CreditCard;
System.Data.DataColumn m_TestColumn_TravelDate;
private System.Data.DataTable m_TableCustomerInfo;
//3 вида тестов, которые мы можем выполнять
public enum testType {
textColumnLookup, cachedIndexLookup, cachedColumnObject
}
//Эти константы определяют размерные характеристики тестов
const int DUMMY_ROWS_OF_DATA = 100;
const int NUMBER_TEST_ITERATIONS = 500;
//Табличная информация
const string TABLE_NAME_PASSENGERINFO = "CustomerTravelInfo";
const string COLUMN_NAME_DATE_OF_TRAVEL = "DateOfTravel";
const string COLUMN_NAME_PASSENGER_NAME = "PassengerName";
const string COLUMN_NAME_PASSENGER_CREDIT_CARD = "PassengerCreditCard";
const string TEST_CREDIT_CARD = "IvoCard-987-654-321-000";
//–
//Создает набор данных
//–
private void createDataSet() {
//1. Создать новый объект DataSet
m_myDataSet = new System.Data.DataSet("TravelService Dataset");
//2. Добавить объект DataTable в объект ADO.NET DataSet
System.Data.DataTable myTestTable;
myTestTable = m_myDataSet.Tables.Add(TABLE_NAME _PASSENGERINFO);
//Добавить 2 столбца в таблицу
//Добавить столбец данных в таблицу DataTable набора данных DataSet
myTestTable.Columns.Add(COLUMN_NAME_DATE_OF_TRAVEL,typeof(System.DateTime));
//Добавить столбец строк в таблицу DataTable набора данных
DataSet myTestTable.Columns.Add(COLUMN_NAME_PASSENGER NAME,typeof(string));
//Добавить столбец строк в таблицу DataTable набора данных DataSet
myTestTable.Columns.Add(COLUMN_NAME_PASSENGER_CREDIT_CARD,typeof(string));
//Данные для размещения в строках данных
object[] objArray;
objArray = new object[3];
//–
//Добавить строки данных в таблицу
//–
System.Text.StringBuilder buildTestString;
buildTestString = new System.Text.StringBuilder();
for (int addItemsCount = 0; addItemsCount < DUMMY_ROWS_OF_DATA; addItemsCount++) {
//Выбрать день отъезда пассажира
objArray[0] = System.DateTime.Today.AddDays(addItemsCount);
//Выбрать имя пассажира
buildTestString.Length = 0;
buildTestString.Append("TestPersonName");
buildTestString.Append(addItemsCount);
objArray[1] = buildTestString.ToString();
//Связать с пассажиром текстовый номер кредитной карточки
buildTestString.Length = 0;
buildTestString.Append("IvoCard-000-000-0000-");
buildTestString.Append(addItemsCount);
objArray[2] = buildTestString.ToString();
//Добавить элементы массива в строку набора данных
myTestTable.Rows.Add(objArray);
}
//Добавить элемент, поиск которого мы хотим проводить при выполнении теста
objArray[0] = System.DateTime.Today;
objArray[1] = "Ms. TestPerson";
objArray[2] = TEST_CREDIT_CARD;
//Добавить элементы массива в строку набора данных
myTestTable.Rows.Add(objArray);
} //Конец функции
//–
//Найти и кэшировать все индексы набора данных, которые нам нужны
//–
private void cacheDataSetInfo() {
//Выйти из функции, если индексы уже загружены
if (m_indexesLookedUp == true) {
return;
}
//Кэшировать индекс таблицы
m_IndexOfTestTable = m_myDataSet.Tables.IndexOf(TABLE_NAME_PASSENGERINFO);
//–
//Итерировать по всем столбцам нашей таблицы
//и кэшировать индексы нужных столбцов
//–
m_TableCustomerInfo = m_myDataSet.Tables[m_IndexOfTestTable];
int dataColumnCount = m_TableCustomerInfo.Columns.Count;
System.Data.DataColumn myColumn;
for (int colIdx = 0; colIdx < dataColumnCount;) {
myColumn = m_TableCustomerInfo.Columns[colIdx];
//Предпринимать поиск, только если это еще не сделано
if (m_IndexOfTestColumn_CreditCard == INVALID_INDEX) {
//Проверить, совпадает ли имя
if (myColumn.ColumnName == COLUMN_NAME_PASSENGER_CREDIT_CARD) {
//Кэшировать индекс
m_IndexOfTestColumn_CreditCard = colIdx;
//Кэшировать столбец
m_TestColumn_CreditCard = myColumn;
goto next_loop_iteration; //Опустить другие операции сравнения...
} //Endif: сравнение строк
} //Endif
if (m_IndexOfTestColumn_TravelDate == INVALID_INDEX) {
//Проверить, совпадает ли имя
if (myColumn.ColumnName == COLUMN_NAME_DATE_OF_TRAVEL) {
//Кэшировать индекс
m_IndexOfTestColumn_TravelDate = colIdx;
//Кэшировать столбец
m_TestColumn_TravelDate = myColumn;
goto next_loop_iteration; //Опустить другие операции сравнения.
} //Endif: сравнение строк
} //Endif
next_loop_iteration:
colIdx++;
}
m_indexesLookedUp =true;
}
//–
//Выполнить тест
//–
void changeDayOfTravel_test(testType kindOfTest) {
//Отобразить курсор ожидания
System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
//Начать с известной даты...
System.DateTime newDate;
newDate = System.DateTime.Today;
changeDayOfTravel_textColumnLookup(ТЕST_CREDIT_CARD, newDate);
//ДОПУСТИМО ТОЛЬКО ДЛЯ ТЕСТОВОГО КОДА!!!
//Вызов сборщика мусора в коде ЗАМЕДЛИТ работу вашего приложения!
System.GC.Collect();
const int testNumber = 0;
//Настроить соответствующим образом в зависимости от вида выполняемого теста
switch (kindOfTest) {
case testType.textColumnLookup:
PerformanceSampling.StartSample(testNumber, "Text based Column lookup.");
break;
case testType.cachedIndexLookup:
PerformanceSampling.StartSample(testNumber, "Cached Column Index lookup.");
break;
case testType.cachedColumnObject:
PerformanceSampling.StartSample(testNumber, "Cached Column objects");
break;
default:
throw new Exception("Unknown state!");
}
//Выполнить тест!
for (int testCount = 0; testCount < NUMBER_TEST_ITERATIONS; testCount++) {
//Передвинуть дату вперед на один день
newDate = newDate.AddDays(1);
int numberRecordsChanged = 0;
//Какой вид теста мы выполняем?
switch (kindOfTest) {
case testType.textColumnLookup:
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Просмотреть все имена, используя СТРОКИ
numberRecordsChanged =
changeDayOfTravel_textColumnLookup(ТЕST_CREDIT_CARD, newDate);
break;
case testType.cachedIndexLookup:
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированные индексы
numberRecordsChanged =
changeDayOfTravel_cachedColumnIndex(ТЕST_CREDIT_CARD, newDate);
break;
case testType.cachedColumnObject:
//НАИЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированные объекты
//столбцов
numberRecordsChanged =
changeDayOfTravel_CachedColumns(TEST_CREDIT_CARD, newDate);
break;
}
//Убедиться в том, что тест выполняется, как и ожидалось...
if (numberRecordsChanged != 1) {
System.Windows.Forms.MessageBox.Show("No matching records found. Test aborted!");
return;
}
}
//Получить время, которое потребовалось для выполнения теста
PerformanceSampling.StopSample(testNumber);
//Обычный курсор
System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
//Отобразить результаты выполнения теста
string runInfo = NUMBER_TEST_ITERATIONS.ToString() + "x" +
DUMMY_ROWS_OF_DATA.ToString() + ": ";
System.Windows.Forms.MessageBox.Show(runInfo +
PerformanceSampling.GetSampleDurationText(testNumber));
}
//ФУНКЦИЯ ПОИСКА, ОБЛАДАЮЩАЯ НИЗКОЙ ПРОИЗВОДИТЕЛЬНОСТЬЮ
private int changeDayOfTravel_ textColumnLookup(string creditCardNumber, System.DateTime newTravelDate) {
int numberRecordsChanged = 0;
//Найти имя таблицы
System.Data.DataTable dataTable_Customers;
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Осуществить поиск в таблице, используя
//сравнение строк!
dataTable_Customers = m_myDataSet.Tables[TABLE_NAME_PASSENGERINFO];
foreach (System.Data.DataRow currentCustomerRow in dataTable_Customers.Rows) {
string currentCreditCard;
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Осуществить поиск в таблице, используя
//сравнение строк!
currentCreditCard = (string)currentCustomerRow[COLUMN_NAME_PASSENGER_CREDIT_CARD];
//Проверить, является ли данная кредитная карточка искомой
if (creditCardNumber == currentCreditCard) {
//Изменить дату отъезда
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Осуществить поиск столбца, используя
//сравнение строк!
System.DateTime currentTravelDate =
(System.DateTime)currentCustomerRow[COLUMN_NAME_DATE_OF_TRAVEL];
if (currentTravelDate != newTravelDate) {
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Осуществить поиск столбца, используя
//сравнение строк!
currentCustomerRow[COLUMN_NAME_DATE_OF_TRAVEL] = newTravelDate;
numberRecordsChanged++;
}
} //endif: сравнение строк
} //end foreach
return numberRecordsChanged; //Количество обновленных записей
}
//ФУНКЦИЯ, ХАРАКТЕРИЗУЮЩАЯСЯ НЕСКОЛЬКО ЛУЧШЕЙ ПРОИЗВОДИТЕЛЬНОСТЬЮ
private int changeDayOfTravel_cachedColumnIndex(string creditCardNumber, System.DateTime newTravelDate) {
int numberRecordsChanged = 0;
//Поиск имени таблицы
System.Data.DataTable dataTable_Customers;
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: использовать кэшированный индекс
dataTable_Customers = m_myDataSet.Tables[m_IndexOfTestTable];
foreach (System.Data.DataRow currentCustomerRow in dataTable_Customers.Rows) {
string currentCreditCard;
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: использовать кэшированный индекс столбца!
currentCreditCard =
(string)currentCustomerRow[m_IndexOfTestColumn_CreditCard];
//Проверить, совпадает ли номер кредитной карточки...
if (creditCardNumber == currentCreditCard) {
//Изменить дату отъезда
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированный индекс столбца!
System.DateTime currentTravelDate =
(System.DateTime)currentCustomerRow[m_IndexOfTestColumn_TravelDate];
if (currentTravelDate != newTravelDate) {
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированный индекс
//столбца!
currentCustomerRow[m_IndexOfTestColumn_TravelDate] = newTravelDate;
numberRecordsChanged++;
}
}
}
return numberRecordsChanged; //Количество обновленных записей
}
//ФУНКЦИЯ, ОБЛАДАЮЩАЯ НАИЛУЧШЕЙ ПРОИЗВОДИТЕЛЬНОСТЬЮ
private int changeDayOfTravel_CachedColumns(string creditCardNumber, System.DateTime newTravelDate) {
int numberRecordsChanged = 0;
//Найти имя таблицы
System.Data.DataTable dataTable_Customers = m_TableCustomerInfo;
foreach (System.Data.DataRow currentCustomerRow in dataTable_Customers.Rows) {
string currentCreditCard;
//НАИЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированный индекс столбца!
currentCreditCard =
(string)currentCustomerRow[m_TestColumn CreditCard];
//Проверить, совпадает ли номер кредитной карточки...
if (creditCardNumber == currentCreditCard) {
//Изменить дату отъезда
//НАИЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированный индекс столбца!
System.DateTime currentTravelDate =
(System.DateTime)currentCustomerRow[m_TestColumn_TravelDate];
if (currentTravelDate != newTravelDate) {
//НАИЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать кэшированный индекс
//столбца!
currentCustomerRow[m_TestColumn TravelDate] = newTravelDate;
numberRecordsChanged++;
}
}
}
return numberRecordsChanged; //Количество обновленных записей
}
//Событие щелчка на кнопке
private void buttonRunTest_Click(object sender, System.EventArgs e) {
createDataSet();
cacheDataSetInfo();
//НИЗКАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать поиск по строкам
changeDayOfTravel_test(testType.textColumnLookup);
//ЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать поиск по целочисленным индексам
changeDayOfTravel_test(testType.cachedIndexLookup);
//НАИЛУЧШАЯ ПРОИЗВОДИТЕЛЬНОСТЬ: Использовать поиск по объектам столбцов
changeDayOfTravel_test(testType.cachedColumnObject);
}
В каких случаях не следует использовать объекты ADO.NET DataSet
Если данные мобильного приложения большей частью применяются только для чтения, если приходится хранить большие объемы данных в памяти или если между данными существуют сравнительно простые отношения, то стоит подумать о привлечении пользовательской модели управления данными. Объекты ADO.NET DataSet можно эффективно реализовать, но предлагаемая ими модель управления данными является универсальной. Можно добиться существенного выигрыша в объеме занимаемой памяти и производительности, создав специализированную модель данных, которая наилучшим образом соответствует вашим потребностям. Ключевым фактором эффективного использования памяти является уменьшение количества объектов, которые должны размещаться для хранения ваших данных; как правило, чем меньше объектов, тем меньше нагрузка на память и тем выше производительность.
В табл. 14.2 и листинге 14.4 представлены результаты использования оптимизированного пользовательского формата для хранения строк данных. Тестовый код, приведенный в листинге 14.4, выполняет ту же задачу, что и код из листинга 14.3, но вместо объектов ADO.NET DataSet в нем используется простой типизированный массив данных. От такой замены производительность приложения выигрывает в двух отношениях:
1. Быстродействие. Как видно из табл. 14.2, применение пользовательского формата данных позволило уменьшить время выполнения приложения до 38% того времени, которое было зафиксировано для решения, базирующегося на ADO.NET с использованием текстового индекса. Сравнение с результатом, достигнутым в оптимизированном варианте поиска, в котором использовался объект DataColumn, свидетельствует о сокращении времени выполнения почти в два раза (12,32/23,54=52,3%).
2. Снижение нагрузки на память. Поскольку в решении, основанном на пользовательском формате данных, для хранения данных требуется лишь минимальное количество объектов, а накладные расходы, связанные с объектами ADO.NET DataSet, отсутствуют, наше приложение предъявляет к памяти менее жесткие требования. Это означает, что при прочих равных условиях объем работы по сборке мусора уменьшается, а рабочее пространство для кода приложения увеличивается. Хотя этот результат и не отражен в таблице, он положительно влияет на общую производительность приложения.
Разумеется, у решения, основанного на пользовательском формате данных, имеются и недостатки по сравнению с вариантами решений, в которых используются объекты ADO.NET DataSet. Самое главное – это то, что при необходимости обновления данных на сервере в нашем коде требуется организовать учет всех изменений данных. Это означает, что мы должны ввести, по крайней мере, один дополнительный столбец булевских значений, указывающих на то, в каких строках были сделаны изменения. Это не составляет особого труда в случае простых табличных данных, но значительно усложняется, если данные разбросаны по нескольким таблицам и связаны между собой целой системой различных отношений. Кроме того, ADO.NET предлагает объекты DataView, обеспечивающие сортировку и фильтрацию данных, чего мы лишены, если останавливаемся на варианте пользовательской реализации модели данных. Расширение возможностей связано с дополнительными накладными расходами, и поэтому вы должны тщательно взвешивать все за и против, выбирая между функциональностью Al)O.NET и потенциально более высокой эффективностью пользовательской реализации. В случае приложений для устройств повышение эффективности часто окупает дополнительные затраты труда на разработку пользовательской модели данных на стадии проектирования.
Таблица 14.2. Сравнение производительности варианта приложения с пользовательским форматом данных, выполняющегося на физическом устройстве Pocket PC, с предыдущими результатами
| (ADO.NET) | (ADO.NET) | (ADO.NET) | (Пользовательский) | |
| 1 | 32,538 | 30,09 | 23,554 | 12,268 |
| 2 | 33,063 | 30,387 | 23,491 | 12,335 |
| 3 | 32,87 | 30,372 | 23,582 | 12,358 |
| Среднее | 32,82 | 30,28 | 23,54 | 12,32 |
| Относительная производительность | 100% | 92% | 72% | 38% |
Приведенный в листинге 14.4 код необходимо включить в форму в проекте Pocket PC. Для создания и выполнения приложения потребуется выполнить следующие действия:
1. Запустите Visual Studio .NET (2003 или более позднюю версию) и выберите в качестве типа приложения C# Smart Device Application.
2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)
3. Добавьте в форму элемент управления Button. Присвойте ему имя buttonRunTest.
4. Дважды щелкните на элементе управления Button в окне конструктора форм. В автоматически сгенерированной и подключенной функции обработчика событий введите код функции buttonRunTest_Click() из листинга 14.4.
5. Введите весь оставшийся код в тот же класс.
6. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения в верхней правой части формы появится кнопка OK, с помощью которой вы легко сможете закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения.
7. Запустите приложение, нажав клавишу
Листинг 14.4. Результаты тестирования производительности при использовании пользовательского формата данных вместо объектов DataSet
//Определение размерных характеристик теста
const int DUMMY_ROWS_OF_DATA = 100;
const int NUMBER_TEST_ITERATIONS = 500;
const string TABLE_NAME_PASSENGERINFO = "CustomerTravelInfo";
const string TEST_CREDIT_CARD = "IvoCard-987-654-321-000";
string [] m_data_creditCards;
string [] m_data_names;
System.DateTime [] m_data_travelDates;
//–
//Создает массив данных (вместо использования объектов DataSet)
//–
private void createDataSet() {
//=============================================
//1. Создать пространство для размещения данных
//=============================================
m_data_creditCards = new string[DUMMY_ROWS_OF_DATA + 1];
m_data_names = new string[DUMMY_ROWS_OF_DATA + 1];
m_data_travelDates = new System.DateTime[DUMMY_ROWS_OF_DATA + 1];
//–
//Добавить строки данных
//–
System.Text.StringBuilder buildTestString;
buildTestString = new System.Text.StringBuilder();
for (int addItemsCount = 0; addItemsCount < DUMMY_ROWS_OF_DATA; addItemsCount++) {
//Выбрать день отъезда пассажира
m_data_travelDates[addItemsCount] = System.DateTime.Today.AddDays(addItemsCount);
//–
//Выбрать имя пассажира
//–
//Очистить строку
buildTestString.Length = 0;
buildTestString.Append("TestPersonName");
buildTestString.Append(addItemsCount);
m_data_names[addItemsCount] = buildTestString.ToString();
//–
//Связать с пассажиром текстовый номер кредитной карточки
//–
//Строка значения третьего столбца набора данных
buildTestString.Length = 0;
buildTestString.Append("IvoCard-000-000-0000-");
buildTestString.Append(addItemsCount);
m_data_creditCards[addItemsCount] = buildTestString.ToString();
}
//Добавить элемент, поиск которого мы хотим выполнить в нашем тесте...
//Выбрать день для значения в первом столбце данных
m_data_travelDates[DUMMY_ROWS_OF_DATA] = System.DateTime.Today;
//Строка для второго столбца данных
m_data_names[DUMMY_ROWS_OF_DATA] = "Ms. TestPerson";
//Строка с идентификатором кредитной карточки
m_data_creditCards[DUMMY_ROWS_OF_DATA] = ТЕST_CRE DIT_CARD;
} //Конец функции
//–
//Выполнить тест...
//–
void changeDayOfTravel_test() {
//Отобразить курсор ожидания
System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
//Начать с известной даты...
System.DateTime newDate;
newDate = System.DateTime.Today;
changeDayOfTravel_CustomArrays(ТЕST_CREDIT_CARD, newDate);
//ТОЛЬКО В ЦЕЛЯХ ТЕСТИРОВАНИЯ!!!
//HE СЛЕДУЕТ использовать вызовы сборщика мусора в готовом программном
//коде. Это ЗАМЕДЛЯЕТ работу приложения.
System.GC.Collect();
const int testNumber = 0;
//Запустить таймер теста
PerformanceSampling.StartSample(testNumber, "Custom Array implementation");
//Запустить тест!
for(int testCount = 0; testCount < NUMBER_TEST_ITERATIONS; testCount++) {
//Передвинуть дату вперед на один день
newDate = newDate.AddDays(1);
int numberRecordsChanged = 0;
//Просмотреть все имена, используя СТРОКИ
numberRecordsChanged = changeDayOfTravel_CustomArrays(TEST_CREDIT_CARD, newDate);
//Убедиться в нормальном выполнении теста...
if (numberRecordsChanged != 1) {
System.Windows.Forms.MessageBox.Show("No matching records found. Test aborted!");
return;
}
}
//Получить время выполнения теста
PerformanceSampling.StopSample(testNumber);
//Обычный курсор
System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
//Отобразить результаты теста
string runInfo = NUMBER_TEST_ITERATIONS.ToString() + "x" +
DUMMY_ROWS_OF_DATA.ToString() + ": ";
System.Windows.Forms.MessageBox.Show(runInfo +
PerformanceSampling.GetSampleDurationText(testNumber));







