Search code examples
.netlinq-to-sqlc#-3.0datacontext

Get object's DataContext


If I have the LINQ objects:

public class SampleDataContext : DataContext {
    public Table<Customer> Customers { get { return this.GetTable<Customer>(); } }
    public SampleDataContext( string connectionString ) : base( connectionString ) { }
}

[Table( Name="dbo.tblCustomers" )]
public class Customer {
    private Guid? customerID;
    [Column( Storage="customerID", DbType="uniqueidentifier NOT NULL", IsPrimaryKey=true )]
    public Guid? CustomerID {
        get { return this.customerID; }
        set { this.customerID = value; }
    }

    private string customerName;
    [Column( Storage = "customerName", DbType = "nvarchar(255) NOT NULL" )]
    public string CustomerName {
        get { return this.customerName; }
        set { this.customerName = value; }
    }
}

and somewhere else in application:

public static void DoSomethingWithCustomer( Customer customer ) {
    // some operations
    // now, I want save changes to the database
}

how can I get instance of DataContext which tracks changes of the "customer" object?

Edit: Why I don't want pass the DataContext into method.

1) Passing always 2 objects instead of 1 is "ugly" pattern for whole application.

  • Methods will need next parameter for every business object.
  • Collection will needs changed from "List" to "List>".

Both points will more hard to maintain - developer must every-time sets the correct instance of DataContext (easy to create a bug), despite the DataContext know that the concrete object is(or not) attached to another DataContext.

2) I want (current version of application use it) process "any" business logic on collection of objects which came from different "places" ( floating windows by drag & drop for example ).

Currentyl we use custom typed DataSets, so informations about changes are in the data rows (DataRow = business object) and wasn't problem to get it, or create a clone and then save it into database.


Solution

  • 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: