Convert a DataReader into a DataSet

Stefano describes a solution they use in a webservice, needing to convert a data reader to a dataset:

 

Public Function ConvertDataReaderToDataSet(ByVal reader As SqlDataReader) As DataSet

Dim dataSet As DataSet = New DataSet()

Dim schemaTable As DataTable = reader.GetSchemaTable()

Dim dataTable As DataTable = New DataTable()

Dim intCounter As Integer

For intCounter = 0 To schemaTable.Rows.Count - 1

Dim dataRow As DataRow = schemaTable.Rows(intCounter)

Dim columnName As String = CType(dataRow("ColumnName"), String)

Dim column As DataColumn = New DataColumn(columnName, _

CType(dataRow("DataType"), Type))

dataTable.Columns.Add(column)

Next

dataSet.Tables.Add(dataTable)

While reader.Read()

Dim dataRow As DataRow = dataTable.NewRow()

For intCounter = 0 To reader.FieldCount - 1

dataRow(intCounter) = reader.GetValue(intCounter)

Next

dataTable.Rows.Add(dataRow)

End While

Return dataSet

End Function

 

While I can see the benefit in this, it seems as though this can create very high perf issues. GetSchemaTable is another round trip to the database and its a costly one. Create a DataTable on the file is also kinda costly. A better solution might be to incorporate a sort of caching mechanism. Remember SqlHelper from the data application block?  It uses a caching mechanism for sproc parameter arrays so that sproc params are "discovered" only once and saved for later use. Same thing could be done here. Caching an array of columns should be done using some sort of key, but, what key can be used here? the reader has no "commandText" property that can be used. What do you think?

Published Thursday, January 22, 2004 11:55 AM by RoyOsherove
Filed under:

Comments

Thursday, January 22, 2004 8:58 AM by Me

# re: Convert a DataReader into a DataSet

Looks like it's taken word for word from this article published a while ago:
http://www.wwwcoder.com/main/parentid/273/site/2114/68/default.aspx
Thursday, January 22, 2004 9:19 AM by AndrewSeven

# re: Convert a DataReader into a DataSet

The dataset will use a reader internaly, so why not examine the logic inside the dataset?

Could a dataAdapter do this directly?
(Using the SqlHelper has reduced my awareness of dataccess classes)
Thursday, January 22, 2004 9:53 AM by Teemu Keiski

# re: Convert a DataReader into a DataSet

Just a quick shot (not tested, typed from my head):

public static DataSet ConvertDataReadertoDataSet(IDataReader reader)
{
DataSet ds=new DataSet();
DataTable dataTable=new DataTable();

DataTable schemaTable=reader.GetSchemaTable();

DataRow row;
string columnName;
DataColumn column;

int count=schemaTable.Rows.Count;

for(int i=0;i<count;i++)
{
row=schemaTable.Rows[i];
columnName=(string)row["ColumnName"];
column=new DataColumn(columnName,(Type)row["DataType"]);
dataTable.Columns.Add(column);
}
ds.Tables.Add(dataTable);

object[] values=new object[count];

try
{
dataTable.BeginLoadData();
while(reader.Read())
{
reader.GetValues(values);
dataTable.LoadDataRow(values,true);
}

}
finally
{
dataTable.EndLoadData();
reader.Close();
}

return ds;
}
Thursday, January 22, 2004 12:07 PM by Diego Mijelshon

# another approach

Why GetSchemaTable?

Since IDataReader is also IDataRecord, it could be:

static DataSet DataReaderToDataSet(IDataReader reader)
{
DataTable table = new DataTable();
int fieldCount = reader.FieldCount;
for (int i = 0; i < fieldCount; i++)
{
table.Columns.Add(reader.GetName(i), reader.GetFieldType(i));
}
table.BeginLoadData();
object[] values = new object[fieldCount];
while (reader.Read())
{
reader.GetValues(values);
table.LoadDataRow(values, true);
}
table.EndLoadData();
DataSet ds = new DataSet();
ds.Tables.Add(table);
return ds;
}
Thursday, January 22, 2004 7:42 PM by Josh

