Introduction
Greetings reader, this article sets out to make the task of defining properties a little less typing intensive. It should provide a convenient location to track changes to entities or whatever you can think of.
In short, you'll be able to write properties that look like this:
public DateTime DateCreated{
get{ return (DateTime)propertyValue; }
set{ propertyValue = value; }
}
The above code is a very simple example, it doesn't do much more than save you defining the underlying variable (i.e. DateTime dateCreated = DateTime.MinValue;
).
Let's look at what's needed to get this going.
The base class
[Serializable]
public abstract class Persistable {
public Persistable(){
properties = (PropertyValue[])
GetType().GetCustomAttributes(typeof(PropertyValue), true);
for (int i = 0; i < properties.Length; i++)
properties[i].parent = this;
}
PropertyValue[] properties;
protected object propertyValue{
set{
PropertyValue property = PropertyValue;
if (property == null)
throw new Exception("Property not found");
property.SetValue(value);
}
get{
PropertyValue property = PropertyValue;
return property == null ? null : property.Value;
}
}
PropertyValue PropertyValue{
get{
System.Diagnostics.StackTrace st =
new System.Diagnostics.StackTrace();
if (st.FrameCount > 0){
string propertyName =
st.GetFrame(2).GetMethod().Name;
propertyName = propertyName.Substring(4);
return FindProperty(propertyName);
}
return null;
}
}
PropertyValue FindProperty(string name){
for (int i = 0; i < properties.Length; i++){
if (properties[i].Name.Equals(name)){
return properties[i];
}
}
return null;
}
[Serializable]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
public class PropertyValue : Attribute{
public PropertyValue(string name){
this.name = name;
}
protected object value;
string name;
internal Persistable parent;
public string Name{
get{ return name; }
}
public virtual object Value{
get { return value; }
}
internal virtual void SetValue(object value){
this.value = value;
}
}
}
Using the code
Here's a simple example:
[Serializable]
[Persistable.PropertyValue("ApplicationNumber")]
public class ApplicationMessage : Persistable {
#region ApplicationMessage Properties
public string ApplicationNumber{
get{ return (string)propertyValue; }
set{ propertyValue = value; }
}
#endregion
}
Important: Note that the first argument to the PropertyValue
constructor is the same as the name of the property. This is how the relationship is made and these two values must match.
Now in order to make PropertyValue
more than just a performance degrader, you need to add some useful functionality. Let's take another look at the implementation of PropertyValue::SetValue(object value)
.
Suppose we need an audit log of changes made to our "Persistable
" entities. First we need to centralize the persistence of our entities.
public virtual Persist(){
...
}
Next add supporting objects for logging.
StringBuilder changeLog;
long id;
public long ID{
get{ return id; }
set{ id = value; }
}
void LogChange(string detail){
if (changeLog == null){
changeLog = new StringBuilder();
if (id == 0)
changeLog.AppendFormat("{0}: Instance" +
" of {1} was created.\n",
DateTime.Now, GetType());
else
changeLog.AppendFormat("{0}: Log started" +
" for instance of {1} with ID : {2}.\n",
DateTime.Now, GetType(), id);
}
changeLog.AppendFormat("{0}: {1}\n", DateTime.Now, detail);
}
string ChangeLog{
get{ return changeLog == null ? string.Empty : changeLog.ToString(); }
}
Now we change the implementation of PropertyValue::SetValue(object value)
.
internal virtual void SetValue(object value){
parent.LogChange(string.Format("Property {0} " +
"was changed from {1} to {2}.", this.value, value));
this.value = value;
}
Now we're logging our changes to a StringBuilder
. We need to output them somewhere, sometime. To do this, we need to make one final tweak to Persistable::Persist()
.
public virtual Persist(){
...
Log.LogAudit(ChangeLog);
}
Log.LogAudit
could do anything. Personally, I like to send to the Event Log. Here's a simple e.g.:
public sealed class Log {
static readonly string applicationName =
ConfigurationSettings.AppSettings["ApplicationName"];
static readonly string logSourceName =
ConfigurationSettings.AppSettings["LogName"];
static Log(){
if (!EventLog.Exists(applicationName))
EventLog.CreateEventSource(logSourceName, applicationName);
}
public static void LogAudit(string info) {
string output = string.Format("{0}\n", info);
EventLog.WriteEntry(logSourceName,
output, EventLogEntryType.Information, 1);
}
}
So now every time you Persist
a Persistable
entity, any changes that have been made are conveniently logged to the event log (or elsewhere). Incidentally, I've adapted all/most of this code from an application whilst writing this article, so nothing has been tested :D
Points of Interest
In the future (post Whidbey), it'd be sweet to change PropertyValue
to support Generics. That way it would know what data type it was storing. This would make it possible to do more cool stuff like put constraints on the property etc.