Search code examples
c#strongly-typed-dataset

Handcrafted Strongly Typed ADO.net DataTable - Can it be cleaner?


I have recently come across a very simple Typed DataTable (without using a .XSD)(I've lost author's URL so I can't credit him) but it looks like there's a lot of duplicated code (Such as the Add/Remove/GetNewRow methods).

I've tried to push the repetitive methods into a super class, but I have issues due to the Employee needing to be generic. I was hoping to get StackOverflow's collective hive mind to suggest some ideas to clean this up? (If it's even possible at all?)

using System;
using System.Data;
using System.Collections;
using System.Data.SqlClient;

namespace TypedDataSet {

  public class Employees : DataTable {
    protected SqlDataAdapter _adapter;

    public Employees() {
      string connectionString = TypedDataSet.Properties.Settings.Default.ConnectionString;
      _adapter = new System.Data.SqlClient.SqlDataAdapter("SELECT Id, Firstname, Surname FROM Employee", connectionString);
      _adapter.Fill(this);
    }

    public Employee this[int index] {
      get { return (Employee)Rows[index]; }
    }

    public void Add(Employee row) {
      Rows.Add(row);
    }

    public void Remove(Employee row) {
      Rows.Remove(row);
    }

    public Employee GetNewRow() {
      Employee row = (Employee)NewRow();
      return row;
    }

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

    public IEnumerator GetEnumerator() {
        return Rows.GetEnumerator();
    }

    protected override Type GetRowType() {
        return typeof(Employee);
    }
  }

  public class Employee : DataRow {
    internal Employee(DataRowBuilder builder)
      : base(builder) {
    }

    public Int64 Id {
      get { return (Int64)base["Id"]; }
      set { base["Id"] = value; }
    }

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

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

Solution

  • I believe that I've answered my question, kind of. I've had to use .net 4.0 to get the results that I was hoping for specificially the dynamic type.

    So changing the existing class in the question:

    Employee.cs

    using System;
    using System.Data;
    using System.Collections;
    using System.Data.Common;
    
    namespace TypedDataSet {
    
      public class Employees : BaseModel<Employee> {
    
        public Employees(bool loadAll) {
            DbDataAdapter adapter = base.Adapter("SELECT * FROM Employees");
            adapter.Fill(this);
        }
    
        protected override DataRow NewRowFromBuilder(DataRowBuilder builder) {
            return new Employee(builder);
        }
      }
    
      public class Employee : DataRow {
        internal Employee(DataRowBuilder builder)
          : base(builder) {
        }
    
        public Int64 Id {
          get { return (Int64)base["Id"]; }
          set { base["Id"] = value; }
        }
    
        public string FirstName {
          get { return (string)base["Firstname"]; }
          set { base["Firstname"] = value; }
        }
    
        public string Surname {
          get { return (string)base["Surname"]; }
          set { base["Surname"] = value; }
        }
      }
    }
    

    And now introducing BaseModel that the above class inherits


    BaseModel.cs

    using System;
    using System.Data;
    using System.Collections;
    using System.Data.Common;
    using System.Data.SqlClient;
    
    namespace TypedDataSet {
    
        public class BaseModel<T> : DataTable {
            protected DbDataAdapter _adapter;
            protected string _connectionString = TypedDataSet.Properties.Settings.Default.ConnectionString;
    
            public BaseModel() {
            }
    
            protected DbDataAdapter Adapter(string sql) {
                _adapter = new System.Data.SqlClient.SqlDataAdapter(sql, _connectionString);
                SqlCommandBuilder cb = new SqlCommandBuilder((SqlDataAdapter)_adapter);
                return _adapter; 
            }
    
            public dynamic this[int index] {
                get { return Rows[index]; }
            }
    
            public void Add(dynamic row) {
                Rows.Add(row);
            }
    
            public void Remove(dynamic row) {
                Rows.Remove(row);
            }
    
            public void Save() {
                _adapter.Update(this);
                this.AcceptChanges();
            }
    
            public dynamic GetNewRow() {
                dynamic row = (dynamic)NewRow();
                return row;
            }
    
            public IEnumerator GetEnumerator() {
                return Rows.GetEnumerator();
            }
    
            protected override Type GetRowType() {
                return typeof(T);
            }
        }
    }
    

    Which allows me to consume the class using the following code:


    Employees employees = new Employees(true);
    
    Employee employee = employees.GetNewRow();
    employee.FirstName = "Greg";
    employee.Surname = "Focker";
    employees.Add(employee);
    
    employees.Save();
    
    foreach (Employee e in employees) {
      Console.WriteLine(e.FirstName + ' ' + e.Surname);
    }
    

    I hope to evolve this further overtime so future StackOverflow users if you're interested in this little project take a look at http://bitbucket.org/Mozketo/typeddataset/ where I hope to host the code.