Исследование технологий доступа к данным для операционной системы Windows

ИССЛЕДОВАНИЕ ТЕХНОЛОГИЙ ДОСТУПА К ДАННЫМ ДЛЯ ОПЕРАЦИОННОЙ СИСТЕМЫ WINDOWS

В.А. Козак Научный руководитель - А.А. Малинин

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

Введение

В сложных распределенных системах очень важно поддерживать актуальность используемых данных, поэтому мы должны уметь их быстро изменять. Кроме того, именно недостаточная скорость выполнения приложения вынудит пользователя отказаться от использования такого продукта. Об архитектурных особенностях различных технологий сказано уже немало, а целью данной работы было выявить технологию с лучшей производительностью. Для выполнения такой задачи был разработан набор тестовых программ, осуществляющих подключение к базе данных и выполнение несложных запросов. Поскольку тестировались не сами базы данных, а средства доступа к ним, то применение изощренных запросов видится автору нецелесообразным. Среди предыдущих работ в данной области хочется отметить статью С. Михайлова «Сравнение скорости доступа к данным (ADO.NET, ADO, ascDB)», опубликованную в журнале «RSDN Magazine» в 2003 году [1]. Но в ней автор использует другую базу данных, поэтому результаты выполнения аналогичных запросов несколько отличаются. Кроме того, нужно учесть, что в той работе среди исследуемых технологий был их собственный продукт. В данном исследовании набор технологий был дополнен ODBC, а для ADO.NET рассматривались два провайдера: стандартный ODBC Provider и специализированный MySQL Provider. Также автор хотел бы поделиться своим субъективным мнением по поводу удобства использования той или иной технологии.

Описание тестов

Для тестирования были отобраны следующие технологии: ODBC, ADO, ADO.NET (ODBC Provider и MySQL Provider). По различиям в этих технологиях можно судить о развитии программирования под Windows. ODBC представляет собой набор динамически подключаемых библиотек [2]. ADO основано на COM, эта модель стала в свое время целой эпохой в развитии клиент/серверных приложений на базе Windows [3]. ADO.NET - часть новой глобальной платформы от Microsoft. Клиентские приложения, использующие ADO.NET, написаны на С#, остальные - на С++. В качестве сервера баз данных использовался MySQL 5.0.20. Сервер был установлен на локальную машину из бинарного дистрибутива, все параметры приняты «по умолчанию». В нем были созданы тестовые таблицы, содержащие 9 полей со следующими типами данных: строки с длиной от 10 до 100 символов (5 полей), int (3 поля), double (одно поле). Таблицы создавались в отдельной базе данных и не связаны с другими таблицами или между собой.

Выполнялись следующие тесты. 1. «ForwardAll» - получение аналога forward-only курсора на 1 миллион записей с прокруткой на различное количество записей. Курсор Forward-only (только вперед) позволяет вам перемещаться по набору данных в направлении от начала к концу [4].

2. «Select 100» - 1000 раз получение аналога forward-only курсора на 100 записей с прокруткой всего списка.

3. «Static» - получение аналога static-курсора на 100 тысяч записей и прокрутка его 1000 раз на удаленные друг от друга позиции (1000 записей с возвратом на первую запись). При использовании статического курсора набор данных полностью перемещается на сторону клиента [4].

4. «ManyConnection» - 1000 раз добавление (изменение, удаление) одной записи. Для каждой операции создавалось новое соединение.

5. «OneConnection» - проведение 1000 операций по добавлению, изменению, удалению одной записи, используя единственное соединение с базой данных.

Каждый тест выполнялся в «in-process» режиме, а ADO.NET тестировалось также и в локальном режиме через COM+. В первом случае весь исполняемый код находился в одном exe-приложении, т.е. приложение напрямую работало с базой данных, минуя промежуточное копирование данных и передачу их между процессами. Во втором -работа велась через отдельный промежуточный COM-объект, создаваемый в другом процессе. В этом объекте происходило получение result-set'a, данные из которого затем возвращались клиентскому приложению. Для передачи данных между процессами использовался объект DataSet.

В качестве тестовой машины использовался компьютер со следующими параметрами:

• ОС Microsoft Windows XP Professional Service Pack 2;