# I don't know why there isnt an article about this

I don't remember all the specifics, but enough to get a working version.

Basically, the DbDataAdapter class (which all provider specific DataAdapters derive from) already contains a protected Fill overload that takes an IDataReader as a parameter.

So, create a custom class that derives from DbDataAdapter, and expose that functionality with a public method, as follows:

public class DataReaderAdapter : DbDataAdapter {
public int FillFromReader(DataTable dataTable, IDataReader dataReader){
return this.Fill(dataTable, dataReader);
}
}

(You are also required to override 4 events. I left them out of this example for brevity. My implementation didnt include any code in the events, just the base implementation provided by VS.NET 2003.)

Now, in your code, it is as easy as:

SqlDataReader dr = cmd.ExecuteReader();
DataSet ds = new DataSet();
DataSet dt = new DataTable();
DataReaderAdapter da = new DataReaderAdapter();
da.FillFromReader(dt, dr);
ds.Tables.Add(dt)

Hurray! You will find your dataset now contains a table with all of the data from the datareader. No messing with schemas, loops, etc. Use the functionality already built into the Framework.
Thursday, January 22, 2004 9:09 PM by Steve Maine

# re: Convert a DataReader into a DataSet

I find that I often want to go the other way (i.e., convert a DataSet into a DataReader). We all know that it's best to use DataReaders when possible for performance reasons, but it's really hard to create a mock data layer with DataReaders (it's very hard to stub them out).

One of the guys on my team came up with a DataSetDataReader, which was an implementation of IDataReader whose underlying stream was a DataSet. This solved our problem -- for unit tests, we could create a DataSet of "dummy" data to represent our mock data layer, wrap a DataSetDataReader around and return it to the business logic layer with noone being the wiser. It worked quite well, and helped us unit test our BL without introducing a dependancy on the database.

I wonder where the code for that got to...
Thursday, January 22, 2004 10:09 PM by TrackBack

# Good stuff on DataReader-

Thursday, January 22, 2004 10:45 PM by TrackBack

# Signal-to-Noise #1

Friday, January 23, 2004 1:33 AM by Teemu Keiski

# re: Convert a DataReader into a DataSet

Diego,

ah yes, how obvious, thanks. :-) I though that it was still too complicated.
And for the others, damn that I love reading these blogs, so much good solutions so quickly. :-)

I didn't even realize that DBDataAdapter has that overload for Fill method (can't just remeber everything and yes it was in docs, but can't just read everything :-D ).

Friday, January 23, 2004 5:05 AM by Roy Osherove

# re: Convert a DataReader into a DataSet

Steve: hey, if you posted that code somewhere that would be great! Please find it :)
Friday, January 23, 2004 11:26 AM by Pete McKinney

# re: Convert a DataReader into a DataSet

Here's some code I recently put together to expose an IDataReader interface from a DataTable. The GetSchemaTable method is pretty incomplete, but it has all the functionality that I've needed from it.

#region references
using System;
using System.Data;
using System.Diagnostics;
using System.Text;
#endregion

