Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

How to Manually Create a Typed DataTable

Rate me:
Please Sign up or sign in to vote.
4.97/5 (52 votes)
28 Oct 2008CPOL6 min read 260.9K   3.3K   149   30
When you override GetRowType, you also need to override NewRowFromBuilder().

Introduction

I am writing this article because there is not a lot of information on the web about using the DataTable.GetRowType() method, and the code examples that I found were plain wrong or incomplete. Furthermore, there doesn't appear to be any automated tools for creating just a typed DataTable--instead, there are tools for creating a typed DataSet. In the end, I ended up creating a typed DataSet simply to figure out what I was doing wrong with my manually created typed DataTable. So, this is a beginner article on what I learned, and the purpose is to provide an example and correct information as resource for others. I don't provide a tool for creating a type DataTable, that might be for a future article.

What is a Typed DataTable?

A typed DataTable lets you create a specific DataTable, already initialized with the required columns, constraints, and so forth. A typed DataTable typically also uses a typed DataRow, which lets you access fields through their property names. So, instead of:

C#
DataTable personTable=new DataTable();
personTable.Columns.Add(new DataColumn("LastName"));
personTable.Columns.Add(new DataColumn("FirstName"));

DataRow row=personTable.NewRow();
row["LastName"]="Clifton";
row["FirstName"]="Marc";

using a typed DataTable would look something like this:

C#
PersonTable personTable=new PersonTable();
PersonRow row=personTable.GetNewRow();
row.LastName="Clifton";
row.FirstName="Marc";

The advantage of a typed DataTable is the same as with a typed DataSet: you have a strongly typed DataTable and DataRow, and you are using properties instead of strings to set/get values in a row. Furthermore, by using a typed DataRow, the field value, which in a DataRow is an object, can instead be already cast to the correct type in the property getter. This improves code readability, and eliminates the chances of improper construction and typos in the field names.

Creating the Typed DataTable

To create a typed DataTable, create your own class derived from DataTable. For example:

C#
public class PersonTable : DataTable
{
}

There are two methods that you need to override: GetRowType() and NewRowFromBuilder(). The point of this article is really that it took me about four hours to find out that I needed to override the second method.

C#
protected override Type GetRowType()
{
  return typeof(PersonRow);
}

protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
{
  return new PersonRow(builder);
}

That second method is vital. If you don't provide it, you will get an exception concerning "array type mismatch" when attempting to create a new row. It took me hours to figure that out!

Creating the Typed DataRow

Next, you need a typed DataRow to define the PersonRow type referenced above.

C#
public class PersonRow : DataRow
{
}

Constructor

The constructor parameters, given the NewRowFromBuilder call above, are obvious, but what is less obvious is that the constructor must be marked protected or internal, because the DataRow constructor is marked internal.

C#
public class PersonRow : DataRow
{
  internal PersonRow(DataRowBuilder builder) : base(builder)
  {
  }
}

Filling in the Details

Next, I'll show the basics for both the typed DataTable and DataRow. The purpose of these methods and properties is to utilize the typed DataRow to avoid casting in the code that requires the DataTable.

PersonTable Methods

Constructor

In the constructor, we can add the columns and constraints that define the table.

C#
public class PersonTable : DataTable
{
  public PersonTable()
  {
    Columns.Add(new DataColumn("LastName", typeof(string)));
    Columns.Add(new DataColumn("FirstName", typeof(string)));
  }
}

The above is a trivial example, which doesn't illustrate creating a primary key, setting constraints on the fields, and so forth.

Indexer

You can implement an indexer that returns the typed DataRow:

C#
public PersonRow this[int idx]
{
  get { return (PersonRow)Rows[idx]; }
}

The indexer is implemented on the typed DataTable because we can't override the indexer on the Rows property. Bounds checking can be left to the .NET framework's Rows property. The typical usage for a non-typed DataRow would look like this:

C#
DataRow row=someTable.Rows[n];

whereas the indexer for the type DataRow would look like this:

C#
PersonRow row=personTable[n];

Not ideal, as it looks like I'm indexing an array of tables. An alternative would be to implement a property, perhaps, named PersonRows; however, this would require implementing a PersonRowsCollection and copying the Rows collection to the typed collection, which would most likely be a significant performance hit every time we index the Rows collection. This is even less ideal!

Add

