Skip to main content

Property setters are just wrong!

In general I think the use of property setters on classes is a sign of bad class design and a lack of OO principles - in most cases when you modify the 'state' of a class some 'behaviour' is invoked whether it be implicit or explicit;

e.g. when I change the address on a user account I want to make sure the address object has been populated (at least).

So these days I've started to design classes that have public getters & private setters, and if you want to modify state you are required to call a method, e.g. 'ChangeAddress'. This is nothing new, there are plenty of examples out there.

So in the example below 'Token' is property that's defined when the object is constructed and 'Path' is a property which is modified by calling the method 'ChangePath'.
    public class File : ObservableEntity<int>
{
private string _path;
private Guid _token;

private readonly IExtractFileProperties _propertiesExtractor = new FilePropertiesExtractor();


public File()
{
_token = Guid.NewGuid();
}


public virtual string Path
{
get { return _path; }
private set { ChangePropertyAndNotify(ref _path, value, x => Path); }
}

public virtual Guid Token
{
get { return _token; }
private set { ChangePropertyAndNotify(ref _token, value, x => Token); }
}


public File ChangePath(string path)
{
Path = path;

Size = _propertiesExtractor.Size(Path);
Format = _propertiesExtractor.Format(Path);
FileCreatedOn = _propertiesExtractor.CreatedOn(Path);
FileLastModified = _propertiesExtractor.LastModified(Path);

return this;
}
}


Now this attitude has been going well as it's made my classes more behaviour rich with better encapsulation & abstraction.

The only problem I've come across is when I'm trying to re-hydrate objects from the database where the properties I'm attempt to set have some kind of special behaviour - properties representing timestamps & versions that are historically set in the database via some function.

So in the following example I've got 3 properties - 'CreatedOn' 'LastModified' & 'Version'. I don't want to expose a method to set this values - I want them to be immutable...

But this is going to be a problem with an ORM, how am I going to update these values when saving or updating - I want them appear to be auto-updating from a users perspective.
    public abstract class ObservableEntity<T> : IEntity<T>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };

private T _id;

private DateTime? _createdOn;
private DateTime? _lastModified;
private int? _version;

public virtual T Id
{
get { return _id; }
private set { ChangePropertyAndNotify(ref _id, value, x => Id); }
}

public virtual DateTime? CreatedOn
{
get { return _createdOn; }
private set { ChangePropertyAndNotify(ref _createdOn, value, x => CreatedOn); }
}

public virtual DateTime? LastModified
{
get { return _lastModified; }
private set { ChangePropertyAndNotify(ref _lastModified, value, x => LastModified); }
}

public virtual int? Version
{
get { return _version; }
private set { ChangePropertyAndNotify(ref _version, value, x => Version); }
}

protected void ChangePropertyAndNotify<T2>(ref T2 value, T2 newValue, Expression<Func<object, T2>> expression)
{
value = newValue;
Notify(expression);
}

protected void Notify<T2>(Expression<Func<object, T2>> expression)
{
var propertyName = ((MemberExpression)expression.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Now when using nHibernate as your ORM this becomes very easy because you can attach Listeners and do custom actions.

So I've got a custom action when NH saves or updates an entity to set these properties with the correct values.
public class EntitySaveOrUpdateEventListener : IPreInsertEventListener, IPreUpdateEventListener
{
public bool OnPreInsert(PreInsertEvent @event)
{
var hasType = (@event.Entity.GetType().GetInterface(typeof(IVersionable).Name, true) != null);
if (!hasType)
return false;

var now = DateTime.Now;
var createdOnIndex = new List<string>(@event.Persister.PropertyNames).FindIndex(x => x == "CreatedOn");
@event.State[createdOnIndex] = now;
SetPrivateVariable("CreatedOn", now, @event.Entity);

var lastModifiedIndex = new List<string>(@event.Persister.PropertyNames).FindIndex(x => x == "LastModified");
@event.State[lastModifiedIndex] = now;
SetPrivateVariable("LastModified", now, @event.Entity);

var versionIndex = new List<string>(@event.Persister.PropertyNames).FindIndex(x => x == "Version");
var version = GetPrivateVariable<int?>("Version", @event.Entity);

version = version.GetValueOrDefault() + 1;
@event.State[versionIndex] = version;
SetPrivateVariable("Version", version, @event.Entity);

return false;
}

public bool OnPreUpdate(PreUpdateEvent @event)
{
var hasType = (@event.Entity.GetType().GetInterface(typeof(IVersionable).Name, true) != null);
if (!hasType)
return false;

var now = DateTime.Now;
var lastModifiedIndex = new List<string>(@event.Persister.PropertyNames).FindIndex(x => x == "LastModified");
@event.State[lastModifiedIndex] = now;
SetPrivateVariable("LastModified", now, @event.Entity);

var versionIndex = new List<string>(@event.Persister.PropertyNames).FindIndex(x => x == "Version");
var version = GetPrivateVariable<int?>("Version", @event.Entity);

version = version.GetValueOrDefault() + 1;
@event.State[versionIndex] = version;
SetPrivateVariable("Version", version, @event.Entity);
return false;
}

public void SetPrivateVariable(string name, object value, object entity)
{
var entityType = entity.GetType();

var pi = entityType.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
pi.ReflectedType.BaseType.InvokeMember(name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance,
null,
entity,
new[] { value });
}

public T GetPrivateVariable<T>(string name, object entity)
{
var entityType = entity.GetType();

var pi = entityType.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

return (T)pi.GetValue(entity, null);
}
}

Now if you follow this code, when an entity is saved for the first time then the 'CreatedOn' & 'LastModified' property values are set to the current date & time and the 'Version' property is incremented (the initial value is 0) via the private backing properties. When the entity is updated only the 'LastModified' & 'Version' properties are set via the private backing properties.

This event listener is then instantiated via the fluent NH config.
private static ISessionFactory CreateSessionFactoryImpl()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(c => c.FromConnectionStringWithKey("Pronunciations")).ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<IMap>())
.ExposeConfiguration(c =>
{
c.EventListeners.PreInsertEventListeners = new IPreInsertEventListener[] { new EntitySaveOrUpdateEventListener() };
c.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { new EntitySaveOrUpdateEventListener() };
})
.BuildSessionFactory();
}



Awkward Coder

Comments