namespace Common.SQL
{
public class DataTableReader : IDataReader
{
private const int NotSet = -1;
private DataTable _table;
private int _currentRow = NotSet;
private DataRow _row;
private bool _closed = false;

public DataTableReader( DataTable table ) {
_table = table;
}

#region IDataReader Members

public int RecordsAffected {
get { return -1; }
}

public bool IsClosed {
get { return _closed; }
}

public bool NextResult() {
Close();
return false;
}

public void Close() {
_closed = true;
_currentRow = NotSet;
_row = null;
_table.Dispose();
}

public bool Read() {
_currentRow++;
if( _currentRow < _table.Rows.Count ) {
_row = _table.Rows[_currentRow];
return true;
}

//passed the end, clean up
Close();
return false;
}

public int Depth {
get {
//we don't have any depth
return 0;
}
}

/// <summary>
/// Note: this makes no real attempt to get all the functionality in there
/// </summary>
/// <returns></returns>
public DataTable GetSchemaTable() {

DataTable schema = new DataTable( "Schema" );
schema.Columns.Add( "ColumnName" );
schema.Columns.Add( "ColumnOrdinal", typeof(int) );
schema.Columns.Add( "ColumnSize", typeof(int) );
schema.Columns.Add( "NumericPrecision", typeof(int) );
schema.Columns.Add( "NumericScale", typeof(int) );
schema.Columns.Add( "DataType", typeof(Type) );
schema.Columns.Add( "ProviderType", typeof(Type) );
schema.Columns.Add( "IsLong", typeof(bool) );
schema.Columns.Add( "AllowDBNull", typeof(bool) );
schema.Columns.Add( "IsReadOnly", typeof(bool) );
schema.Columns.Add( "IsRowVersion", typeof(bool) );
schema.Columns.Add( "IsUnique", typeof(bool) );
schema.Columns.Add( "IsKeyColumn", typeof(bool) );
schema.Columns.Add( "IsAutoIncrement", typeof(bool) );
schema.Columns.Add( "BaseSchemaName" );
schema.Columns.Add( "BaseCatalogName" );
schema.Columns.Add( "BaseTableName" );
schema.Columns.Add( "BaseColumnName" );

foreach( DataColumn col in _table.Columns ) {
DataRow colRow = schema.NewRow();
colRow["ColumnName"] = col.ColumnName;
colRow["ColumnOrdinal"] = col.Ordinal; //col.Ordinal;
colRow["ColumnSize"] = col.MaxLength; //col.ColumnSize;
colRow["NumericPrecision"] = null; //col.NumericPrecision;
colRow["NumericScale"] = null; //col.NumericScale;
colRow["DataType"] = col.DataType;
colRow["ProviderType"] = col.DataType; //col.ProviderType;
colRow["IsLong"] = null; //col.IsLong;
colRow["AllowDBNull"] = col.AllowDBNull;
colRow["IsReadOnly"] = true; //col.IsReadOnly;
colRow["IsRowVersion"] = false; //col.IsRowVersion;
colRow["IsUnique"] = false; //col.IsUnique;
colRow["IsKeyColumn"] = false; //col.IsKeyColumn;
colRow["IsAutoIncrement"] = false; //col.IsAutoIncrement;
colRow["BaseSchemaName"] = null; //col.BaseSchemaName;
colRow["BaseCatalogName"] = null; //col.BaseCatalogName;
colRow["BaseTableName"] = null; //col.BaseTableName;
colRow["BaseColumnName"] = null; //col.BaseColumnName;
schema.Rows.Add( colRow );
}
return schema;
}

#endregion

#region IDisposable Members

public void Dispose() {
Close();
}

#endregion

#region IDataRecord Members

public int GetInt32(int i) {
return Convert.ToInt32( _row );
}

public object this[string name] {
get {
try {
return _row[name];
} catch( ArgumentException err ) {
throw new IndexOutOfRangeException( err.Message, err );
}
}
}

object System.Data.IDataRecord.this[int i] {
get {
return _row[i];
}
}

public object GetValue(int i) {
return _row[i];
}

public bool IsDBNull(int i) {
return _row[i] == DBNull.Value;
}

public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
throw new ApplicationException( "Not implemented" );
}

public byte GetByte(int i) {
return Convert.ToByte( _row[i] );
}

public Type GetFieldType(int i) {
return _row[i].GetType();
}

public decimal GetDecimal(int i) {
return Convert.ToDecimal( _row[i] );
}

public int GetValues(object[] values) {
int count = Math.Min( values.Length, _row.ItemArray.Length );
for( int i = 0; i < count; i++ )
values[i] = _row[i];

return _row.ItemArray.Length;
}

public string GetName(int i) {
return _table.Columns[i].ColumnName;
}

public int FieldCount {
get {
return _row.ItemArray.Length;
}
}

public long GetInt64(int i) {
return Convert.ToInt64( _row[i] );
}

public double GetDouble(int i) {
return Convert.ToDouble( _row[i] );
}

public bool GetBoolean(int i) {
return Convert.ToBoolean( _row[i] );
}

public Guid GetGuid(int i) {
return (Guid)_row[i];
}

public DateTime GetDateTime(int i) {
return Convert.ToDateTime( _row[i] );
}

public int GetOrdinal(string name) {
return _table.Columns[name].Ordinal;
}

public string GetDataTypeName(int i) {
return _table.Columns[i].DataType.Name;
}

public float GetFloat(int i) {
return (float)Convert.ToDouble( _row[i] );
}

public IDataReader GetData(int i) {
return null;
}

public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) {
char[] chars = _row[i].ToString().ToCharArray( Convert.ToInt32( fieldoffset ), length );
length = Math.Min( chars.Length, length );
for( int j = 0; j < length; j++ )
buffer[j+bufferoffset] = chars[j];
return length;
}

