Demonstrating Attributes and Reflection in .NET - Generic Database Manager (Page 3 of 8 )
One common requirement of software development projects is the storage, retrieval, update, and deletion of business objects within a database. Quite often, business objects map directly to a table in an underlying database. Typically, a class maps to a table and the individual properties of the class map to the table's columns. Once developers create the required business objects, they must then write SQL to support add, retrieve, update and delete operations.
In this first section, we will examine how .NET attributes can be used to achieve persistence of objects to a database without writing SQL statements for each business object.
In this section, we will:
Create three custom attributes used to describe how a class maps to a table in a database.
Develop a support class called SqlGenerator that uses .NET Reflection to inspect an object and create the appropriate SQL statements.
Develop an SqlObjectManager class that stores, retrieves, updates, and deletes rows from a SQL Server database given an object.
Examine code that exercises the object manager and its support classes.
The class diagram below shows the custom attributes we will design in the next section.
Note that each custom attribute derives from System.Attribute:
Table Attribute
Our first attribute specifies the table that a class maps to. The DbTable attribute contains properties indicating the table name and owner. Here is the attribute's complete definition:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class DBTable : System.Attribute { protected string _tableName = ""; // the name of the table protected string _tableOwner = ""; // the owner (i.e. dbo) /// /// overloaded. Instantiates a copy of the DbTable Attribute /// /// The name of the column /// The type of the column public DBTable(string tableName, string tableOwner) { // set the instance variables this.TableName = tableName; this.TableOwner = tableOwner; } /// /// The name of the column that this attribute describes /// public string TableName { get { return _tableName; } set { _tableName = value; } } /// /// The owner of this table in SQL server (i.e. dbo) /// public string TableOwner { get { return _tableOwner; } set { _tableOwner = value; } } }
Note the AttributeUsage attribute prior to the class definition. This attribute indicates that the DbTable attribute 1) can only be applied to classes, 2) cannot be used more than once on a particular class, and 3) will be inherited by derived classes.
Column Attribute
The DBColumn attribute specifies which column a particular property represents in a database. This attribute contains the name and the data type of the column, as well as a flag specifying the "nullability" of the column (i.e. whether the column must contain a value).
Here is the full definition of the DBColumn attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class DBColumn : System.Attribute { protected string _columnName = ""; // the name of the column protected DbType _columnType = DbType.AnsiString; // type of column protected bool _nullable; // specifies whether the column is nullable /// /// overloaded. Instantiates a copy of the DBColumn Attribute /// /// The name of the column /// The type of the column public DBColumn (string colName, DbType colType, bool bNullable) { // set the instance variables this.ColumnName = colName; this.ColumnType = colType; this.Nullable = bNullable; } /// /// Specifies whether the column is nullable /// public bool Nullable { get { return _nullable; } set { _nullable = value; } } /// /// The name of the column that this attribute describes /// public string ColumnName { get { return _columnName; } set { _columnName = value; } } /// /// The type of the column that this attribute describes /// public DbType ColumnType { get { return _columnType; } set { _columnType = value; } } }
Notice that the AttributeUsage attribute indicates that DBColumn may only apply to a property within a class.
Primary Key Attribute
The last attribute we will define is one that identifies the properties that map to the table's underlying primary key. Later in this article, we will write code that uses the primary key to formulate an appropriate where clause.
Here is the definition of the DBPrimaryKeyField attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class DBPrimaryKeyField: System.Attribute { protected string _columnName = ""; // the name of the column protected DbType _columnType = DbType.AnsiString; // the type
/// /// overloaded. Instantiates a DBUniqueRow attribute given /// the column name and type /// /// /// the column name that is part of the primary key /// /// /// the database type that is part of the primary key /// public DBPrimaryKeyField(string colName, DbType colType) { // set the instance variables this.ColumnName = colName; this.ColumnType = colType; } /// /// the name of the column that is part of the primary key /// public string ColumnName { get { return _columnName; } set { _columnName = value; } } /// /// the database type of the column that is part of the /// primary key /// public DbType ColumnType { get { return _columnType; } set { _columnType = value; } } }
Note that this attribute may only be applied to properties within the class, and that only one attribute can be applied to a particular property.
The only properties of the DBPrimaryKeyField attribute are the ColumnName and ColumnType.