views:

310

answers:

3

I have a Linq-To-SQL object obj of type MyClass that I have loaded via my data context.

Now I want to force that object to save, even if no fields have actually changed, so that the save action can set off some triggers behind the scenes.

What's the easiest way of making my data context think that obj is dirty, so that a call to SubmitChanges() will cause obj to be saved?

+4  A: 

Just change a property to a dummy value and then back...

var value = obj.SomeField;
obj.SomeField = "dummy";
obj.SomeField = value;
dc.SubmitChanges();

Edit: let me take that back. The L2S change tracker won't be fooled by that. The easiest/cleanest/safest way if you don't want to change any of the existing columns is probably to add a new column and change that.

If you absolutely can't make any db changes (i.e. add a new column), then going at the change tracker with reflection might be an option. I haven't tried it, but it looks like the route to that would be (roughly):

1) the datacontext has a private member called services.
2) services points to a CommonDataServices which has a private member tracker and an internal member ChangeTracker (returning the former).
3) Change trackers has a GetTrackedObject internal method that returns a TrackedObject.
4) TrackedObject has a ConvertToModified method...

Edit #2: I just tested the reflection route above and it seems to work. E.g.:

            using (advWorksDataContext dc = new advWorksDataContext())
        {
            Employees emp = dc.Employees.FirstOrDefault();
            dc.MakeDirty(emp);
            dc.SubmitChanges();
        }

...and the implementation of MakeDirty is:

public static class DCExtensions
{
    internal static void MakeDirty(this System.Data.Linq.DataContext dc, object someEntity)
    {
        //get dc type
        Type dcType = dc.GetType();
        while (dcType != typeof(System.Data.Linq.DataContext))
        {
            dcType = dcType.BaseType;
        }  

        //get hold of the CommonDataServices thing in the DC
        System.Reflection.FieldInfo commonDataServicesField 
            = dcType.GetField("services", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
        object commonDataServices = commonDataServicesField.GetValue(dc);
        Type commonDataServicesType = commonDataServices.GetType();  

        //get hold of the change tracker
        System.Reflection.PropertyInfo changeTrackerProperty 
            = commonDataServicesType.GetProperty("ChangeTracker", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
        object changeTracker = changeTrackerProperty.GetValue(commonDataServices, null);
        Type changeTrackerType = changeTracker.GetType();  

        //get the tracked object method
        System.Reflection.MethodInfo getTrackedObjectMethod
            = changeTrackerType.GetMethod("GetTrackedObject", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
        object trackedObject = getTrackedObjectMethod.Invoke(changeTracker, new object[] { someEntity } );  

        //get the ConvertToModified method
        Type trackedObjectType = trackedObject.GetType();
        System.Reflection.MethodInfo convertToModifiedMethod
            = trackedObjectType.GetMethod("ConvertToModified", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);  

        //call the convert to modified method
        convertToModifiedMethod.Invoke(trackedObject, null);
    }
}
KristoferA - Huagati.com
Ah, I was wondering about that idea. Does Linq determine dirtiness by the fact that any property's "Set" method was called, or by comparing loaded values vs current values? From what you're saying, it's the former.
Shaul
I was too fast at replying - I was wrong... :)
KristoferA - Huagati.com
Aha. I also discovered that. Pity, that would have been the easiest for me. I don't much fancy all these dark arts of attacking private members of the data context... seems kinda dangerous. So I guess it'll be the "dirty" field for me after all. Thanks!
Shaul
A: 

You could try 2 submits if that's not going to break anything else? Therefore you can just use a variation of Kristofer's first answer:

Just change a property to a dummy value, save it, and then change back...
var value = obj.SomeField;
obj.SomeField = "dummy";
dc.SubmitChanges();
obj.SomeField = value;
dc.SubmitChanges();

Any good for your purposes?

h4xxr
Works, but results in unnecessary db roundtrips...
KristoferA - Huagati.com
Agreed, but is workable if he is unable to add any additional columns to db
h4xxr
A: 

This doesn't appear to work. I get an error on this line

object trackedObject = getTrackedObjectMethod.Invoke(changeTracker, new object[] { someEntity } );

It seems to fix it if I just do a deep copy on the object I'm trying to update. Inefficient, but no where near as bad as making an extra trip to the db.

Thanks for your help guys.

Lee