What's the cleanest way to make a Linq object

2020-04-14 08:44发布

问题:

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?

回答1:

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);
    }
}


回答2:

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?