Differ is a fast generic object compare tool. It's based on SqlDiffer
from Dapper found here.
I needed a solution that could do change tracking on "stateless" (different instances) of the same type T
when persisting (i.e. original vs. modified/current). During an update, the original value of T
is pulled from the database and compared with the current object posted to the server. From here we can make business decisions based on what properties have changed and produce a human-readable audit log.
Snappshotter
works really well for objects are not serialized over the wire. When dealing with a REST facade and a statless web connection, during a POST
or PUT
back do the server, I needed a way to yank out the original object and compare it to the modified one- not simply for SQL generation but also for auditing purposes.
The real magic is in IL emitting (Specifically Emit
and CreateDelegate
methods). @SamSaffron, the original creator of Dapper (and Snapshotter
), explains at a high level how this paid dividends for Stack Overflow here.
TLDR: You don't pay a reflection penalty for reading object properties after the first comparison of any given T
; the "Differ" method is cached as a Func
.
With a little help from our friend AutoFixture we can fill our SimpleType
"object under test" with mock changes:
//in the real world, we'd be comparing the updated value with what's currently in the repository
var @out = fixture.CreateAnonymous<SimpleType>();
var outChanged = fixture.CreateAnonymous<SimpleType>();
var changes = Differ<SimpleType>.Diff(@out, outChanged).ToList();
That's it.
There's also a small helper method for creating more human-friendly messages useful for logging to an audit log (given changes
from above):
var auditText = diffs.ToFriendlyDescription();
which produces something simiar to the following:
SimpleType had the following properties changed:
SomeProp: 0 -> 1
Unfortunately, this code will not cure cancer or stupidity. Though pull requests are welcome.
It also won't compare "sub types"- that is, given:
public class SimpleType
{
public int SomeProp { get; set; }
}
public class ComplexType
{
public SimpleType SimpleTypeProp { get; set; }
}
... it won't compare SimpleTypeProp
. This would require some recursive diffing and I haven't encounted a need for it yet (of course contributions are welcome 😃).
- Dapper (specifically, Snapshotter mentioned earlier).
- Bookkeeper by @iristyle
There may be others... if you know of one, create an issue and I'll list it here.