Using T4 to automatically generate your entities

*** This is a repost of a previous post because of moving to a new blog engine in which some formatting was lost ***

Today I discovered something very interesting, namely how to automatically generate my entity classes (classes that also implement the INotifyPropertyChanged and IDataErrorInfo interfaces) using T4.

T4 is a text generation language built into Visual Studio 2005 and later, normally intended for DSL code generation. But we can also use it for generating any code we would like. I for one, I would like automatic properties to automatically implement INotifyPropertyChanged if the interface is on the class, but of course, life doesn’t work that way *sigh*. The real problem is that for each property you have, part of the implementation needs to raise an event using the property name. When you change the name of the property you also have to change this string, which is error prone…

Instead we can generate code using T4, and to be honest, it isn’t that difficult to use. First of all, go to tangible to download their free T4 intelli-sense tool.

Next add to your project a new text file, naming it Entity.tt (the extension is important!). Now add this content:

<#@ template language="C#" #>
<#@ output extension="cs" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
// ---------------------------------
// U2U Sample, use at your own risk!
//        http://www.u2u.be
// ---------------------------------

using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace <#= this.Namespace #> 
{  
  public partial class <#= this.ClassName #> 
    : INotifyPropertyChanged
    , IDataErrorInfo  
  {
    #region Private fields
    <# for (int idx = 0; idx < this.properties.GetLength(0); idx++) { #>    
    private <#= this.properties[idx,0] #> _<#= this.properties[idx,1].ToLower() #>;<# }#>   
    
    #endregion
    
    #region Properties
    <# for (int idx = 0; idx < this.properties.GetLength(0); idx++) { #>    
    public <#= this.properties[idx,0] #> <#= this.properties[idx,1] #>
    {        
      get
      {            
        return _<#= this.properties[idx,1].ToLower() #>;        
      }        
      set        
      {            
        _<#= this.properties[idx,1].ToLower() #> = value;
        OnPropertyChanged("<#= this.properties[idx,1]#>");        
      }   
    }
    <#   }   #>      
    #endregion
    
    #region INotifyPropertyChanged Members   
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)   
    {       
      if (PropertyChanged != null)           
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));   
    }   
    
    #endregion
    
    #region IDataErrorInfo Members
    
    private string _error = null;
    
    public string Error {
      get
      { 
        return _error; 
      }
      set 
      {
        _error = value;
      }
    }
    
    private Dictionary<string,string> _columnErrors =
      new Dictionary<string,string>();
      
    public string this[string columnName]
    {
      get
      {
        if( _columnErrors != null && _columnErrors.ContainsKey(columnName))
          return _columnErrors[columnName];
        else
          return null;
      }
      set
      {
        if( value != null ) // Insert error
        {
          if( _columnErrors == null )
          {
            _columnErrors = new Dictionary<string,string>();
          }
          _columnErrors[columnName] = value;
        }
        else // Clear error
        {
          _columnErrors.Remove(columnName);
          if (_columnErrors.Count == 0)
          {
            _columnErrors = null;
          }
        }
        // Notify validation that something has changed
        OnPropertyChanged( columnName );
      }
    }
    #endregion
  }
}

<#+    
  string Namespace = "Demo";    
  string ClassName = "DemoClass";    
  string[,] properties = {        
    {"int", "Property1"},        
    {"string", "Property2"}
  };               
#>

Building your project will generate a little DemoClass with these two properties. But that is of course not what you want; so add another file Category.tt:

<#    
  this.Namespace = "U2U.Samples";    
  this.ClassName = "Category";    
  this.properties = new string[,]
  {        
    {"int"   , "CategoryID"}
  , {"string", "CategoryName"}      
  , {"string", "Description"}      
  , {"byte[]", "Picture"}
  }; 
#>

<#@ include file="Entity.tt" #>

Building will now automatically generate a Category.cs file with code like this:

// ---------------------------------
// U2U Sample, use at your own risk!
//        http://www.u2u.be
// ---------------------------------

using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace U2U.Samples
{
  public partial class Category
    : INotifyPropertyChanged
    , IDataErrorInfo
  {
    #region Private fields

    private int _categoryid;
    private string _categoryname;
    private string _description;
    private byte[] _picture;

    #endregion

    #region Properties

    public int CategoryID
    {
      get
      {
        return _categoryid;
      }
      set
      {
        _categoryid = value;
        OnPropertyChanged("CategoryID");
      }
    }

    public string CategoryName
    {
      get
      {
        return _categoryname;
      }
      set
      {
        _categoryname = value;
        OnPropertyChanged("CategoryName");
      }
    }

    public string Description
    {
      get
      {
        return _description;
      }
      set
      {
        _description = value;
        OnPropertyChanged("Description");
      }
    }

    public byte[] Picture
    {
      get
      {
        return _picture;
      }
      set
      {
        _picture = value;
        OnPropertyChanged("Picture");
      }
    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region IDataErrorInfo Members

    private string _error = null;

    public string Error
    {
      get
      {
        return _error;
      }
      set
      {
        _error = value;
      }
    }

    private Dictionary<string, string> _columnErrors =
      new Dictionary<string, string>();

    public string this[string columnName]
    {
      get
      {
        if (_columnErrors != null && _columnErrors.ContainsKey(columnName))
          return _columnErrors[columnName];
        else
          return null;
      }
      set
      {
        if (value != null) // Insert error
        {
          if (_columnErrors == null)
          {
            _columnErrors = new Dictionary<string, string>();
          }
          _columnErrors[columnName] = value;
        }
        else // Clear error
        {
          _columnErrors.Remove(columnName);
          if (_columnErrors.Count == 0)
          {
            _columnErrors = null;
          }
        }
        // Notify validation that something has changed
        OnPropertyChanged(columnName);
      }
    }
    #endregion
  }
}

So to change/add/remove a property, we edit the Category.tt file and all the rest is done automatically.

Now for some feedback, what is your way of implementing IDataErrorInfo?
Comments are closed