• AMD Athlon(tm) 64 Processor 3000+, 2010 MHz;

• RAM 512 Мб.

Особенности теста ForwardAll

В этом тесте операции проводятся над очень большой таблицей в forward-only режиме. На базу данных посылался запрос "SELECT * FROM EmployeesMySQL", в таблице миллион записей. Цель этого теста - проверить, станет ли MySQL сразу производить выборку или будет искать нужные строки во время прокрутки курсора. Такое поведение демонстрирует, например, MS SQL Server. Как оказалось, MySQL честно подготовил выборку сразу после посылки команды «выполнить», что не лучшим образом сказалось на времени выполнения теста. В среднем около 11 секунд тестовые программы ждали, пока сервер закончит выборку. Далее из полученной выборки на клиентское приложение извлекались отдельные строки - производился перебор выборки. В зависимости от количества перебираемых строк мы получали различное время. Было интересно, каково же отношение скорости формирования выборки на сервере к скорости обработки записей из этой выборки и как такое поведение MySQL сервера скажется на быстродействии при обработке части первоначальной выборки.

Код программы этого теста для технологии ODBC был написан на C++ без использования каких-либо вспомогательных классов из библиотеки MFC или других. Производился непосредственный вызов функций ODBC, которым передавались необходимые параметры. Таким образом, код оказался довольно-таки громоздким. Начальные установки в ODBC удовлетворяют условиям нашего теста. При выделении памяти для идентификаторов использовалась функция SQLAllocHandle, для выполнения подготовленного оператора - SQLExecute, для перемещения по выборке - SQLFetch. Чтобы в дальнейшем не возвращаться к написанию кода для базовых операций, ниже представлена последовательность вызова основных функций. Каждая ODBC функция возвращает код успеха или неудачи, для упрощения восприятия в представленном коде эти результаты не проверяются и никак не обрабатываются. На самом деле код этого теста для ODBC занял более ста строк.

SQLHENV henv = NULL; /* дескриптор среды */

SQLHDBC hdbc = NULL; /* дескриптор подклчения к БД */

SQLHSTMT hstmt = NULL; /* дескриптор оператора */

SQLRETURN rc; /* результат выполнения команды*/

SD WORD sDWord; /* размер считываемого столбца */

int employeeld; /* сюда запишем одно считанное значение для столбца типа int */

//получаем уникальный дескриптор среды

rc = ::SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); //устанавливаем версию ODBC

rc = :: SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);

//получаем дескриптор подклчения к БД

rc = ::SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

//подключаемся к источнику данных

rc = ::SQLConnect(hdbc, (SQLCHAR*)"MySQLdata", SQL_NTS,

(SQLCHAR*)"user", SQL_NTS, (SQLCHAR*)"password", SQL_NTS); //получаем дескриптор оператора

rc = ::SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); //текст запроса

LPCSTR szSQL = "SELECT * FROM EmployeesMySQL"; //подготавливаем оператор

rc = ::SQLPrepare(hstmt, (unsigned char*)szSQL, SQL_NTS );

//выполнение

rc = ::SQLExecute(hstmt);

//перебор по выборке (прокрутка countRow) строк

for(int i = 0; i<countRow; i++) {

//считываем данные из курсора. Здесь пример для одного столбца SQLBindCol(hstmt, 1, SQL_C_LONG, & employeeId, 0, &sDWord); //перемещение курсора rc = SQLFetch(hstmt);

}

//далее необходимо освободить выделенную память, закрыть курсор и соединение

В тесте «ForwardAll» для ADO в качестве forward-only курсора использовался объект Recordset с установкой значений свойств: CursorType в adOpenForwardOnly, LockType в adLockReADOnly и CursorLocation в adUseServer. Переход по записям осуществлялся с помощью вызова метода MoveNext. В результате перебор в ADO оказался на 30 % менее эффективен, чем в ODBC. Часть кода этого теста для ADO представлена ниже.

CoInitialize(NULL); HRESULT hr; int employeeId;

_ConnectionPtr myConnection; myConnection.CreateInstance(__uuidof(Connection)); //открытие соединения

hr = myConnection->Open("DSN=MySQLdata;", _bstr_t("user"), _bstr_t("password"), adModeUnknown);

