7

How do I take a DataTable and convert it to a List?

I've included some code below in both C# and VB.NET, the issue with both of these is that we create a new object to return the data, which is very costly. I need to return a reference to the object.

The DataSetNoteProcsTableAdapters.up_GetNoteRow object does implement the INote interface.

I am using ADO.NET, along with .NET 3.5

c# code

public static IList<INote> GetNotes() 
{ 
    DataSetNoteProcsTableAdapters.up_GetNoteTableAdapter adapter =
        new DataSetNoteProcsTableAdapters.up_GetNoteTableAdapter(); 
    DataSetNoteProcs.up_GetNoteDataTable table =
        new DataSetNoteProcs.up_GetNoteDataTable(); 

    IList<INote> notes = new List<INote>(); 

    adapter.Connection = DataAccess.ConnectionSettings.Connection; 
    adapter.Fill(table); 

    foreach (DataSetNoteProcs.up_GetNoteRow t in table) { 
        notes.Add((INote)t); 
    } 

    return notes;
} 

VB.NET Code

Public Shared Function GetNotes() As IList(Of INote)
    Dim adapter As New DataSetNoteProcsTableAdapters.up_GetNoteTableAdapter
    Dim table As New DataSetNoteProcs.up_GetNoteDataTable

    Dim notes As IList(Of INote) = New List(Of INote)

    adapter.Connection = DataAccess.ConnectionSettings.Connection
    adapter.Fill(table)

    For Each t As DataSetNoteProcs.up_GetNoteRow In table
        notes.Add(CType(t, INote))
    Next

    Return notes
End Function
LukeH
  • 263,068
  • 57
  • 365
  • 409
Coppermill
  • 6,676
  • 14
  • 67
  • 92

7 Answers7

6

I have another approach that might be worth taking a look at. It's a helper method. Create a custom class file named CollectionHelper:

    public static IList<T> ConvertTo<T>(DataTable table)
    {
        if (table == null)
            return null;

        List<DataRow> rows = new List<DataRow>();

        foreach (DataRow row in table.Rows)
            rows.Add(row);

        return ConvertTo<T>(rows);
    }

Imagine you want to get a list of customers. Now you'll have the following caller:

List<Customer> myList = (List<Customer>)CollectionHelper.ConvertTo<Customer>(table);

The attributes you have in your DataTable must match your Customer class (fields like Name, Address, Telephone).

I hope it helps!

For who are willing to know why to use lists instead of DataTables: link text

The full sample:

http://lozanotek.com/blog/archive/2007/05/09/Converting_Custom_Collections_To_and_From_DataTable.aspx

confusedandamused
  • 746
  • 2
  • 8
  • 28
Junior Mayhé
  • 16,144
  • 26
  • 115
  • 161
  • I was wandering if it's posible to avoid this "must match" for class attributes and datatable fields! – Junior Mayhé Sep 21 '09 at 22:30
  • I can't get this to compile I get the error: "Error 45 Argument 1: cannot convert from 'System.Collections.Generic.List' to 'System.Data.DataTable – Matthew Lock Mar 27 '17 at 07:33
2

No, creating a list is not costly. Compared to creating the data table and fetching the data from the database, it's very cheap.

You can make it even cheaper by creating the list after populating the table, so that you can set the initial capacity to the number of rows that you will put in it:

IList<INote> notes = new List<INote>(table.Rows.Count);
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • surely you mean IList notes = new List(table.Rows) if you do you get the following error 'Public Sub New(collection AsIEnumerable(Of INote))':implicit conversions from 'DataRowCollection' to 'IEnumerable(Of INote)'. – Coppermill Apr 02 '09 at 15:00
  • 5
    No he means what he wrote. That will cause the list to be set to the size of the results set. – Jake Apr 02 '09 at 15:42
1

Why not pass the DataTable into to the function instead of instantiating it? That would simply contain a reference.

That's way too simple an answer too be worthwhile to you I'm sure, but I don't see how it doesn't solve your problem.

cerhart
  • 381
  • 3
  • 17
1

No sure if this is what your looking for but you could try something like this.

    public class Category
{
    private int _Id;
    public int Id
    {
        get { return _Id; }
        set { _Id = value; }
    }

    private string _Name = null;
    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }

    public Category()
    {}

    public static List<Category> GetCategories()
    {
        List<Category> currentCategories = new List<Category>();

        DbCommand comm = GenericDataAccess.CreateTextCommand();
        comm.CommandText = "SELECT Id, Name FROM Categories Order By Name";
        DataTable table = GenericDataAccess.ExecuteSelectCommand(comm);

        foreach (DataRow row in table.Rows)
        {
            Category cat = new Category();
            cat.Id = int.Parse(row["Id"].ToString());
            cat.Name = row["Name"].ToString();
            currentCategories.Add(cat);
        }
        return currentCategories;
    }
}

This is what I have done so hope this helps. Not sure if it is the right way to do it but it works for what we needed it for.

  • This generates a new object, I need the code to reference the object Category cat = new Category(); Almost like Category cat = (list)tables.Rows But that does not work – Coppermill Apr 02 '09 at 13:45
  • with that you can map which attributes you want to use. that's a bigger approach but not bad – Junior Mayhé Sep 21 '09 at 22:34
0

This is one simple way to do it. Say you have a history of events to pull from the database and you've a class like this:

public class EventHistRecord
{
    public string EvtDate { get; set; }
    public string EvtType { get; set; }
    public string EvtUser { get; set; }
    public string EvtData { get; set; }
}

Then from database you pull event history records in a dataset:

DataSet ds = SqlQueries.getDataSet("select * from EventHist", phoneNum);

And then from the dataset in to a List of EventHistRecord:

    List<EventHistoryRecord> lstRecs = ds.Tables[0].AsEnumerable().Select(
dr => new EventHistoryRecord { EvtDate= dr["EvtDate"].ToString(),
                               EvtData = dr["EvtData"].ToString(),
                               EvtType = dr["EvtType"].ToString(),
                               EvtUser = dr["EvtUser"].ToString() 
                              }).ToList();
Vijay Bansal
  • 737
  • 7
  • 10
0

Do you want to reference the rows of the table? In that case you should use the DataTable.Rows property.

Try this:

notes.AddRange(table.Rows);

If the table rows implement INote then this should work.

Alternatively you could do as you did above:

foreach (INote note in table.Rows)
{
  notes.Add(note)
}
Rune Grimstad
  • 35,612
  • 10
  • 61
  • 76
  • This is on table.Rows Warning 1 Runtime errors might occur when converting 'System.Data.DataRowCollection' to 'System.Collections.Generic.IList(Of.INote)' This is generating a new object, which is what I am trying to avoid – Coppermill Apr 02 '09 at 12:00
  • No, this should not create new objects. All you are doing is casting the object to an other type. – Rune Grimstad Apr 02 '09 at 12:26
-1

If the properties of the list match the field names in the datatable you should be able to create some kind of generic reflection routine.

Craig
  • 36,306
  • 34
  • 114
  • 197