I have a general distaste for decorating my code with Attributes and Annotations. Most of the time, I can't help but feel like there must be a better way to accomplish what I am trying to do, and/or that I have somewhere sprung a leak in what should be a helpful abstraction.
Other times, though, custom attributes can be just the tool for the job, and sometimes, the only practical way to solve a problem.
An easy to understand use case for Custom Attributes might be the mapping of object properties to database fields in a data access layer. You have no doubt seen this before when using Entity Framework. In EF, we often utilize System.ComponentModel.DataAnnotations to decorate the properties of our data objects.
Here, we're going to take a quick look at creating our own custom attributes.
Yes, EF and the System.ComponentModel.DataAnnotations
namespace provide a ready-made means to do this, but for one, you may find yourself building your own data access layer or tool, and for another, this is an easy-to-understand example case.
Let's see how we might implement our own version of these data annotations as Custom Attributes. To create a Custom Attribute in C#, we simply create a class which inherits from System.Attribute
. For example, if we wanted to implement our own [PrimaryKey]
Attribute to indicate that a particular property on a class in our application represents the Primary Key in our database, we might create the following Custom Attribute:
public class PrimaryKeyAttribute : Attribute { }
Now, consider a class in our application, the Client
class. Client
has a ClientId
property which corresponds to the Primary Key in the Clients database table:
public class Client
{
public int ClientId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
We could simply decorate the ClientId
property with our new attribute, and then access this from code as in the simple example following:
Decorate the ClientId property with the Custom Primary Key Attribute:
public class Client
{
[PrimaryKey]
public int ClientId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
Then, we can cook up a simple console app demo to see how this works. First, we'll take the long way around, iterating using a foreach
structure so we can see more clearly what's going on. Then we will look at a more concise (and efficient) LINQ-based implementation.
Silly Example of Accessing a Custom Property:
static void WritePK<T>(T item) where T : new()
{
var type = item.GetType();
var properties = type.GetProperties();
Console.WriteLine("Finding PK for {0}", type.Name);
foreach(var property in properties)
{
var attributes = property.GetCustomAttributes(false);
foreach(var attribute in attributes)
{
if(attribute.GetType() == typeof(PrimaryKeyAttribute))
{
string msg = "The Primary Key for the {0} class is the {1} property";
Console.WriteLine(msg, type.Name, property.Name);
}
}
}
}
In the code above, we pass in a Generic object of type T (meaning, this method could be used with ANY domain object to check for the presence of a [PrimaryKey]
attribute). We first use the GetType()
method to find the object's Type
information, and then we call the GetProperties()
method of the Type
instance, which returns an array of PropertyInfo
objects.
Next, we iterate over each of the PropertyInfo
instances, and call the GetCustomAttributes()
method, which will return an array of objects representing the CustomAttributes
found on that property. We can then check the type of each CustomAttribute
object, and if it is of type PrimaryKeyAttribute
, we know we have found a property that represents a primary key in our database.
We could re-write the code above, using LINQ, for a more compact and efficient method as follows:
The WritePk Method, Re-Written Using LINQ:
static void WritePK<T>(T item) where T : new()
{
var type = item.GetType();
var properties = type.GetProperties();
Console.WriteLine("Finding PK for {0}", type.Name);
var property = properties
.FirstOrDefault(p => p.GetCustomAttributes(false)
.Any(a => a.GetType() == typeof(PrimaryKeyAttribute)));
if (property != null)
{
string msg = "The Primary Key for the {0} class is the {1} property";
Console.WriteLine(msg, type.Name, property.Name);
}
}
This example is fairly simplistic, but illustrates well how we can access CustomAttributes to useful end.
Another case, which I ran into recently is mapping properties to database columns. In creating a general-purpose data access tool, you never know how database columns are going to align with the properties on your domain objects. In my case, we needed to dynamically build some SQL, using reflection to grab object properties, and map to the database. However, there is no guarantee that the database column names will match the property names on the domain object.
In cases where column names differ from object properties in such a situation, Custom Attributes are one means of dealing with the situation (this is the part where the abstraction layer of the data access tool gets violated by the Db rearing its head into the business object domain . . .).
The previous example simply used a Custom Attribute simply as sort of a tag on a property. Attributes can also convey information if needed. Let's consider a means to map the property to a specific database column name.
Once again, we create a class which inherits from System.Attribute
, but this time we will add a property and a constructor:
The Custom DbColumn Attribute:
public class DbColumnAttribute : Attribute
{
string Name { get; private set; }
public DbColumnAttribute(string name)
{
this.Name = name;
}
}
Now, let's pretend you inherit a database which you need to integrate with your existing code base. The table from which the client information will be sourced uses all lower-case column names, with underscores between segments instead of proper or camel casing:
SQL For a Table With Column Names Which Do Not Match Class Properties:
CREATE TABLE Clients (
client_id int IDENTITY(1,1) PRIMARY KEY NOT NULL,
last_name varchar(50) NOT NULL,
first_name varchar(50) NOT NULL,
email varchar(50) NOT NULL
);
Now you can do this:
The Client Class with Column Name Attributes:
public class Client
{
[PrimaryKey]
[DbColumn("client_id")]
public int ClientId { get; set; }
[DbColumn("last_name")]
public string LastName { get; set; }
[DbColumn("first_name")]
public string FirstName { get; set; }
[DbColumn("email")]
public string Email { get; set; }
}
You can access these attributes, and their properties, from code like so:
Reading Custom Attribute Properties from Code:
static void WriteColumnMappings<T>(T item) where T : new()
{
var type = item.GetType();
var properties = item.GetType().GetProperties();
Console.WriteLine("Finding properties for {0} ...", type.Name);
foreach(var property in properties)
{
var attributes = property.GetCustomAttributes(false);
string msg = "the {0} property maps to the {1} database column";
var columnMapping = attributes
.FirstOrDefault(a => a.GetType() == typeof(DbColumnAttribute));
if(columnMapping != null)
{
var mapsto = columnMapping as DbColumnAttribute;
Console.WriteLine(msg, property.Name, mapsto.Name);
}
}
}
"But John," you say, "Entity Framework already does this!"
Precisely. But now you know how it works. Believe me, you may not always have EF at your disposal. Also, you WILL run into databases "in the wild" where column naming conventions do not align with C# Class and property naming conventions (Work with a Postgresql database for five minutes, and get back to me).
Cache Results from Calls Using Reflection Where Appropriate
A quick note, which is only marginally applicable to the examples above, but
important in the design of a real-world application. Calls to using reflection
can be expensive. Overall, machines these days are fast, and generally, the odd
call to GetType()
and GetCustomAttributes()
are not all
that significant. Except when they are.
For example, if the above code were used in a larger application context
repeatedly, it might be better to walk through the object properties at object
initialization (or even at application load) and map the object properties for
each to its respective column name and stash them all in a Dictionary<string, string>
.
Then, anywhere in your code where the mapping is needed, you can access the
primary key name for a specific object by use of the property as a key.
How you do this and where would depend heavily on what you are doing, and the
larger structure of your application. For an example of what I am talking about,
check out the Biggy project, where I recently had to do this
very thing.
I was working on an open-source project recently, and the project maintainer wisely pointed out that column-mapping attributes such as the above are "the database pushing right on up through the abstraction." Which is true. In the case of mapping database columns to object properties, we are attempting to solve but one aspect of the age-old impedance mismatch problem faced by all Object-Relational Mapping (ORM) frameworks. It ain't always elegant, but sometimes, it is the only way.
None of this is to say that Custom Attributes are only useful in the context of mapping database columns. There are any number of potential use-cases. While I personally dislike cluttering up my code with attributes and annotations, there will be times when it is the best way to solve a problem.
The next time you find yourself wishing you could know something extra about a particular property or method, Custom Attributes are one more tool in your chest.
John on GoogleCodeProject