The Add method should accept the typed DataRow. This protects us from adding a row to a different table. If you try to do that with a non-typed DataTable, you get an error at runtime. The advantage of a typed Add method is that you will get a compiler error, rather than a runtime error.

C#
public void Add(PersonRow row)
{
  Rows.Add(row);
}

Remove

A typed Remove method has the same advantages as the typed Add method above:

C#
public void Remove(PersonRow row)
{
  Rows.Remove(row);
}

GetNewRow

Here, we end up with a conflict if we try to use the DataTable.NewRow() method, because the only thing different is the return type, not the method signature (parameters). So, we could write:

C#
public new PersonRow NewRow()
{
  PersonRow row = (PersonRow)NewRow();

  return row;
}

However, I am personally against using the "new" keyword to override the behavior of a base class. So, I prefer a different method name all together:

C#
public PersonRow GetNewRow()
{
  PersonRow row = (PersonRow)NewRow();

  return row;
}

PersonRow Properties

The typed DataRow should include properties for the columns defined in the PersonTable constructor:

C#
public string LastName
{
  get {return (string)base["LastName"];}
  set {base["LastName"]=value;}
}

public string FirstName
{
  get {return (string)base["FirstName"];}
  set {base["FirstName"]=value;}
}

The advantage here is that we have property names (any typo results in a compiler error), we can utilize Intellisense, and we can convert the object type here instead of in the application. Furthermore, we could add validation and property changed events if we wanted to. This might also be a good place to deal with DBNull to/from null conversions, and if we use nullable types, we can add further intelligence to the property getters/setters.

PersonRow Constructor

You may want to initialize the fields in the constructor:

C#
public class PersonRow : DataRow
{
  internal PersonRow(DataRowBuilder builder) : base(builder)
  {
    LastName=String.Empty;
    FirstName=String.Empty;
  }
}

Row Events

If necessary, you may want to implement typed row events. The typical row events are:

  • ColumnChanged
  • ColumnChanging
  • RowChanged
  • RowChanging
  • RowDeleted
  • RowDeleting

We'll look at one of these events, RowChanged, to illustrate a typed event.

Defining the Delegate

First, we need a delegate of the appropriate type:

C#
public delegate void PersonRowChangedDlgt(PersonTable sender, 
                     PersonRowChangedEventArgs args);

Note that this delegate defines typed parameters.

The Event

We can now add the event to the PersonTable class:

C#
public event PersonRowChangedDlgt PersonRowChanged;

Defining the Event Argument Class

We also need a typed event argument class because we want to use our typed PersonRow:

C#
public class PersonRowChangedEventArgs
{
  protected DataRowAction action;
  protected PersonRow row;

  public DataRowAction Action
  {
    get { return action; }
  }

  public PersonRow Row
  {
    get { return row; }
  }

  public PersonRowChangedEventArgs(DataRowAction action, PersonRow row)
  {
    this.action = action;
    this.row = row;
  }
}

Overriding the OnRowChanged Method

Rather than add a RowChanged event handler, we can override the OnRowChanged method and create a similar pattern for the new method OnPersonRowChanged. Note that we still call the base DataTable implementation for RowChanged. These methods are added to the PersonDataTable class.

C#
protected override void OnRowChanged(DataRowChangeEventArgs e)
{
  base.OnRowChanged(e);
  PersonRowChangedEventArgs args = 
    new PersonRowChangedEventArgs(e.Action, (PersonRow)e.Row);
  OnPersonRowChanged(args); 
}

protected virtual void OnPersonRowChanged(PersonRowChangedEventArgs args)
{
  if (PersonRowChanged != null)
  {
    PersonRowChanged(this, args);
  }
}

Note that the above method is virtual, as this is the pattern for how events are raised in the .NET framework, and it's good to be consistent with this pattern.

Now, that's a lot of work to add just one typed event, so you can see that having a code generator would be really helpful.

Using the Event

Here's a silly example to illustrate using the typed DataTable and the event:

C#
class Program
{
  static void Main(string[] args)
  {
    PersonTable table = new PersonTable();
    table.PersonRowChanged += new PersonRowChangedDlgt(OnPersonRowChanged);
    PersonRow row = table.GetNewRow();
    table.Add(row);
  }

