Я обнаружил, что использование DAO определенным образом примерно в 30 раз быстрее, чем использование ADO.NET.Я делюсь кодом и результатом в этом ответе.В качестве фона, ниже, тест должен выписать 100 000 записей таблицы с 20 столбцами.
Краткое описание техники и времени - от лучшего к худшему:
- 02,8 секунд: Используйте DAO, используйте
DAO.Field
для ссылки на столбцы таблицы - 02,8 секунд: Запишите в текстовый файл, используйте Automation дляимпортировать текст в Access
- 11,0 секунд: Использовать DAO, использовать индекс столбца для ссылки на столбцы таблицы.
- 17,0 секунд: ИспользоватьDAO, обратитесь к столбцу с именем
- 79,0 секунд: Использовать ADO.NET, генерировать операторы INSERT для каждой строки
- 86,0 секунд: ИспользоватьADO.NET, используйте DataTable для DataAdapter для «пакетной» вставки
В качестве фона, иногда мне нужно провести анализ достаточно больших объемов данных, и я считаю, что Access - лучшая платформа.Анализ включает в себя много запросов и часто много кода VBA.
По различным причинам я хотел использовать C # вместо VBA.Типичный способ - использовать OleDB для подключения к Access.Я использовал OleDbDataReader
, чтобы получить миллионы записей, и это работало довольно хорошо.Но при выводе результатов в таблицу это заняло много времени.Более часа.
Сначала давайте обсудим два типичных способа записи записей в Access из C #.Оба способа связаны с OleDB и ADO.NET.Первый - генерировать операторы INSERT по одному и выполнять их, затрачивая на 100 000 записей 79 секунд.Код:
public static double TestADONET_Insert_TransferToAccess()
{
StringBuilder names = new StringBuilder();
for (int k = 0; k < 20; k++)
{
string fieldName = "Field" + (k + 1).ToString();
if (k > 0)
{
names.Append(",");
}
names.Append(fieldName);
}
DateTime start = DateTime.Now;
using (OleDbConnection conn = new OleDbConnection(Properties.Settings.Default.AccessDB))
{
conn.Open();
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
cmd.CommandText = "DELETE FROM TEMP";
int numRowsDeleted = cmd.ExecuteNonQuery();
Console.WriteLine("Deleted {0} rows from TEMP", numRowsDeleted);
for (int i = 0; i < 100000; i++)
{
StringBuilder insertSQL = new StringBuilder("INSERT INTO TEMP (")
.Append(names)
.Append(") VALUES (");
for (int k = 0; k < 19; k++)
{
insertSQL.Append(i + k).Append(",");
}
insertSQL.Append(i + 19).Append(")");
cmd.CommandText = insertSQL.ToString();
cmd.ExecuteNonQuery();
}
cmd.Dispose();
}
double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
return elapsedTimeInSeconds;
}
Обратите внимание, что я не нашел в Access никакого метода, позволяющего выполнять массовую вставку.
Тогда я подумал, что, возможно, использование таблицы данных с адаптером данных будет доказанополезно.Тем более, что я думал, что смогу делать пакетные вставки, используя свойство UpdateBatchSize
адаптера данных.Однако, по-видимому, только SQL Server и Oracle поддерживают это, а Access нет.И это заняло самое большое время - 86 секунд.Код, который я использовал, был:
public static double TestADONET_DataTable_TransferToAccess()
{
StringBuilder names = new StringBuilder();
StringBuilder values = new StringBuilder();
DataTable dt = new DataTable("TEMP");
for (int k = 0; k < 20; k++)
{
string fieldName = "Field" + (k + 1).ToString();
dt.Columns.Add(fieldName, typeof(int));
if (k > 0)
{
names.Append(",");
values.Append(",");
}
names.Append(fieldName);
values.Append("@" + fieldName);
}
DateTime start = DateTime.Now;
OleDbConnection conn = new OleDbConnection(Properties.Settings.Default.AccessDB);
conn.Open();
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
cmd.CommandText = "DELETE FROM TEMP";
int numRowsDeleted = cmd.ExecuteNonQuery();
Console.WriteLine("Deleted {0} rows from TEMP", numRowsDeleted);
OleDbDataAdapter da = new OleDbDataAdapter("SELECT * FROM TEMP", conn);
da.InsertCommand = new OleDbCommand("INSERT INTO TEMP (" + names.ToString() + ") VALUES (" + values.ToString() + ")");
for (int k = 0; k < 20; k++)
{
string fieldName = "Field" + (k + 1).ToString();
da.InsertCommand.Parameters.Add("@" + fieldName, OleDbType.Integer, 4, fieldName);
}
da.InsertCommand.UpdatedRowSource = UpdateRowSource.None;
da.InsertCommand.Connection = conn;
//da.UpdateBatchSize = 0;
for (int i = 0; i < 100000; i++)
{
DataRow dr = dt.NewRow();
for (int k = 0; k < 20; k++)
{
dr["Field" + (k + 1).ToString()] = i + k;
}
dt.Rows.Add(dr);
}
da.Update(dt);
conn.Close();
double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
return elapsedTimeInSeconds;
}
Затем я попробовал нестандартные способы.Сначала я записал в текстовый файл, а затем использовал Automation для его импорта. Это было быстро - 2,8 секунды - и привязано к первому месту.Но я считаю это хрупким по ряду причин: Выводить поля даты сложно.Мне пришлось специально отформатировать их (someDate.ToString("yyyy-MM-dd HH:mm")
), а затем настроить специальную «спецификацию импорта», которая кодирует в этом формате.В спецификации импорта также должен быть установлен правильный разделитель «кавычек».В приведенном ниже примере, только с целочисленными полями, в спецификации импорта не было необходимости.
Текстовые файлы также хрупки для "интернационализации", где используются запятые для десятичных разделителей, возможны разные форматы датыиспользование unicode.
Обратите внимание, что первая запись содержит имена полей, чтобы порядок столбцов не зависел от таблицы, и что мы использовали Automation для фактического импорта текстового файла.
public static double TestTextTransferToAccess()
{
StringBuilder names = new StringBuilder();
for (int k = 0; k < 20; k++)
{
string fieldName = "Field" + (k + 1).ToString();
if (k > 0)
{
names.Append(",");
}
names.Append(fieldName);
}
DateTime start = DateTime.Now;
StreamWriter sw = new StreamWriter(Properties.Settings.Default.TEMPPathLocation);
sw.WriteLine(names);
for (int i = 0; i < 100000; i++)
{
for (int k = 0; k < 19; k++)
{
sw.Write(i + k);
sw.Write(",");
}
sw.WriteLine(i + 19);
}
sw.Close();
ACCESS.Application accApplication = new ACCESS.Application();
string databaseName = Properties.Settings.Default.AccessDB
.Split(new char[] { ';' }).First(s => s.StartsWith("Data Source=")).Substring(12);
accApplication.OpenCurrentDatabase(databaseName, false, "");
accApplication.DoCmd.RunSQL("DELETE FROM TEMP");
accApplication.DoCmd.TransferText(TransferType: ACCESS.AcTextTransferType.acImportDelim,
TableName: "TEMP",
FileName: Properties.Settings.Default.TEMPPathLocation,
HasFieldNames: true);
accApplication.CloseCurrentDatabase();
accApplication.Quit();
accApplication = null;
double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
return elapsedTimeInSeconds;
}
Наконец я попробовал DAO.Многие сайты там дают огромные предупреждения об использовании DAO.Однако оказывается, что это просто лучший способ взаимодействия между Access и .NET, особенно когда вам нужно выписать большое количество записей.Кроме того, он дает доступ ко всем свойствам таблицы.Я где-то читал, что проще всего программировать транзакции, используя DAO вместо ADO.NET.
Обратите внимание, что есть несколько строк кода, которые комментируются.Они скоро будут объяснены.
public static double TestDAOTransferToAccess()
{
string databaseName = Properties.Settings.Default.AccessDB
.Split(new char[] { ';' }).First(s => s.StartsWith("Data Source=")).Substring(12);
DateTime start = DateTime.Now;
DAO.DBEngine dbEngine = new DAO.DBEngine();
DAO.Database db = dbEngine.OpenDatabase(databaseName);
db.Execute("DELETE FROM TEMP");
DAO.Recordset rs = db.OpenRecordset("TEMP");
DAO.Field[] myFields = new DAO.Field[20];
for (int k = 0; k < 20; k++) myFields[k] = rs.Fields["Field" + (k + 1).ToString()];
//dbEngine.BeginTrans();
for (int i = 0; i < 100000; i++)
{
rs.AddNew();
for (int k = 0; k < 20; k++)
{
//rs.Fields[k].Value = i + k;
myFields[k].Value = i + k;
//rs.Fields["Field" + (k + 1).ToString()].Value = i + k;
}
rs.Update();
//if (0 == i % 5000)
//{
//dbEngine.CommitTrans();
//dbEngine.BeginTrans();
//}
}
//dbEngine.CommitTrans();
rs.Close();
db.Close();
double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
return elapsedTimeInSeconds;
}
В этом коде мы создали переменные DAO.Field для каждого столбца (myFields[k]
) и затем использовали их. Это заняло 2,8 секунды. Кроме того, можно напрямую получить доступ к этим полям, как указано в закомментированной строке rs.Fields["Field" + (k + 1).ToString()].Value = i + k;
, что увеличило время до 17 секунд. Свертывание кода в транзакции (см. Закомментированные строки) уменьшило его до 14 секунд. Использование целочисленного индекса rs.Fields[k].Value = i + k;
уменьшило значение до 11 секунд. Использование DAO.Field (myFields[k]
) и транзакция фактически заняли больше времени, увеличив время до 3,1 секунды.
Наконец, для полноты, весь этот код был в простом статическом классе, а операторы using
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ACCESS = Microsoft.Office.Interop.Access; // USED ONLY FOR THE TEXT FILE METHOD
using DAO = Microsoft.Office.Interop.Access.Dao; // USED ONLY FOR THE DAO METHOD
using System.Data; // USED ONLY FOR THE ADO.NET/DataTable METHOD
using System.Data.OleDb; // USED FOR BOTH ADO.NET METHODS
using System.IO; // USED ONLY FOR THE TEXT FILE METHOD