patterncsharpMinor
Framework to track changes and relationships in C#
Viewed 0 times
trackchangesandrelationshipsframework
Problem
Recently, I wanted to see how I might could track state changes to objects, and manage relationships (1 to 1, 1 to N, N to N) between types in C#. This was a really interesting project, and I'm wondering how it might be improved.
Full code
The full code can be found here, which includes a test project.
Basics:
Everything in my
All the
To actually track changes to properties in
Full code
The full code can be found here, which includes a test project.
Basics:
TrackableEntity, EntityManager and TrackablePropertyEverything in my
EntityTracker project works with TrackableEntity, which does what you might think. /// Base type that supports change tracking
public abstract class TrackableEntity
{
public int Id
{
get;
internal set;
}
public bool IsDirty
{
get;
internal set;
}
public void Commit()
{
IsDirty = false;
}
public TrackableEntity()
{
EntityManager.Instance.Create(this);
}
public void Delete()
{
EntityManager.Instance.Delete(this);
}
}All the
TrackableEntity objects are stored in a globally accessible repository, EntityManager. I'll save discussion of the Delete method referenced by TrackableEntity for later.///
/// Container class to hold TrackableEntity objects
///
public class EntityManager
{
private static EntityManager instance = new EntityManager();
private int next;
private Dictionary entities;
private EntityManager()
{
entities = new Dictionary();
}
public static EntityManager Instance
{
get { return instance; }
}
/// Adds the TrackableEntity in the container
public void Create(TrackableEntity entity)
{
entity.Id = next++;
entities[entity.Id] = entity;
}
/// Gets the TrackableEntity stored at id
public TrackableEntity Lookup(int id)
{
return entities.ContainsKey(id) ? entities[id] : null;
}
}To actually track changes to properties in
TrackableEntity, I created an object to wrap each property which allows me to manage the sSolution
General
If you need to get a value of a
See also: what-is-more-efficient-dictionary-trygetvalue-or-containskeyitem
TrackableEntity
This abstract class seems almost fine to me. The only missing is the lack of documentation. A framework should always have enough documentation targeting the the parts which are accesible from outside. So each
While we talk about scope, an
If I would implement the abstract
EntityManager singleton
Hmmm, a singletone is coming along. How am I be able to write unit tests for a singleton or for classes which uses this singleton? Hmmm, I just can't easily do this, because I can't reset the state of this singleton between tests which is bad.
For instance assume we want to test the creation of a
Next we write another test which test for
Then you run both tests and they passed and you ask yourself hey what is this guy talking about but then you change the order of the tests and hence the first written test fails because after the test for
What are possible ways out of this ? You can let
/// Wrapper property to track changes
public class TrackableProperty where T : IEquatable
{
Dictionary values = new Dictionary();
/// Gets the owner's value
public T GetValue(TrackableEntity owner)
{
T foundValue;
values.TryGetValue(owner.Id, out foundValue);
return foundValue;
}
/// Sets the owner's
If you need to get a value of a
Dictionary you shouldn't us ContainsKey() together with the Item property getter but TryGetValue(), because by using ContainsKey() in combination with the Item getter you are doing the check if the key exists twice.See also: what-is-more-efficient-dictionary-trygetvalue-or-containskeyitem
TrackableEntity
This abstract class seems almost fine to me. The only missing is the lack of documentation. A framework should always have enough documentation targeting the the parts which are accesible from outside. So each
public and protected property/method should be documentated.While we talk about scope, an
abstract class only serves one purpose, it is intended to be implemented. By making a setter of a property internal one who wants to implement such a class can't set these properties (if it is allowed to set them).If I would implement the abstract
TrackableEntity class, I would maybe want to set the IsDirty property to a specific value, but because its internal I just can't. So making properties which you feel should be set from outside protected would be a good decision.EntityManager singleton
Hmmm, a singletone is coming along. How am I be able to write unit tests for a singleton or for classes which uses this singleton? Hmmm, I just can't easily do this, because I can't reset the state of this singleton between tests which is bad.
For instance assume we want to test the creation of a
TrackableEntity object and because it comes handy we assert that TrackableEntity.Id == 1 so, no problem there, the test passes.Next we write another test which test for
TrackableEntity.Delete() and we assert with EntityManager.Instance.Lookup(TrackableEntity.Id) == null).Then you run both tests and they passed and you ask yourself hey what is this guy talking about but then you change the order of the tests and hence the first written test fails because after the test for
Delete the private int next of the EntityManager is 1. So if the creation test is executed the assert will fail.What are possible ways out of this ? You can let
EntityManager implement an interface IEntityManager having Create(), Lookup()andDelete()methods and use a private field inTrackableEntitywhich is holding a reference ofIEntityManager which is constructor injected.
The changes you would need to do are simple, some thing along these lines
public interface IEntityManager
{
TrackableEntity Lookup(int id);
void Create(TrackableEntity entity);
void Delete(TrackableEntity entity);
}
public class EntityManager : IEntityManager
{
private static IEntityManager instance = new EntityManager();
private int next;
private Dictionary entities;
private EntityManager()
{
entities = new Dictionary();
}
public static IEntityManager Instance
{
get { return instance; }
}
/// Adds the TrackableEntity in the container
public void Create(TrackableEntity entity)
{
entity.Id = next++;
entities[entity.Id] = entity;
}
/// Gets the TrackableEntity stored at id
public TrackableEntity Lookup(int id)
{
TrackableEntity entity;
entities.TryGetValue(id, out entity);
return entity;
}
public void Delete(TrackableEntity entity)
{
// we will target this later
throw new NotImplementedException();
}
}
public abstract class TrackableEntity
{
private readonly IEntityManager manager;
public TrackableEntity()
{
manager = EntityManager.Instance;
manager.Create(this);
}
public TrackableEntity(IEntityManager manager)
{
this.manager = manager;
manager.Create(this);
}
public int Id
{
get;
internal set;
}
public bool IsDirty
{
get;
internal set;
}
public void Commit()
{
IsDirty = false;
}
public void Delete()
{
manager.Delete(this);
}
}
Now you use a mocked IEntityManager obejct for unit tests.
Another maybe easier aproach would be like described in the answer to unit-testing-singletons
Short version: do not write your singletons as singletons. Write them as normal classes, and call them via an Inversion of Control container, where you have configured the class to be a singleton instead.
TrackableProperty
Using multiple code statements in the same line is hard to read and to debug and should be avoided.
Access to a dictionary should be changed like written above like so
``/// Wrapper property to track changes
public class TrackableProperty where T : IEquatable
{
Dictionary values = new Dictionary();
/// Gets the owner's value
public T GetValue(TrackableEntity owner)
{
T foundValue;
values.TryGetValue(owner.Id, out foundValue);
return foundValue;
}
/// Sets the owner's
Code Snippets
public interface IEntityManager
{
TrackableEntity Lookup(int id);
void Create(TrackableEntity entity);
void Delete(TrackableEntity entity);
}public class EntityManager : IEntityManager
{
private static IEntityManager instance = new EntityManager();
private int next;
private Dictionary<int, TrackableEntity> entities;
private EntityManager()
{
entities = new Dictionary<int, TrackableEntity>();
}
public static IEntityManager Instance
{
get { return instance; }
}
/// <summary>Adds the TrackableEntity in the container</summary>
public void Create(TrackableEntity entity)
{
entity.Id = next++;
entities[entity.Id] = entity;
}
/// <summary>Gets the TrackableEntity stored at id</summary>
public TrackableEntity Lookup(int id)
{
TrackableEntity entity;
entities.TryGetValue(id, out entity);
return entity;
}
public void Delete(TrackableEntity entity)
{
// we will target this later
throw new NotImplementedException();
}
}public abstract class TrackableEntity
{
private readonly IEntityManager manager;
public TrackableEntity()
{
manager = EntityManager.Instance;
manager.Create(this);
}
public TrackableEntity(IEntityManager manager)
{
this.manager = manager;
manager.Create(this);
}
public int Id
{
get;
internal set;
}
public bool IsDirty
{
get;
internal set;
}
public void Commit()
{
IsDirty = false;
}
public void Delete()
{
manager.Delete(this);
}
}/// <summary>Wrapper property to track changes</summary>
public class TrackableProperty<T> where T : IEquatable<T>
{
Dictionary<int, T> values = new Dictionary<int, T>();
/// <summary>Gets the owner's value</summary>
public T GetValue(TrackableEntity owner)
{
T foundValue;
values.TryGetValue(owner.Id, out foundValue);
return foundValue;
}
/// <summary>Sets the owner's value</summary>
public void SetValue(TrackableEntity owner, T value)
{
T foundValue;
if (values.TryGetValue(owner.Id, out foundValue) && foundValue.Equals(value)
{
return;
}
owner.IsDirty = true;
values[owner.Id] = value;
}
}public void Delete(TrackableEntity entity)
{
if (entity is RelationshipEntity)
{
var casted = (RelationshipEntity)entity;
casted.Accept(new RelationshipBreaker(casted));
}
entities.Remove(entity.Id);
}Context
StackExchange Code Review Q#104752, answer score: 7
Revisions (0)
No revisions yet.