Using Reflection and Attributes for Late Binding in Your .NET Classes - O/R Mappers
by David Hayden ( .NET Developer )
I thought I would take a break from so many Enterprise Library 2.0 Articles. I know everyone who reads my blog can use a break, too :) In fact, I just finished 3 articles on the Cryptography Application Block that I didn't even aggregate to the mainfeed because I was tired of seeing so much Enterprise Library 2.0 stuff in my news reader. If you have some interest, here are the links:
Reflection and Attributes
A blog entry by a fellow developer re-peaked my interest in playing with Reflection and Attributes this past week. I love attributes and reflection, but most of the time I can't justify using them in my applications. I'll use the conventional custom provider model at times that reads a class from the web.config file and instantiates it at run time using reflection into a singleton, but often that is even overkill and my feelings about 1) the provider model, and 2) singletons are quite mixed right now. I typically avoid them for reasons that are beyond the scope of this article. However, I am aware of the positives of the provider model and singletons as well.
In general, instantiating an object using reflection is slower than using the new keyword. This performance hit may or may not be a big deal depending on the situation, but it is something to consider. One also doesn't get compile time checking when using reflection, because the compiler doesn't know anything about the object being instantiated. Again, this is just something to consider.
To read attributes on classes, properties, fields, methods, etc. requires reflection. This has a performance hit as well as anything that requires reflection will be a bit slower at least the first time. You can of course do some caching the first time and reuse information in a cache on subsequent tasks. In general, reflection provides a very powerful late binding, polymorphism-like, pluggable environment with a performance hit, that quite frankly might be well worth it in the long run depending on the maintenance, programming effort, and time savings.
Reflection, Attributes, and O/R Mapping
The discussion in question that sparked my interest in playing with attributes and reflection had to do with persisting and retrieving objects to a datastore, essentially for O/R Mapping.
Three particular O/R Mappers come to mind when you mention reflection, attributes, and O/R Mapping: Gentle.NET, Retina.NET, and NPersist. I have played with each of these O/R Mappers in my spare time, but never used them in a production environment. Of course, you know the Microsoft version, DLinq.
Creating an Attribute
Creating an attribute is extremely easy. Just create a class that derives from System.Attribute. Name the attribute class so that it ends in Attribute. This is a best practice and has some coding benefits as well. Let's create a Class Attribute that specifies the Database Table associated with the class.
[AttributeUsage(AttributeTargets.Class,
AllowMultiple=false)]
public class TableAttribute : System.Attribute
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public TableAttribute(string name)
{
_name = name;
}
}
Once we create the attribute that has a class scope, we can add it to a class as such:
[Table("Customer")]
public class Customer
{
// Anything...
}
It can't get any simpler than that. We now have an attribute that relates the Customer Class to a Customer Table in our database.
Retrieving The Attribute Using Reflection
Now that we have created a Class-level Attribute and added it to the Customer Class, we need to read the attribute at runtime. Here is a simple code snippet that retrieves the attribute and the value in the name property:
private string GetTable(Type type)
{
object[] tables = type.
GetCustomAttributes(typeof(TableAttribute), true);
if (tables.Length == 1)
return (tables[0] as TableAttribute).Name;
else
return null;
}
When you call it via GetTable(typeof(Customer)), the return value is “Customer“.
You can start to see the power of this if you wrap it up into some type of DataManager Class that reads the class at runtime and generates select, insert, update, delete, and other dynamic sql at runtime. Of course, you can also use it to invoke stored procedures at runtime, too.
public class DataManager
{
public DataManager()
{
}
public object Fetch(object id, Type type)
{
string tableName = GetTable(type);
// ...
}
}
You wouldn't program it like that, but you see the possibilities. Generating a simple, but unrealistic select statement could be as simple as:
string selectSQL = string.Format
("SELECT * FROM [{0}]", tableName);
Specifying Columns Using Property Attributes
We can extend this example further by creating additional attributes that are focused on properties. Perhaps an attribute that specifies the primary key of the table and whether it is an identity. This is by no means complete, but gets one in the desired direction:
[AttributeUsage(AttributeTargets.Property,
AllowMultiple=false)]
public class KeyAttribute : System.Attribute
{
public KeyAttribute()
{
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
private bool _isIdentity;
public bool IsIdentity
{
get
{
return _isIdentity;
}
set
{
_isIdentity = value;
}
}
}
We can add this to our customer class as follows:
[Table("Customer")]
public class Customer
{
private int _id;
[Key(Name = "CustomerID", IsIdentity=true)]
public int ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
// Anything...
}
You can see where we are going with this. This tells us that the ID Property on the Customer Class maps to the CustomerID Column on the Customer Table and that it is an identity, which means the table itself generates the value of CustomerID.
We can check for this attribute using the following sample using reflection, where type would be typeof(Customer) in this case:
PropertyInfo[] properties =
type.GetProperties();
foreach (PropertyInfo p in properties)
{
object[] keys = p.GetCustomAttributes
(typeof(KeyAttribute), true);
// ...
}
This can help in the generation of the selectSql statement above to select a single item.
Conclusion
This just scratches the surface on the subject of reflection and attributes. Although I am discussing it in terms of O/R Mapping, there are many more possibilities. I will talk more about this in additional posts.
Source: David Hayden ( .NET Developer )