You can use for example datasets; datasets keep track of changes, and can update only changed rows.
But in most of the projects that I had to work on, datasets weren't used, or as less as possible, they used their own domain model for storing and transmitting data from point a to point b.
Another problem they encountered was the fact that some of the objects marked as serializable are in fact not really serializable (they are, but have issues), like the ObservableCollection
Knowing that these were problems, I've created a solution to bypass all these problems without inheriting or wrapping or using surrogates or whatever.
I've used code that I've blogged about earlier in the IL section:
» Deep cloning objects with IL
This code has the ability to clone objects with IL, you can choose for cloning deeply or shallow, or to exclude fields from being cloned, you can also adapt it to work with other attributes for usage in WCF for example.
It can clone ObserveableCollection
The code I've written makes it possible to check for changes in an object, it doesn't really matter which object, because it uses a MD5 hash code. The only problem is: knowing what has been changed, but that's not needed in my case.
Feel free to contribute for knowing where the object has been changed, without wrapping it, or adding stuff to it :)
Here's the code that does all the work:
/// <summary>
/// Class for checking wether objects have been changed or not.
/// Clones the object in order to check, for stripping events etc,
/// but checks against the original object.
/// Author: Slaets Filip
/// Date: 25/06/2008
/// </summary>
public static class DirtyChecker
{
/// <summary>
/// Cache for holding hashed objects-data.
/// </summary>
private static List<DirtyEntity> _hashesCache = new List<DirtyEntity>();
/// <summary>
/// Retrieve a hashcode for an object.
/// </summary>
/// <param name="objToCheck">Object to get hashcode for.</param>
/// <returns>hexadecimal hashcode</returns>
private static string GetHashRecord(object objToCheck)
{
// Create our hash-record of this object.
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, objToCheck);
MD5 md5 = MD5.Create();
ms.Position = 0;
byte[] hash = md5.ComputeHash(ms);
// Convert the hashvalue to a HEX string.
StringBuilder sb = new StringBuilder();
foreach (byte outputByte in hash)
{
// Convert each byte to a Hexadecimal lower case string
sb.Append(outputByte.ToString("X2").ToLower());
}
return sb.ToString();
}
/// <summary>
/// Check wether an object has changed somewhere or not.
/// </summary>
/// <param name="objToCheck">Object to check dirtyness of.</param>
/// <returns>True when something has been changed in the object.</returns>
public static bool IsDirty(object objToCheck)
{
if (objToCheck.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Length == 0)
{
throw new ArgumentException("Object is not serializable, mark it with [Serializable]", "objToCheck");
}
string name = string.Format("{0}.CloneHelper`1[[{1}]]", typeof(DirtyChecker).Namespace, objToCheck.GetType().AssemblyQualifiedName);
// Let's invoke the cloning mechanism
Type cloneType = Type.GetType(name);
if (cloneType == null)
throw new InvalidOperationException("Check the CloneHelper location, must be in same assembly and namespace as DirtyChecker!");
MethodInfo mi = cloneType.GetMethod("Clone", new Type[] { objToCheck.GetType(), typeof(CloneType) });
object toHashObject = mi.Invoke(null, new object[] { objToCheck, CloneType.ShallowCloning });
string hashRecord = GetHashRecord(toHashObject);
DirtyEntity entity = new DirtyEntity(objToCheck.GetHashCode(), hashRecord);
if (!_hashesCache.Contains(entity))
{
_hashesCache.Add(entity);
return false;
}
DirtyEntity foundEntity = _hashesCache[_hashesCache.IndexOf(entity)];
if (foundEntity.HashRecord.Equals(entity.HashRecord, StringComparison.InvariantCulture))
{
return false;
}
if (foundEntity.UpdateCascading)
{
foundEntity.HashRecord = hashRecord;
}
return true;
}
/// <summary>
/// Check wether an object has changed somewhere or not.
/// </summary>
/// <param name="objToCheck">Object to check dirtyness of.</param>
/// <param name="cascaded">If the record is dirty, take the new record as base-record for checking dirtyness.</param>
/// <returns>True when something has been changed in the object.</returns>
public static bool IsDirty(object objToCheck, bool cascaded)
{
if (objToCheck.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Length == 0)
{
throw new ArgumentException("Object is not serializable, mark it with [Serializable]", "objToCheck");
}
string name = string.Format("{0}.CloneHelper`1[[{1}]]", typeof(DirtyChecker).Namespace, objToCheck.GetType().AssemblyQualifiedName);
// Let's invoke the cloning mechanism
Type cloneType = Type.GetType(name);
MethodInfo mi = cloneType.GetMethod("Clone", new Type[] { objToCheck.GetType() });
object toHashObject = mi.Invoke(null, new object[] { objToCheck });
string hashRecord = GetHashRecord(toHashObject);
DirtyEntity entity = new DirtyEntity(objToCheck.GetHashCode(), hashRecord, cascaded);
if (!_hashesCache.Contains(entity))
{
_hashesCache.Add(entity);
return false;
}
DirtyEntity foundEntity = _hashesCache[_hashesCache.IndexOf(entity)];
if (foundEntity.HashRecord.Equals(entity.HashRecord, StringComparison.InvariantCulture))
{
return false;
}
if (foundEntity.UpdateCascading || cascaded)
{
foundEntity.HashRecord = hashRecord;
}
return true;
}
/// <summary>
/// Class for storing hashing data in the cache.
/// </summary>
private class DirtyEntity
{
private int _hashCodeOfObject;
private string _hashRecord;
private bool _updateCascading;
public DirtyEntity(int hc, string hr)
{
_hashCodeOfObject = hc;
_hashRecord = hr;
_updateCascading = true;
}
public DirtyEntity(int hc, string hr, bool cascading)
{
_hashCodeOfObject = hc;
_hashRecord = hr;
_updateCascading = cascading;
}
public int HashCodeOfObject
{
get { return _hashCodeOfObject; }
set { _hashCodeOfObject = value; }
}
public string HashRecord
{
get { return _hashRecord; }
set { _hashRecord = value; }
}
public bool UpdateCascading
{
get { return _updateCascading; }
set { _updateCascading = value; }
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is DirtyEntity)
{
DirtyEntity de = obj as DirtyEntity;
return de._hashCodeOfObject == this._hashCodeOfObject;
}
return base.Equals(obj);
}
}
}
/// Class for checking wether objects have been changed or not.
/// Clones the object in order to check, for stripping events etc,
/// but checks against the original object.
/// Author: Slaets Filip
/// Date: 25/06/2008
/// </summary>
public static class DirtyChecker
{
/// <summary>
/// Cache for holding hashed objects-data.
/// </summary>
private static List<DirtyEntity> _hashesCache = new List<DirtyEntity>();
/// <summary>
/// Retrieve a hashcode for an object.
/// </summary>
/// <param name="objToCheck">Object to get hashcode for.</param>
/// <returns>hexadecimal hashcode</returns>
private static string GetHashRecord(object objToCheck)
{
// Create our hash-record of this object.
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, objToCheck);
MD5 md5 = MD5.Create();
ms.Position = 0;
byte[] hash = md5.ComputeHash(ms);
// Convert the hashvalue to a HEX string.
StringBuilder sb = new StringBuilder();
foreach (byte outputByte in hash)
{
// Convert each byte to a Hexadecimal lower case string
sb.Append(outputByte.ToString("X2").ToLower());
}
return sb.ToString();
}
/// <summary>
/// Check wether an object has changed somewhere or not.
/// </summary>
/// <param name="objToCheck">Object to check dirtyness of.</param>
/// <returns>True when something has been changed in the object.</returns>
public static bool IsDirty(object objToCheck)
{
if (objToCheck.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Length == 0)
{
throw new ArgumentException("Object is not serializable, mark it with [Serializable]", "objToCheck");
}
string name = string.Format("{0}.CloneHelper`1[[{1}]]", typeof(DirtyChecker).Namespace, objToCheck.GetType().AssemblyQualifiedName);
// Let's invoke the cloning mechanism
Type cloneType = Type.GetType(name);
if (cloneType == null)
throw new InvalidOperationException("Check the CloneHelper location, must be in same assembly and namespace as DirtyChecker!");
MethodInfo mi = cloneType.GetMethod("Clone", new Type[] { objToCheck.GetType(), typeof(CloneType) });
object toHashObject = mi.Invoke(null, new object[] { objToCheck, CloneType.ShallowCloning });
string hashRecord = GetHashRecord(toHashObject);
DirtyEntity entity = new DirtyEntity(objToCheck.GetHashCode(), hashRecord);
if (!_hashesCache.Contains(entity))
{
_hashesCache.Add(entity);
return false;
}
DirtyEntity foundEntity = _hashesCache[_hashesCache.IndexOf(entity)];
if (foundEntity.HashRecord.Equals(entity.HashRecord, StringComparison.InvariantCulture))
{
return false;
}
if (foundEntity.UpdateCascading)
{
foundEntity.HashRecord = hashRecord;
}
return true;
}
/// <summary>
/// Check wether an object has changed somewhere or not.
/// </summary>
/// <param name="objToCheck">Object to check dirtyness of.</param>
/// <param name="cascaded">If the record is dirty, take the new record as base-record for checking dirtyness.</param>
/// <returns>True when something has been changed in the object.</returns>
public static bool IsDirty(object objToCheck, bool cascaded)
{
if (objToCheck.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Length == 0)
{
throw new ArgumentException("Object is not serializable, mark it with [Serializable]", "objToCheck");
}
string name = string.Format("{0}.CloneHelper`1[[{1}]]", typeof(DirtyChecker).Namespace, objToCheck.GetType().AssemblyQualifiedName);
// Let's invoke the cloning mechanism
Type cloneType = Type.GetType(name);
MethodInfo mi = cloneType.GetMethod("Clone", new Type[] { objToCheck.GetType() });
object toHashObject = mi.Invoke(null, new object[] { objToCheck });
string hashRecord = GetHashRecord(toHashObject);
DirtyEntity entity = new DirtyEntity(objToCheck.GetHashCode(), hashRecord, cascaded);
if (!_hashesCache.Contains(entity))
{
_hashesCache.Add(entity);
return false;
}
DirtyEntity foundEntity = _hashesCache[_hashesCache.IndexOf(entity)];
if (foundEntity.HashRecord.Equals(entity.HashRecord, StringComparison.InvariantCulture))
{
return false;
}
if (foundEntity.UpdateCascading || cascaded)
{
foundEntity.HashRecord = hashRecord;
}
return true;
}
/// <summary>
/// Class for storing hashing data in the cache.
/// </summary>
private class DirtyEntity
{
private int _hashCodeOfObject;
private string _hashRecord;
private bool _updateCascading;
public DirtyEntity(int hc, string hr)
{
_hashCodeOfObject = hc;
_hashRecord = hr;
_updateCascading = true;
}
public DirtyEntity(int hc, string hr, bool cascading)
{
_hashCodeOfObject = hc;
_hashRecord = hr;
_updateCascading = cascading;
}
public int HashCodeOfObject
{
get { return _hashCodeOfObject; }
set { _hashCodeOfObject = value; }
}
public string HashRecord
{
get { return _hashRecord; }
set { _hashRecord = value; }
}
public bool UpdateCascading
{
get { return _updateCascading; }
set { _updateCascading = value; }
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is DirtyEntity)
{
DirtyEntity de = obj as DirtyEntity;
return de._hashCodeOfObject == this._hashCodeOfObject;
}
return base.Equals(obj);
}
}
}
bool IsDirty(object objToCheck) and bool IsDirty(object objToCheck, bool cascaded) the first one checks that the object is dirty against a previous version of the object, the previous version being the version when the IsDirty method is called for the first time.
The second method has the cascaded parameter, when it's set to true, then the method will compare against the previous called IsDirty result, each time it's called, the internal hashing-cache will be updated.
An example of usage:
// Create a list of 100 persons...
List<Person> list = CreatePersonsList(100);
// Initialize the dirty checker to know how the object looks like
bool dirty1 = DirtyChecker.IsDirty(list);
// Change something
list[0].Addresses[0].City = "Jut City";
// Check again to see that something has been changed.
bool dirty2 = DirtyChecker.IsDirty(list);
// Output:
// dirty1 = false;
// dirty2 = true;
List<Person> list = CreatePersonsList(100);
// Initialize the dirty checker to know how the object looks like
bool dirty1 = DirtyChecker.IsDirty(list);
// Change something
list[0].Addresses[0].City = "Jut City";
// Check again to see that something has been changed.
bool dirty2 = DirtyChecker.IsDirty(list);
// Output:
// dirty1 = false;
// dirty2 = true;
Voila, if it's useful, let me know please ;)
Cheers!