47

On Linq to SQL's DataContext I am able to call SubmitChanges() to submit all changes.

What I want is to somehow reject all changes in the datacontext and rollback all changes (preferable without going to the database).

Is this possible?

Luke
  • 18,585
  • 24
  • 87
  • 110
Thomas Jespersen
  • 11,493
  • 14
  • 47
  • 55

11 Answers11

30

Why not discard the data context and simply replace it with a new instance?

Haacked
  • 58,045
  • 14
  • 90
  • 114
  • 11
    Because I might have objects fetched through the context, which I might want to change/use at a later time. If I discard the data context I have to fetch those objects again. This is a windows service, where the datacontext live for a long time. – Thomas Jespersen Nov 04 '08 at 08:38
  • 8
    DataContext is meant to be a used in a _unit of work_ approach and is thus not intended to live for a long time. see: [Lifetime of a LINQ to SQL DataContext](http://blogs.msdn.com/b/dinesh.kulkarni/archive/2008/04/27/lifetime-of-a-linq-to-sql-datacontext.aspx) – mbx Apr 05 '13 at 13:02
20
public static class DataContextExtensions
{
    /// <summary>
    ///     Discard all pending changes of current DataContext.
    ///     All un-submitted changes, including insert/delete/modify will lost.
    /// </summary>
    /// <param name="context"></param>
    public static void DiscardPendingChanges(this DataContext context)
    {
        context.RefreshPendingChanges(RefreshMode.OverwriteCurrentValues);
        ChangeSet changeSet = context.GetChangeSet();
        if (changeSet != null)
        {
            //Undo inserts
            foreach (object objToInsert in changeSet.Inserts)
            {
                context.GetTable(objToInsert.GetType()).DeleteOnSubmit(objToInsert);
            }
            //Undo deletes
            foreach (object objToDelete in changeSet.Deletes)
            {
                context.GetTable(objToDelete.GetType()).InsertOnSubmit(objToDelete);
            }
        }
    }

    /// <summary>
    ///     Refreshes all pending Delete/Update entity objects of current DataContext according to the specified mode.
    ///     Nothing will do on Pending Insert entity objects.
    /// </summary>
    /// <param name="context"></param>
    /// <param name="refreshMode">A value that specifies how optimistic concurrency conflicts are handled.</param>
    public static void RefreshPendingChanges(this DataContext context, RefreshMode refreshMode)
    {
        ChangeSet changeSet = context.GetChangeSet();
        if (changeSet != null)
        {
            context.Refresh(refreshMode, changeSet.Deletes);
            context.Refresh(refreshMode, changeSet.Updates);
        }
    }
}

Refer to Linq to SQL - Discard Pending Changes

Community
  • 1
  • 1
Teddy
  • 1,407
  • 10
  • 19
  • Wow I love this, this work so well in my C# DotNet 4.0 code. Teddy you rule! I do not understand why Microsoft doesn't have this built into the datacontext in the first place. I did a db.SubmitChanges() right after the DiscardPendingChanges() to verify if no changes happened. Simply Gorgeous. – Omzig Dec 07 '13 at 02:08
  • 1
    @Omzig This can be useful in a long-lifetime context. When in a short-lifetime context, another approch is simply re-initialize data context. So I think that's the reason why Microsoft doesn't include this method in entity framework, they assume in most cases it's a short-lifetime context. – Teddy Dec 09 '13 at 10:51
11

In .net 3.0 use the db.GetChangeSet().Updates.Clear() for updated, db.GetChangeSet().Inserts.Clear() for new or db.GetChangeSet().Deletes.Clear() for deleted items.

In .net 3.5 and above the result of GetChangeSet() is now readonly, loop the collection in for or foreach and refresh every ChangeSet table like also macias wrote in his comment.

Alexander Zwitbaum
  • 4,776
  • 4
  • 48
  • 55
  • 19
    This was the accepted answer? This doesn't work -- the Updates/Inserts/Deletes collections are read-only. And the MSDN documentation says that these collections are "computed at the time of the call" (of GetChangeSet()). http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.getchangeset.aspx – shaunmartin Jan 14 '10 at 00:37
  • Does not work for me (VS2008) -- I get exceptions "collection is read-only". But putting it into foreach loop and refreshing each table which is in ChangeSet -- works. – greenoldman Jan 25 '10 at 09:53
  • Yes, this is only read only, this won't work as it's coming from a GetChangeSet() get = readonly – franko_camron Dec 16 '11 at 00:20
  • PLEASE READ FIRST! If you dont' like the answer, please choose another one or write your comment, but PLEASE DON'T DOWNVOTE this answer. I can't delete it, because it was accepted. – Alexander Zwitbaum Nov 28 '12 at 16:09
9

Calling Clear() on the Updates, Deletes and Inserts collection does not work.

GetOriginalEntityState() can be useful, but it only gives the IDs for foreign key relationships, not the actual entities so you're left with a detached object.

Here's an article that explains how to discard changes from the data context: http://graemehill.ca/discard-changes-in-linq-to-sql-datacontext

EDIT: Calling Refresh() will undo updates, but not deletes and inserts.

Graeme Hill
  • 623
  • 6
  • 11
9

As Haacked said, just drop the data context.

You probably shouldn't keep the data context alive for a long time. They're designed to be used in a transactional manner (i.e. one data context per atomic work unit). If you keep a data context alive for a long time, you run a greater risk of generating a concurrency exception when you update a stale entity.

Richard Poole
  • 3,946
  • 23
  • 29
4

The Refresh will work, however you have to give the entities you want to reset.

For example

dataContext.Refresh(RefreshMode.OverwriteCurrentValues, someObject);
David Basarab
  • 72,212
  • 42
  • 129
  • 156
  • 1
    this will however hit the database to get the most recent values, it doens't just revert to the old cached values. – Lucas Feb 16 '09 at 18:22
3

You can use the GetOriginalEntityState(..) to get the original values for the objects e.g. Customers using the old cached values.

You can also iterate through the changes e.g. updates and refresh the specific objects only and not the entire tables because the performance penalty will be high.

foreach (Customer c in MyDBContext.GetChangeSet().Updates)
        {
            MyDBContext.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, c);
        }

