I'm revisiting my class tracking (dirty logic), which I wrote last year. Currently I have an uber base class that deals with all the state tracking, but each property whos values I need to track needs to stick to the standard get { return _x; } set { _isDirty = true; _x = value; }
way of working.
After playing with Entity Framework and reading up on the Proxy Pattern, I was hoping there was a nicer way to implement my IsDIrty Logic whilst being able to make use of auto implemented properties?
To be completely honest, I haven't a clue of what I'm talking about. Is there a way I can do something like the following:
public class Customer : ITrackable
{
[TrackState(true)] // My own attribute
public virtual string Name { get;set;}
[TrackState(true)]
public virtual int Age { get;set;}
// From ITrackable
public bool IsDirty { get; protected set; }
}
And then implement a dynamic proxy which will use reflection (or another magical solution) to call another method first before setting the values on the properties with the TrackState
attribute.
Obviously I could easily do this by creating a phyiscal proxy class and use IoC:
public class CustomerProxy : Customer
{
Customer _customer;
public override string Name
{
get { return _customer.Name; }
set { IsDirty = true; return _customer.Name; }
}
// Other properties
}
But I don't fancy having to do this for every object, otherwise there's no benefit from my existing solution. Hope someone can satisfy my curiosi开发者_如何学Pythonty, or at least tell me how EF achieves it.
Castle's DynamicProxy does exactly this: http://www.castleproject.org/dynamicproxy/index.html
Allows you to provide an interceptor:
public void Intercept(IInvocation invocation)
{
// Call your other method first... then proceed
invocation.Proceed();
}
You get access to the MethodInfo object via invocation.Method. You may override the return value by setting invocation.ReturnValue. And you can access (and override) the arguments.
PostSharp could help.
Or if you're feeling like it, you can write your own IL-rewriter for this. Mono.Cecil is a great library that'll make it a breeze. Here is quick concoction:
class Program {
static ModuleDefinition _module;
static void Main(string[] args) {
// the argument is the assembly path
_module = ModuleDefinition.ReadModule(args[0]);
var trackables = _module.Types.
Where(type => type.Interfaces.Any(tr => tr.Name == "ITrackable"));
var properties = trackables.SelectMany(type => type.Properties);
var trackableProperties = properties.
Where(property => property.CustomAttributes.
Any(ca => ca.Constructor.DeclaringType.Name == "TrackStateAttribute"));
trackableProperties.
Where(property => property.SetMethod != null).
ToList().
ForEach(property => CallIsDirty(property.SetMethod));
_module.Write(args[0]);
}
private static void CallIsDirty(MethodDefinition setter) {
Console.WriteLine(setter.Name);
var isDirty = setter.DeclaringType.Methods.
Single(method => method.Name == "set_IsDirty");
var reference = new MethodReference(isDirty.Name,
_module.Import(typeof(void))) {
DeclaringType = setter.DeclaringType,
HasThis = true,
CallingConvention = MethodCallingConvention.Default
};
reference.Parameters.Add(new ParameterDefinition(
_module.Import(typeof(bool))));
var IL = setter.Body.GetILProcessor();
var param0 = IL.Create(OpCodes.Ldarg_0);
var param1 = IL.Create(OpCodes.Ldc_I4_1);
var call = IL.Create(OpCodes.Call, reference);
IL.InsertBefore(setter.Body.Instructions[0], call);
IL.InsertBefore(setter.Body.Instructions[0], param1);
IL.InsertBefore(setter.Body.Instructions[0], param0);
}
}
It uses these helpers:
public class TrackStateAttribute : Attribute { }
public interface ITrackable { bool IsDirty { get; } }
Example code:
public class Customer : ITrackable {
[TrackState] public string Name { get; set; }
[TrackState] public int Age { get; set; }
public bool IsDirty { get; protected set; }
}
It's assumed that the IsDirty
property will also have a setter.
精彩评论