_CommandPtr pCommand;

pCommand.CreateInstance(__uuidof(Command));

pCommand->ActiveConnection = myConnection;

pCommand->CommandText = "SELECT * FROM EmployeesMySQL";

_RecordsetPtr pRecordset;

pRecordset.CreateInstance(__uuidof(Recordset));

pRecordset->CursorLocation = adUseServer;

//открытее Recordset'a, сейчас команда посылается на СУБД

pRecordset->Open((IDispatch*)pCommand, vtMissing, adOpenForwardOnly, adLockBatchOptimistic, adCmdUnknown);

// далее приведены действия, необходимые для чтения одного столбца FieldsPtr pFields = pRecordset->Fields; FieldPtr pEmpId = pFields->GetItem("EmployeeID"); ASSERT(NULL != pEmpId);

_variant_t vEmpId;

//прокрутка Recordset'a на countRow строк

for(int i=0; i< countRow; i++) {

vEmpId = pEmpId->Value; employeeld = (int) vEmpId; pRecordset->MoveNext();

}

pCommand->Cancel();

myConnection->Close();

CoUninitialize();

В качестве аналога forward-only курсора в ADO.NET использовался IDataReader. Считывание производилось по одной записи, использовался метод Read. Как выяснилось, данная технология открывает соединение гораздо быстрее предыдущих. Когда же дело доходит до чтения данных, становится понятно, зачем разработчики баз данных создают специализированные провайдеры. В отличие от ODBC Provider'a, вынужденного взаимодействовать с менеджером драйверов, загружать конкретный драйвер, подключаться к базе данных по ее DNC, специализированный провайдер взаимодействует с базой данных напрямую. В итоге: ADO.NET с ODBC Provider демонстрирует самые плохие результаты перебора среди тестируемых технологий, тогда как ADO.NET с MySQL Provider - лучшие. Ниже представлен код для MySQL Provider'a. int employeeId;

MySqlConnection connection = null; IDataReader reader = null; try {

connection = new MySqlConnection(connectionString); MySqlCommand command = connection.CreateCommand(); connection. Open();

command.CommandText = "SELECT * FROM EmployeesMySQL"; reader = command.ExecuteReader(); for(int j = 0; j < countRow; ++j) {

// Переходить на след. запись с чтением данных reader.Read();

employeeId = reader.GetInt32(0);

}

reader.Close

connection. Close

reader.Close();

}

В случаях, когда возвращаемая выборка имеет такой большой размер, очень полезно иметь возможность считать небольшую часть выборки и сразу же начать ее использовать, а в дальнейшем производить быструю подкачку данных из выборки. ODBC и ADO реализуют такой механизм путем установки размера кэша курсора. В ADO.NET для считывания одновременно нескольких записей подходит лишь объект DbDataAdapter. Действительно, используя метод Fill, в параметры которого можно передать номер стартовой записи и количество считываемых записей, можно очень эффективно произвести первое считывание. Но, когда в следующий раз мы вызываем метод Fill, происходит переоткрытие IDataReader'a, и мы вновь ждем, пока сервер произведет выборку миллиона строк. Следовательно, на получение следующей порции данных в нашем случае уйдет минимум 11 секунд. Такой результат неприемлем.

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

быть открыт только один ГОа!аКеаёег, то, пропустив одну лишь строку в переборе, вы уже не сможете к ней вернуться.

Итоги теста БотагёАН отражены на рис 1.

80 1 70 60 50

8 40

I 30 CD

ср

Ш 20

10

ODBC

.. ф.. ADO

ADO.NE T

- -•- ADO.NE T

т-1-1-1-1-1

0 200 400 600 800 1000

количество прокрученных строк

Рис. 1. Зависимость времени выборки от количества перебираемых строк

в «in process» режиме

Особенности теста Select100

Этот тест был проведен с целью подтверждения «виновности» MySQL сервера в потере 11 секунд в тесте ForwardAll. Здесь мы также получаем forward-only курсор, только уже на 100 записей, и прокручиваем весь список. Приведенное в табл. 1 время соответствует 1000 повторов в цикле этого теста. В итоге выборка и перебор ста записей (одна итерация цикла) заняла, в зависимости от технологии, 3-9 миллисекунды. Следовательно, небольшую выборку сервер подготовил практически мгновенно, а трехкратный разброс скорости перебора только подтвердил результаты предыдущего теста.