this will revert the changes using persisted data in the database.

Another solution is to dump the datacontext you use, using Dispose().

In any case it is a good practice to override the Insert and Remove methods in the collection of e.g. Customers you use and add e.g. an InsertOnSubmit() call. This will resolve your issue with pending Insertions and Deletions.

Theodore Zographos
  • 2,215
  • 1
  • 24
  • 23
1

Excellent write up on here, but here is a copy and paste of the code used.

Public Sub DiscardInsertsAndDeletes(ByVal data As DataContext)
    ' Get the changes
    Dim changes = data.GetChangeSet()

    ' Delete the insertions
    For Each insertion In changes.Inserts
        data.GetTable(insertion.GetType).DeleteOnSubmit(insertion)
    Next

    ' Insert the deletions
    For Each deletion In changes.Deletes
        data.GetTable(deletion.GetType).InsertOnSubmit(deletion)
    Next
End Sub

Public Sub DiscardUpdates(ByVal data As DataContext)
    ' Get the changes
    Dim changes = data.GetChangeSet()

    ' Refresh the tables with updates
    Dim updatedTables As New List(Of ITable)
    For Each update In changes.Updates
        Dim tbl = data.GetTable(update.GetType)
        ' Make sure not to refresh the same table twice
        If updatedTables.Contains(tbl) Then
            Continue For
        Else
            updatedTables.Add(tbl)
            data.Refresh(RefreshMode.OverwriteCurrentValues, tbl)
        End If
    Next
End Sub
Scott
  • 11,046
  • 10
  • 51
  • 83
1

My application is outlook style with a icon to select an active form (ListBox). Before allowing the user to change their context they have to accept changes or discard them.

var changes = db.GetChangeSet();
if ((changes.Updates.Count > 0) || (changes.Inserts.Count > 0) || (changes.Deletes.Count > 0))
{
    if (MessageBox.Show("Would you like to save changes?", "Save Changes", MessageBoxButton.YesNo)  == MessageBoxResult.Yes)
    {
        db.SubmitChanges();
    } else
    {
        //Rollback Changes
        foreach (object objToInsert in changes.Inserts)
        {
            db.GetTable(objToInsert.GetType()).DeleteOnSubmit(objToInsert);
        }
        foreach (object objToDelete in changes.Deletes)
        {
            db.GetTable(objToDelete.GetType()).InsertOnSubmit(objToDelete);
        }
        foreach (object objToUpdate in changes.Updates)
        {
            db.Refresh(RefreshMode.OverwriteCurrentValues, objToUpdate);
        }
        CurrentForm.SetObject(null); //Application Code to Clear active form
        RefreshList(); //Application Code to Refresh active list
    }
}
Kyght
  • 617
  • 7
  • 8
0

Here is how I did it. I just followed Teddy's example above and simplified it. I have one question though, why even bother with the refresh on the DELETES?

  public static bool UndoPendingChanges(this NtsSuiteDataContext dbContext)
  {
     if (dbContext.ChangesPending())
     {
        ChangeSet dbChangeSet = dbContext.GetChangeSet();

        dbContext.Refresh(RefreshMode.OverwriteCurrentValues, dbChangeSet.Deletes);
        dbContext.Refresh(RefreshMode.OverwriteCurrentValues, dbChangeSet.Updates);

        //Undo Inserts
        foreach (object objToInsert in dbChangeSet.Inserts)
        {
           dbContext.GetTable(objToInsert.GetType()).DeleteOnSubmit(objToInsert);
        }

        //Undo deletes
        foreach (object objToDelete in dbChangeSet.Deletes)
        {
           dbContext.GetTable(objToDelete.GetType()).InsertOnSubmit(objToDelete);
        }
     }

     return true;
  }
M Moore
  • 66
  • 1
  • 8
-1

//This works for me, 13 years later using Dotnet (Core) 6:

dbContext.ChangeTracker.Clear();

Daniel Barnes
  • 163
  • 2
  • 9