public string GetString(int i) {
return _row[i].ToString();
}

public char GetChar(int i) {
return Convert.ToChar( _row[i] );
}

public short GetInt16(int i) {
return Convert.ToInt16( _row[i] );
}

#endregion
}
}
Wednesday, January 28, 2004 1:38 PM by PA-CA-USA

# re: Convert a DataReader into a DataSet

'''' PA-CA-USA, 28 January 2004, Include this attribution line if you re-use some
'''' or all of this code, please.
'''' This code has been working well for me for some time now. It handles
'''' multiple result sets from a single reader.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Shared Function convertSqlDataReaderToDataSet(ByVal reader As SqlDataReader) As DataSet
Dim ds As DataSet = New DataSet()
Dim schema As DataTable
Dim data As DataTable
Dim i As Integer
Dim dr As DataRow
Dim dc As DataColumn
Dim columnName As String

Do
schema = reader.GetSchemaTable()
data = New DataTable()
If Not schema Is Nothing Then
For i = 0 To schema.Rows.Count - 1
dr = schema.Rows(i)
columnName = dr("ColumnName")
If data.Columns.Contains(columnName) Then
columnName = columnName + "_" + i.ToString()
End If
dc = New DataColumn(columnName, CType(dr("DataType"), Type))
data.Columns.Add(dc)
Next

ds.Tables.Add(data)

While reader.Read()
dr = data.NewRow()
For i = 0 To reader.FieldCount - 1
dr(i) = reader.GetValue(i)
Next
data.Rows.Add(dr)
End While
Else
dc = New DataColumn("RowsAffected")
data.Columns.Add(dc)
ds.Tables.Add(data)
dr = data.NewRow()
dr(0) = reader.RecordsAffected
data.Rows.Add(dr)
End If
Loop While reader.NextResult()

reader.Close()

Return ds
End Function
Saturday, January 31, 2004 9:30 PM by TrackBack

# Signal-to-Noise #1

Monday, February 09, 2004 8:18 AM by TrackBack

# DataReader to DataTable Happy Ending

DataReader to DataTable Happy Ending
Tuesday, March 23, 2004 8:06 AM by TrackBack

# DataReader to DataTable sample code

DataReader to DataTable sample code
Friday, April 30, 2004 10:30 AM by Anatoly Lubarsky

# re: Convert a DataReader into a DataSet

You all are completely out of date.

1) look at ado.net 2.0:
...
DataTable orders = new DataTable("Orders");
conn.Open();
SqlCommand cmd = new SqlCommand("select * from orders");
SqlDataReader r = cmd.ExecuteReader();
orders.Load(r);
...
2) there were similar solutions for this available long time ago


