Get object's DataContext
Kevin - I feel your pain... when you are building business logic around your business objects, there are times when you simply have to have access to the DataContext to which an object belongs, since not knowing the DataContext menas having to put your code in places that reduce the maintainability of your code.
I wrote the following code (VB, I'm afraid), which presents a Context property which can be placed onto a data object, and then used to return the DataContext (if any) that the object is attached to.
Private Const StandardChangeTrackerName As String = "System.Data.Linq.ChangeTracker+StandardChangeTracker"
Private _context As DataClasses1DataContext
Public Property Context() As DataClasses1DataContext
Get
Dim hasContext As Boolean = False
Dim myType As Type = Me.GetType()
Dim propertyChangingField As FieldInfo = myType.GetField("PropertyChangingEvent", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim propertyChangingDelegate As PropertyChangingEventHandler = propertyChangingField.GetValue(Me)
Dim delegateType As Type = Nothing
For Each thisDelegate In propertyChangingDelegate.GetInvocationList()
delegateType = thisDelegate.Target.GetType()
If delegateType.FullName.Equals(StandardChangeTrackerName) Then
propertyChangingDelegate = thisDelegate
hasContext = True
Exit For
End If
Next
If hasContext Then
Dim targetField = propertyChangingDelegate.Target
Dim servicesField As FieldInfo = targetField.GetType().GetField("services", BindingFlags.NonPublic Or BindingFlags.Instance)
If servicesField IsNot Nothing Then
Dim servicesObject = servicesField.GetValue(targetField)
Dim contextField As FieldInfo = servicesObject.GetType.GetField("context", BindingFlags.NonPublic Or BindingFlags.Instance)
_context = contextField.GetValue(servicesObject)
End If
End If
Return _context
End Get
Set(ByVal value As DataClasses1DataContext)
_context = value
End Set
End Property
Here is a C# version:
public DataContext GetMyDataContext()
{
// Find the StandardChangeTracker listening to property changes on this object.
// If no StandardChangeTracker is listening, then this object is probably not
// attached to a data context.
var eventField = this.GetType().GetField("PropertyChangingEvent", BindingFlags.NonPublic | BindingFlags.Instance);
var eventDelegate = eventField.GetValue(this) as Delegate;
if (eventDelegate == null)
return null;
eventDelegate = eventDelegate.GetInvocationList().FirstOrDefault(
del => del.Target.GetType().FullName == "System.Data.Linq.ChangeTracker+StandardChangeTracker");
if (eventDelegate == null)
return null;
// Dig through the objects to get the underlying DataContext.
// If the following fails, then there was most likely an internal change
// to the LINQ-to-SQL framework classes.
var targetField = eventDelegate.Target;
var servicesField = targetField.GetType().GetField("services", BindingFlags.NonPublic | BindingFlags.Instance);
var servicesObject = servicesField.GetValue(targetField);
var contextField = servicesObject.GetType().GetField("context", BindingFlags.NonPublic | BindingFlags.Instance);
return (DataContext)contextField.GetValue(servicesObject);
}
Take care to note that the object can only locate it's DataContext if it is currently attached to the context with ChangeTracking switched on. This property relies on the fact that the DataContext has subscribed to the object's OnPropertyChanging event to monitor changes over the lifespan of the object.
If this was helpful, please up-vote this post.
For more info on using reflection to find event handlers:
- http://weblogs.asp.net/avnerk/archive/2007/03/29/reflecting-over-an-event.aspx
- https://web.archive.org/web/20131213074318/http://bobpowell.net/eventsubscribers.aspx
Part of the fun of POCO is that you can't be sure that the object knows who is tracking him. If the object has data-aware / lazy-loading properties, then you might be able to trace the context via reflection, but in reality this would be a mess. It would be far cleaner to simply pass the data-context to the code that needs it.
The simplest thing to do is to pass the DataContext into your method.
However, you may also consider changing your design so that you follow the rule that "a single method should have only one purpose", in which case you wouldn't want to "Save" in the same method that you "Modify".