My previous post got me thinking, how does the DataContext know if a partial method has been implemented in order to direct the call to the partial method instead of its own internal method? It’s interesting because, if the partial method has not been implemented, all the calls are removed at compile time, and the names of the methods are quite dynamic (InsertObject…(object o)) making it difficult to call this ‘known’ method. The methods are defined by the designer, so the internal framework will not know anything about these methods. While I am taking in terms of how Linq does this, this could be valid for any application.
So, in situations like this there is only one option – Lutz Roeder’s Reflector. The Linq implementation is massive and takes a little bit of time to understand how its all been put together. There is a video by some of members of the Linq team talking about the pipeline processing of Linq to SQL – watch it here
In Linq to SQL, every object returned from the database is tracked by an internal Lookup Table. There are many reasons why Linq tracks all of the objects, such as caching and change management. These objects are of type StandardTrackedObject, which inherits from TrackedObject. If you look in reflector, you will have a list of ways these objects are exposed.
One of the main ways they are exposed is by a series of methods on the System.Data.Linq.ChangeDirector.StandardChangeDirector object. Some of the methods are DynamicDelete, DynamicInsert, DynamicUpdate which take a TrackedObject as a parameter. These methods are used is various places, but one place is within System.Data.Linq.DataContext.ExecuteDynamicUpdate(), there are equivalent delete and insert methods but I will continue to talk in terms of Update. This means that this method is called to update the object and execute the command against the database. As mentioned in the last post, this is the method we need to call in order to execute the command within a partial method, so this doesn’t decide which method to call.
One of the other places DynamicUpdate is used is by StandardChangeDirector.Update() which in turn is called by ChangeProcessor.SubmitChanges(). This means that when DataContext.SubmitChanges() is called, StandardChangeDirector.Update() is also called later in the process, if updates are required.
StandardChangeDirector.Update() is the important method that decides if the internal ExecuteDynamicUpdate() should be called, or if the call should be passed to the appropriate partial method.
The method checks to see if a property on the TrackedObject is null, this property is called UpdateMethod that returns a Reflection.MethodInfo object. If it is null, then it calls ExecuteDynamicUpdate() otherwise the method relating to the MethodInfo is invoked using the Invoke command. Just a bit of background on the MethodInfo object, when using reflection you can access any field or method at runtime without knowing about it at compile time. The FieldInfo and MethodInfo objects are a bridge between the calling code and the method on the object, by calling MethodInfo.Invoke() the method can be indirectly invoked. The MethodInfo object would be null, if the method does not exist on the object, or this case if the partial method hasn’t been implemented. This is how Linq can override logic and execute a partial method instead of its own internal method without having to worry if it has been implemented by the developer.
But this still leaves a question unanswered. How does it know the name of the method to execute or even how it finds out if it has been implemented? The framework knows that each object in a DataContext has three partial methods and the prefix for each of them but not the full name.
The answer is clever, it tries to find the method based on Prefix (“Update”) + this.RowType.Name. RowType.Name is the name of the entity, so if the partial method has been implemented the object will have a method with that name and if its found it will populate the property ready to be invoked, if the method hasn’t been implemented, it wouldn’t be found and so will be null.
Simple but really clever! Hope you found this interesting, I know I did.