Can you convince a DataContext to treat a column as always dirty?

后端 未结 5 712
花落未央
花落未央 2020-12-06 02:39

Is there a way to force LINQ-to-SQL to treat a column as dirty? Globally would suffice....

Basically, I\'ve got a problem with some audit code on a legacy system tha

相关标签:
5条回答
  • 2020-12-06 03:16

    Unfortunately, I think you will have to use a new DataContext

    0 讨论(0)
  • 2020-12-06 03:16

    If you want to go down the [dirty] reflection route, you could try something along the lines of:

    1) Override SubmitChanges
    2) Go through the change set
    3) Use reflection to get hold of the change tracker for each updated object (see What's the cleanest way to make a Linq object "dirty"? )
    4) Make the column dirty (there's a dirtyMemberCache field in the StandardTrackedObject class)

    0 讨论(0)
  • 2020-12-06 03:22

    Details at: http://blog.benhall.me.uk/2008/01/custom-insert-logic-with-linq-to-sql.html

    You can override the default update behavior. There are 2 ways of doing this

    The easiest is to create a stored procedure (if you can't do that on your database, the second method should work) which takes the parameters of your customer object and updates the table:

    1. Create the stored procedure that has a parameter for each property of Customers that needs to be updated.
    2. Import that stored procedure into your Linq To SQL DBML file.
    3. Now you can right click on your customers entity and select "Configure Behavior".
    4. Select your Customers class under the Class dropdown and "Update" on the behavior drop down.
    5. Select the "Customize" radio button and choose the stored procedure you just created.
    6. Now you can map class's properties to the stored procedure.

    Now when Linq to SQL tries to update your Customers table, it'll use your stored procedure instead. Just be careful because this will override the update behavior for Customers everywhere.

    The second method is to use partial methods. I haven't actually tried this, so hopefully this might just give you some general direction to pursue. In a partial class for your data context, make a partial method for the update (It'll be Update_____ with whatever your class is in the blank. I'd suggest searching in your data context's designer file to make sure you get the right one)

    public partial SomeDataContext
    {
        partial void UpdateCustomer(Customer instance)
        {
           // this is where you'd do the update, but I'm not sure exactly how it's suppose to work, though. :(
        }
    }
    
    0 讨论(0)
  • 2020-12-06 03:32

    Based on KristoferA's answer, I ended up with something like below; this is evil and brittle (reflection often is), but may have to suffice for now. The other side of the battle is to change the triggers to behave:

    partial class MyDataContext // or a base-class
    {
        public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
        {
            this.MakeUpdatesDirty("UpdatedBy", "Updated_By");
            base.SubmitChanges(failureMode);
        }
    }
    public static class DataContextExtensions
    {
        public static void MakeUpdatesDirty(
            this DataContext dataContext,
            params string[] members)
        {
            if (dataContext == null) throw new ArgumentNullException("dataContext");
            if (members == null) throw new ArgumentNullException("members");
            if (members.Length == 0) return; // nothing to do
            foreach (object instance in dataContext.GetChangeSet().Updates)
            {
                MakeDirty(dataContext, instance, members);
            }
        }
        public static void MakeDirty(
            this DataContext dataContext, object instance ,
            params string[] members)
        {
            if (dataContext == null) throw new ArgumentNullException("dataContext");
            if (instance == null) throw new ArgumentNullException("instance");
            if (members == null) throw new ArgumentNullException("members");
            if (members.Length == 0) return; // nothing to do
            const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
            object commonDataServices = typeof(DataContext)
                .GetField("services", AllInstance)
                .GetValue(dataContext);
            object changeTracker = commonDataServices.GetType()
                .GetProperty("ChangeTracker", AllInstance)
                .GetValue(commonDataServices, null);
            object trackedObject = changeTracker.GetType()
                .GetMethod("GetTrackedObject", AllInstance)
                .Invoke(changeTracker, new object[] { instance });
            var memberCache = trackedObject.GetType()
                .GetField("dirtyMemberCache", AllInstance)
                .GetValue(trackedObject) as BitArray;
    
            var entityType = instance.GetType();
            var metaType = dataContext.Mapping.GetMetaType(entityType);
            for(int i = 0 ; i < members.Length ; i++) {
                var member = entityType.GetMember(members[i], AllInstance);
                if(member != null && member.Length == 1) {
                    var metaMember = metaType.GetDataMember(member[0]);
                    if (metaMember != null)
                    {
                        memberCache.Set(metaMember.Ordinal, true);
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 03:38

    The following works for me. Note though that I'm using the linq2sql provider from DevArt, but that may not matter:

    MyDataContext dc = new MyDataContext();
    
    Message msg = dc.Messages.Single(m => m.Id == 1);
    Message attachingMsg = new Message();
    attachingMsg.Id = msg.Id;
    
    dc.Messages.Attach(attachingMsg);
    
    attachingMsg.MessageSubject = msg.MessageSubject + " is now changed"; // changed
    attachingMsg.MessageBody = msg.MessageBody; // not changed
    dc.SubmitChanges();
    

    This produces the following sql:

    UPDATE messages SET messageSubject = :p1, messageBody = :p2 WHERE Id = :key1
    

    So, messageBody is updated even though its value is not changed. One other change necessary for this, is that for each property (column) of my entity Message, I have set UpdatedCheck = UpdateCheck.Never, except for its ID, which is the primary key.

    0 讨论(0)
提交回复
热议问题