ODBC ADO ADO.NET

Select100 8.768 9.640 9.5153 2.776

Static 1.327/0.021 2.503/0.06 8.134/0.007 3.001/0.007

Insert 1000 ManyConnection 14.095 13.612 10.343 4.535

Update 1000 ManyConnection 14.037 12.783 10.287 4.495

Delete 1000 ManyConnection 14.107 12.469 10.375 4.549

Insert 1000 OneConnection 0.803 2.205 Невозможно Невозможно

Update 1000 OneConnection 0.788 2.201 Невозможно Невозможно

Delete 1000 OneConnection 0.812 2.249 Невозможно Невозможно

Таблица 1. Быстродействие доступа к данным в «in process» режиме

Итогом этих двух тестов стали следующие выводы.

• Несмотря на все рекламные заявления, MySQL непростительно долго готовит выборку большого размера.

• Для перебора ODBC оказалась наиболее быстрой из универсальных технологий.

• Специализированный провайдер MySQL во время перебора опережал ODBC в 4 раза. Это прекрасное средство для скачивания больших объемов информации.

• На ADO.NET невозможно организовать эффективную подкачку данных, что необходимо в случае такой большой выборки.

Особенности теста Static

В данном тесте также хотелось поработать с таблицей на миллион записей, но с ADO.NET опять возникли проблемы. Объект DataSet, так разрекламированный во всех описаниях технологии, оказался неспособен обработать большую выборку. Проблема кроется во внутреннем механизме сериализации данных, реализованном для этого объекта. Если заполнение DataSet'a 200 000 записей происходит более-менее адекватно, то на 300 000 программа заметно тормозит. При увеличении выборки до 500 000 программа во время заполнения DataSeta'a (вызов метода Fill объекта DbDataAdapter) использовала до 450 Мб оперативной памяти и, загрузив процессор на 97 %, благополучно зависала.

В итоге, учитывая проблемы с DataSet, в тесте «Static» на сервер посылался запрос на выборку 100 000 записей. Далее 1000 раз производится его прокрутка на удаленные друг от друга позиции. При использовании статического курсора набор данных полностью перемещается на сторону клиента. Для этого в ODBC при помощи функции SQLSetStmtAttr атрибуту SQL_ATTR_CURSOR_TYPE было присвоено значение SQL_CURSOR_STATIC. Перемещение по выборке осуществлялось функцией SQLFetchScroll. Других принципиальных отличий от предыдущих примеров нет. В ADO объект Recordset создавался со следующими атрибутами: значение свойства CursorType - adOpenStatic, LockType в adLockReADOnly и CursorLocation в adUseClient. Переход по записям осуществлялся с помощью вызова метода Move. Для ADO.NET статический курсор создавался с использованием объекта DataSet, данные в который заносились объектом DbDataAdapter. Вначале данные помещались в этот объект, затем определенные строки извлекались, используя свойство Rows объекта Table, входящего в DataSet. Основной код для этого случая представлен ниже. DataSet dataSet = null; MySqlConnection connection = null; IDataReader reader = null;

string commandText = "SELECT * FROM EmployeesMySQL WHERE (EmployeeID<=100000)"; try {

connection = new MySqlConnection(connectionString); connection.Open();

IDbDataAdapter my_adapter = new MySqlDataAdapter(commandText, connection); dataSet = new DataSet();

my_adapter.Fill(dataSet, 0, 100000, "EmployeesMySQL"); for(int i=0; i<100; i++) {

DataRow row = dataSet.Tables[0].Rows[i*1000]; employeeId = (int)row["EmployeeID"];

}

connection.Close

connection.Close(); if(reader != null) reader.Close();

}

Поскольку данные уже находятся на стороне клиента, доступ к ним осуществляется значительно быстрее, чем в случае forward-only курсора. В представленных в табл. 1 данных отдельно отражены как время выборки и кэширования на клиенте ста тысяч записей, так и время перебора. Здесь во время формирования выборки и передачи ее клиентскому потоку ODBC вновь предпочтительней. Низкая производительность ADO.NET связана с формированием DataSet'a. Зато доступ к полученным данным, когда они уже приведены к необходимому типу, осуществляется гораздо быстрее, чем в других технологиях.

