2016-12-09 42 views
-1

Я написал функцию, которая выполняет SQL-запрос, и выставил ее в Excel с помощью ExcelDNA. Сам запрос использует SqlDataAdapter и соответствующий ему метод .Fill() для заполнения DataTable.Экспорт результатов SqlDataReader в массив в C#

Я тогда перебирать строки и столбцы DataTable для заполнения 2D массива, который определяется как,

object[,] results = new object[dt.Rows.Count, dt.Columns.Count]; 

можно затем непосредственно вернуть results объект в Excel, и все отображается правильно (строки как строки и числа как значения).

Однако я столкнулся с проблемой, когда определенные SQL-запросы вызывают исключение «Out of Memory» при вызове метода .Fill().

Я провел небольшое исследование и выяснил, что SqlDataReader может быть более эффективным, поскольку мне интересно только получать данные в Excel, и он не будет загружать результирующий набор в память, а скорее читать их подряд за строкой ,

Проблема у меня есть то, что нет .Fill() метод для SqlDataReader. У меня есть некоторый рабочий код, где я могу выводить результаты SQL как CSV-файл. Тогда, я полагаю, я мог бы написать еще одну функцию для импорта CSV в Excel. Но это кажется очень крутым.

Есть ли более простой способ достичь этого?

Полный след следа исключения приведен ниже.

System.Data.SQLite.SQLiteException (0x80004005): out of memory 
out of memory 
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt) 
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt) 
at System.Data.SQLite.SQLiteDataReader.NextResult() 
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave) 
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior) 
at System.Data.SQLite.SQLiteCommand.ExecuteDbDataReader(CommandBehavior behavior) 
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior) 
at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior) 
at System.Data.Common.DbDataAdapter.Fill(DataTable[] dataTables, Int32 startRecord, Int32 maxRecords, IDbCommand command, CommandBehavior behavior) 
at System.Data.Common.DbDataAdapter.Fill(DataTable dataTable) 
at UtilXL.Utils.UtilsSQLite.RunQueryCSLite(String SQLStatement, String FilePath, Boolean IncludeHeaders) in h:\Projects\UtilXL\UtilXL\Utils\UtilsSQLite.cs:line 37 

Строка 37 в ссылке, приведенной выше, является вызовом sda.Fill().

Это трассировки стека при выполнении SqlDataReader,

System.Data.SQLite.SQLiteException (0x80004005): out of memory out of memory 
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt) 
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt) 
at System.Data.SQLite.SQLiteDataReader.NextResult() 
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave) 
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior) 
at System.Data.SQLite.SQLiteCommand.ExecuteReader() 
at UtilXL.Utils.UtilsSQLite.RunQueryCSReader(String SQLStatement, String FilePath, Boolean IncludeHeaders) in h:\Projects\UtilXL\UtilXL\Utils\UtilsSQLite.cs:line 111 
+0

Wouldn 't, что все будет проще, если вы вместо этого добавили данные в Excel через QueryTables.Add()? BTW это помечено как SQL-сервер, хотя это SQLite. –

+0

Спасибо, исправил тег. Я не понимаю, почему заполнение самого DataTable является проблемой, если результатом запроса является всего несколько строк. Не те ли строки, которые отправляются в DataTable? Некоторые запросы, которые возвращают еще много строк (1000+), работают нормально. – insomniac

+0

Где-то (может быть, в другом протекте), вы говорили 18 миллионов строк, а теперь несколько! Вероятно, «несколько» более реалистичны, DataTable вряд ли сможет справиться с такой нагрузкой. Я не видел ваш код и понятия не имею о ваших данных. –

ответ

0

У меня нет опыта работы с ExcelDNA, но ...

Я думаю, вы должны попытаться понять, почему вы получаете в OutOfMemoryException. Это часто случается в 32-битных приложениях, которые используют большой объект (в вашем случае, вероятно, строки).

Это правда, что при использовании SqlDataReader для обработки одной строки за один раз будет использоваться меньше памяти в управляемой куче, чем загрузка всех строк в DataTable. Но тогда вам нужно будет загружать одну строку за раз в Excel - например. из массива, объявленного как:

object[,] results = new object[1, reader.FieldCount]; 

Это, вероятно, будет медленнее, чем загрузка двумерного массива при одном вызове.

+0

Спасибо, Джо. Я не понимаю, почему я получаю исключение памяти. Сам набор результатов довольно мал, около 160 строк и 3 столбца. Полная информация здесь http://stackoverflow.com/questions/41057622/c-sharp-outofmemory-exception-when-returning-a-small-datatable. – insomniac

+0

@insomniac - возможно, ваш код имеет ошибку, вызывающую бесконечную рекурсию. У вас есть трассировка стека для OutOfMemoryException? Вы пробовали переходить через ваш код с помощью отладчика? – Joe

+0

просто добавил трассировку стека. – insomniac

0

Это не ответ, я думаю, но в остальном я не знаю, как отформатировать код.Захваченный базовый код с другого протектора, если вы получили IQToolkit от NuGet, вы могли бы попробовать что-то вроде этого:

void Main() 
{ 
    System.Data.SQLite.SQLiteConnection con = new System.Data.SQLite.SQLiteConnection(@"Data Source="+FilePath); 
    SQLiteQueryProvider provider = new SQLiteQueryProvider(con, new ImplicitMapping(), QueryPolicy.Default); 
    var data = provider.GetTable<MyTable>("MyTable") 
    .Where(mt => mt.Level1 == "M_TO" && mt.AggCode == "C_DTA") 
    .GroupBy(mt => new {mt.Date, mt.CompanyName}) 
    .Select(mt => new 
    { 
     Date = mt.Key.Date 
     CompanyName = mt.Key.CompanyName, 
     Sum = mt.Sum(t => t.Amount) 
    } 
    ).ToList(); 

// Если это удастся попасть сюда, то вы находитесь в хорошей форме

int numRows = data.Count() + (IncludeHeaders ? 1 : 0); 
    object[,] ret = new object[numRows, 3]; 
    // .. 
} 

//// Entity Class 
public class MyTable 
{ 
    public string CompanyName { get; set; } 
    public decimal Amount { get; set; } 
    public DateTime? Date { get; set; } // varchar, really? 
    public string AggCode { get; set; } 
    public string Level1 { get; set; } 
    //... 
    public string Level5 { get; set; } 
    // ... 
    public string Level20 { get; set; } 
}