  static void OnPersonRowChanged(PersonTable sender, PersonRowChangedEventArgs args)
  {
    // This is silly example only for the purposes of illustrating using typed events.
    // Do not do this in real applications, because you would never use this Changed event
    // to validate row fields!
    if (args.Row.LastName != String.Empty)
    {
      throw new ApplicationException("The row did not initialize " + 
                "to an empty string for the LastName field.");
    }
  }
}

This, however, illustrates the beauty of a typed DataTable and a typed DataRow: readability, and compiler checking of proper usage.

Conclusion

Hopefully, this article clearly illustrates how to create a typed DataTable manually. The "discovery" that I made (that I couldn't find anywhere else on the Internet) is that, when you override GetRowType(), you also need to override NewRowFromBuilder().

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
Questionfill data into that datatable Pin
Ameer Adel Al-Zubaidy28-Nov-15 23:12
professionalAmeer Adel Al-Zubaidy28-Nov-15 23:12 
Questiondatarow.item() Pin
polychromenz23-Jul-14 20:59
polychromenz23-Jul-14 20:59 
QuestionCustom Dataset Pin
joejop3-Feb-14 7:20
joejop3-Feb-14 7:20 
AnswerRe: Custom Dataset Pin
Marc Clifton3-Feb-14 7:33
mvaMarc Clifton3-Feb-14 7:33 
QuestionI'm missing something Pin
RosieC21-Jan-13 4:22
RosieC21-Jan-13 4:22 
AnswerRe: I'm missing something Pin
joejop3-Feb-14 7:34
joejop3-Feb-14 7:34 
GeneralRe: I'm missing something Pin
RosieC19-Feb-14 4:45
RosieC19-Feb-14 4:45 
GeneralSuggestion for Primary Key definition Pin
Guttorm Haaversen26-Oct-12 13:07
Guttorm Haaversen26-Oct-12 13:07 
GeneralMy vote of 5 Pin
Kanasz Robert26-Sep-12 7:39
professionalKanasz Robert26-Sep-12 7:39 
QuestionInstead I'd rather have the ide do the chore for me, I just drag and drop and make a few edit, much quicker. Pin
J Xie10-Oct-11 16:28
J Xie10-Oct-11 16:28 
AnswerRe: Instead I'd rather have the ide do the chore for me, I just drag and drop and make a few edit, much quicker. Pin
Marc Clifton11-Oct-11 1:05
mvaMarc Clifton11-Oct-11 1:05 
GeneralMy vote of 5 Pin
Kanasz Robert13-Nov-10 6:21
professionalKanasz Robert13-Nov-10 6:21 
Very good article and very very interesting idea.
GeneralNote about the NewRow() Method Pin
hybridguy6-Oct-09 7:34
hybridguy6-Oct-09 7:34 
GeneralRe: Note about the NewRow() Method Pin
Dan Neely1-Dec-09 8:46
Dan Neely1-Dec-09 8:46 
GeneralI'd give you a 6 if I could! Pin
torial10-Dec-08 6:32
torial10-Dec-08 6:32 
GeneralIsNull Pin
Y_R4-Nov-08 21:45
Y_R4-Nov-08 21:45 
GeneralRe: IsNull Pin
Marc Clifton5-Nov-08 1:43
mvaMarc Clifton5-Nov-08 1:43 
GeneralRe: IsNull Pin
Y_R5-Nov-08 9:01
Y_R5-Nov-08 9:01 
GeneralUseful. Pin
Rajesh Pillai29-Oct-08 19:05
Rajesh Pillai29-Oct-08 19:05 
QuestionWhy not build the Person table with xml? Pin
Paul Brower29-Oct-08 1:23
Paul Brower29-Oct-08 1:23 
AnswerRe: Why not build the Person table with xml? Pin
Marc Clifton29-Oct-08 2:14
mvaMarc Clifton29-Oct-08 2:14 
GeneralRe: Why not build the Person table with xml? Pin
Paul Brower29-Oct-08 2:57
Paul Brower29-Oct-08 2:57 
GeneralRe: Why not build the Person table with xml? Pin
Marc Clifton29-Oct-08 4:18
mvaMarc Clifton29-Oct-08 4:18 
GeneralRe: Why not build the Person table with xml? Pin
Paul Brower29-Oct-08 4:35
Paul Brower29-Oct-08 4:35 
GeneralRe: Why not build the Person table with xml? Pin
maruf_d3-Nov-08 20:03
maruf_d3-Nov-08 20:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.