Особенности теста ManyConnection

В этом тесте на СУБД посылаются команды изменения данных. Производится вставка, изменение и удаление строк в тестовой таблице. Для каждой команды открывается свое соединение, после выполнения операции соединение закрывалось. Результаты, приведенные в табл. 1, соответствуют 1000 повторов вышеуказанных команд. Такое поведение сильно напоминает разъединенную модель, реализованную в ADO.NET, где одному соединению может соответствовать только одна команда.

Код для ODBC несложен, а про ADO надо сказать, что необходимую команду на сервер можно выполнить двумя способами: выполнить метод Execute интерфейса Command или вызвать метод Open интерфейса Recordset. Первый способ полезен, когда о результатах выполнения заботиться не нужно, второй необходим для возвращения набора записей из базы данных. В данном случае вызывался метод Execute. ADO.NET в этом тесте чувствовала себя наиболее комфортно, и результаты отражают это. В этой технологии очень быстро открывается соединение, а поскольку результаты команды обрабатывать не нужно, ADO.NET - лидер, причем, в случае с MySQL Provider'ом, превосходство над ODBC трех кратное.

Особенности теста OneConnection

Этот тест отличается от предыдущего тем, что на сервер посылается 1000 команд изменения данных, используя единственное соединение. Использование ADO.NET в таком режиме невозможно. Для этой технологии можно создать локальный DataSet, произвести над ним необходимые команды и отправить весь набор данных на сервер разом. Но такое поведение не удовлетворяет условиям теста. Необходимо проведения 1000 команд изменения данных, а не одной - закачки DataSet'a. Таким образом, ADO.NET в этом тесте участвовать не будет. Но это не просто архитектурное ограничение последней технологии, отсутствие возможности такого поведения - огромный недостаток в случае, когда необходима немедленная доставка данных на сервер. Разница в быстродействии последних двух тестов очень велика, и она была бы гораздо больше при использовании удаленного сервера, поэтому отсутствие такого поведения в ADO.NET непонятно.

Проанализируем полученные результаты. Разница в быстродействии ODBC и ADO в тесте ManyConnection невелика. Но в тесте OneConnection ODBC обогнал конкурента в три раза. Отсюда вывод: в ODBC очень удачно реализована команда, отправляющая запрос на сервер, ADO же гораздо быстрее открывает соединение с последним.

На этом исследование технологий в «in-process» режиме завершено. Подведем некоторые промежуточные результаты. Разработанная в начале 90-х годов технология ODBC так и не потеряла своей актуальности. Ее неоспоримым достоинством является быстродействие. Среди недостатков, в первую очередь, выделяется сложность использования для современных прикладных программистов.

Технология ADO, ставшая заключительным этапом развития технологий для работы с данными, основанными на OLE и COM, уступает ODBC по производительности, но в ис-

пользовании ADO гораздо удобней и обладает большей функциональностью. Кроме того, действие, выполняемое в ODBC вызовом нескольких функций, в ADO часто может быть выполнено вызовом одной, более оптимизированной функции. Несмотря на это, ADO постепенно уходит из поля зрения программистов, причина этому - новая платформа .NET и входящая в ее состав технология ADO.NET. Эта технология успешно работает в распределенных системах и использует разъединенную модель доступа к данным [5]. Для данной технологии желательно использовать специализированные провайдеры. В этом случае быстродействие не будет уступать ODBC, а порой и превосходит последнюю. Если же применять универсальные провайдеры, такие как ODBC Provider или OLE DB Provider, быстродействие такой системы будет существенно уступать предыдущим. Кроме того, количество кода, которое должен написать программист для использования ADO.NET, несравнимо меньше, чем в случае с какой-либо другой технологией.

COM+

С использованием технологии COM+ тестировалось только ADO.NET. Основной целью здесь была проверка эффективности взаимодействия модели компонентных объектов и .Net в целом. Это актуально, поскольку очень часто сборки .NET должны успешно работать в мире сложных приложений, где значительную часть кода составляют классические COM-серверы. Помимо классического COM, технология COM+ объединила в себе возможности MTS (Microsoft Transaction Server) - очень мощного сервера приложений для размещения приложений уровня предприятия. Кроме того, COM+ добавила многие важные возможности и устранила недостатки этих двух систем [6].