Friday, July 02, 2004 12:26 AM by rishi

# re: Convert a DataReader into a DataSet

Please can anybody tell abt performance issue on Convert a dataReader into a dataset or going old method which is provided by microsoft, like retrive data from database and using data adapter fill the dataset.

Which is faster and more reliable..? please explain.

thanks.
Tuesday, June 06, 2006 7:15 AM by Brijesh Mishra

# How to convert typed Datatable to DataTable

I create XSD in which I create Datatable. when I want assined normal datatable it give error
Monday, September 18, 2006 5:43 PM by wijaya t

# re: Convert a DataReader into a DataSet

public class DataReaderAdapter : DbDataAdapter { public int FillFromReader(DataTable dataTable, IDataReader dataReader) { return this.Fill(dataTable, dataReader); } protected override void OnRowUpdated(System.Data.Common.RowUpdatedEventArgs oValue) { //protected abstract new void OnRowUpdated ( System.Data.Common.RowUpdatedEventArgs value ) //Member of System.Data.Common.DbDataAdapter } protected override void OnRowUpdating(System.Data.Common.RowUpdatingEventArgs oValue) { //protected abstract new void OnRowUpdating ( System.Data.Common.RowUpdatingEventArgs value ) //Member of System.Data.Common.DbDataAdapter } protected override System.Data.Common.RowUpdatingEventArgs CreateRowUpdatingEvent(System.Data.DataRow oDataRow , System.Data.IDbCommand oCommand , System.Data.StatementType oStatementType , System.Data.Common.DataTableMapping oTableMapping) { return null; //protected abstract new System.Data.Common.RowUpdatingEventArgs CreateRowUpdatingEvent ( System.Data.DataRow dataRow , System.Data.IDbCommand command , System.Data.StatementType statementType , System.Data.Common.DataTableMapping tableMapping ) //Member of System.Data.Common.DbDataAdapter } protected override System.Data.Common.RowUpdatedEventArgs CreateRowUpdatedEvent(System.Data.DataRow oDataRow , System.Data.IDbCommand oCommand , System.Data.StatementType oStatementType , System.Data.Common.DataTableMapping oTableMapping) { return null; //protected abstract new System.Data.Common.RowUpdatedEventArgs CreateRowUpdatedEvent ( System.Data.DataRow dataRow , System.Data.IDbCommand command , System.Data.StatementType statementType , System.Data.Common.DataTableMapping tableMapping ) //Member of System.Data.Common.DbDataAdapter } }
Tuesday, January 30, 2007 8:39 AM by Dennis van der Stelt

# re: Convert a DataReader into a DataSet

Wow Pete McKinney, your DataReader works! :)

Just one small bug in the GetInt32 method where you want to convert the entire row into an integer. Use row[i] and you're done!

I'm using this to stub out a DataReader that I've copied from CSLA. I've made my own implementation from it, but wanted to test it. As it requires another IDataReader as parameter in its constructor, I had to stub that one out. So I used your implementation and now I can test my own datareader implementation!

Things like these make programming fun! ;)

Tuesday, January 30, 2007 8:39 AM by Dennis van der Stelt

# re: Convert a DataReader into a DataSet

Wow Pete McKinney, your DataReader works! :)

Just one small bug in the GetInt32 method where you want to convert the entire row into an integer. Use row[i] and you're done!

I'm using this to stub out a DataReader that I've copied from CSLA. I've made my own implementation from it, but wanted to test it. As it requires another IDataReader as parameter in its constructor, I had to stub that one out. So I used your implementation and now I can test my own datareader implementation!

Things like these make programming fun! ;)

Tuesday, February 20, 2007 1:10 AM by Pradeep

# re: Convert a DataReader into a DataSet

I have 65 lack records to be read.When  I loop through the records it is taking almost 1 minutes for 30,000 records.

How i can minimize this time.To read the data into data reader it is taking only 30 seconds.