50 45 40 35 30

ю OR О 25

К 20  15

ср 15 ш

10 5 0

0 50 100 150 200

количество прокрученных строк

Рис. 2. Зависимость времени выборки от количества перебираемых строк

в COM+ режиме

Для возвращения результатов работы COM+ объекта в основной поток использовался объект DataSet. Код серверной части, непосредственно обращающийся к базе данных, ничем не отличается от предыдущего. Но сами классы и методы, содержащие этот код, имеют много дополнительных атрибутов из пространства имен System.EnterpriseServices. Кроме того, клиент будет взаимодействовать не с самим классом, содержащим выполняемый код, а с интерфейсом, имплементируемым этим классом. Вообще СОМ - очень сложная технология, и она не является темой данной работы. Поэтому о реализации тестов, взаимодействующих с СОМ, хочется сказать лишь следующее: помимо компиляции кодов серверной части, для удобного взаимодействия с ними клиента полученный файл

zzz

Ж

-•—ADO.NE T

> - ADO.NE T

(Server.dll) был зарегистрирован в GAC (global assembly cache) утилитой gacutil.exe. После этого объекты из библиотеки Server.dll были зарегистрированы в COM+ при помощи утилиты regsvcs. Затем было написано клиентское приложение, вызывающее методы серверного. Клиент использовал позднее связывание, т.е. получал информацию о типах сборки непосредственно во время выполнения.

В результате все тесты выполнялись значительно медленней. Замедление ForwardAll и Static связано с возвращением этими тестами довольно-таки объемного результата. Следовательно, необходимо формирование DataSet'a, копирование данных между процессами и преобразование типов этих данных. Другой неприятностью стало переполнение памяти при попытке вернуть более 200 000 строк. Низкая производительность остальных тестов вызвана большим количеством вызовов методов COM+ объекта. Результаты теста ForwardAll показаны на рис. 2, остальных тестов - в табл. 2.

Если сравнить результаты из табл. 1 и табл. 2, видно, что большинство времени уходит на активизацию и вызов промежуточного объекта.

ADO.NET ADO.NET

Select100 23,187 17,483

Static 28,384/0.009 25,184/0.009

Insert 1000 ManyConnection 23,345 18,648

Update 1000 ManyConnection 23,916 18,057

Delete 1000 ManyConnection 24,165 18,246

Таблица 2. Быстродействие доступа к данным технологии ADO.NET в локальном

COM+ режиме

Заключение

В работе проведено исследование основных технологий доступа к данным для операционной системы Windows. Учитывая жесткие требования к производительности современных систем, главным критерием оценки было быстродействие. В ходе исследования ODBC проявила себя как одна из самых быстродействующих. Эта технология универсальна и до сих пор применяется не только для операционной системы Windows. Быстродействие следующей технологии, ADO, несколько хуже. Развитием ADO, ориентированным на решение проблем, связанных с разработкой веб-систем и устраняющим многие недостатки устаревшей технологии, стало ADO.NET. Несомненно, пока специалисты из Microsoft не придумают что-то новое, именно эта технология станет главной для доступа к данным для операционной системы Windows.

Литература

1. Михаилов С. Сравнение скорости доступа к данным (ADO.NET, ADO, ascDB). // RSDN Magazine. 2003. № 3. С. 3-34.

2. Пирогов В. Ю. Ms SQL Server 2000 управление и программирование. СПб: БХВ -Петербург, 2005. 608 с.

3. Арчер Т., Уайтчепел Э. Visual C++.NET. Библия пользователя. М-СПб-К.: Вильямс, 2005. 1216 с.

4. Кэнту М. Delphi 7. Для профессионалов. СПб: Питер, 2003. 840 с.

5. Фролов А.В., Фролов Г.В. Визуальное проектирование приложений С#. М.: Кудиц -образ, 2003. 512 с.

6. Троэлсен Э. С# и платформа.№Т. СПб: Питер, 2006. 796 с.