47

I am adding several entities to an object context.

try
{
    forach (var document in documents)
    {
        this.Validate(document); // May throw a ValidationException.

        this.objectContext.AddToDocuments(document);
    }

    this.objectContext.SaveChanges();
}
catch
{
    // How to clean-up the object context here?

    throw;
}

If some of the documents pass the the validation and one fails, all documents that passed the validation remain added to the object context. I have to clean-up the object context because it may be reused and the following can happen.

var documentA = new Document { Id = 1, Data = "ValidData" };
var documentB = new Document { Id = 2, Data = "InvalidData" };
var documentC = new Document { Id = 3, Data = "ValidData" };

try
{
    // Adding document B will cause a ValidationException but only
    // after document A is added to the object context.
    this.DocumentStore.AddDocuments(new[] { documentA, documentB, documentC });
}
catch (ValidationException)
{
}

// Try again without the invalid document B. This causes an exception because
// of a duplicate primary key - document A with id 1 is added a second time.
this.DocumentStore.AddDocuments(new[] { documentA, documentC });

This will again add document A to the object context and in consequence SaveChanges() will throw an exception because of a duplicate primary key.

So I have to remove all already added documents in the case of an validation error. I could of course perform the validation first and only add all documents after they have been successfully validated but sadly this does not solve the whole problem - if SaveChanges() fails, all documents still remain added but unsaved.

I tried to detach all objects returned by

 this.objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added)

but I am getting a exception stating that the object is not attached. So how do I get rid of all added but unsaved objects?

1
  • 8
    I would recommend not reusing an objectcontext. There are scenarios where it makes sense, but they are very rare. Remember the longer an ObjectContext is used the more bloated it gets, and if it gets into an inconsistent state (i.e. something get's partially done) you might get some inconsistent side effects too. Commented Mar 18, 2010 at 5:56

6 Answers 6

47

Daniel's answer worked for me, however the EntityFramework API is different in version 6+. Here is a method I added to my custom repository container that will detach all entities from the DbContext's ChangeTracker:

    /// <summary>
    /// Detaches all of the DbEntityEntry objects that have been added to the ChangeTracker.
    /// </summary>
    public void DetachAll() {

        foreach (DbEntityEntry dbEntityEntry in this.Context.ChangeTracker.Entries().ToArray()) {

            if (dbEntityEntry.Entity != null) {
                dbEntityEntry.State = EntityState.Detached;
            }
        }
    }
Sign up to request clarification or add additional context in comments.

Comments

40

It was just a trivial bug but I am going to leave the question here - maybe it helps others.

I had the following

var objectStateEntries = this.objectContext
                             .ObjectStateManager
                             .GetObjectStateEntries(EntityState.Added);

foreach (var objectStateEntry in objectStateEntries)
{
    this.objectContext.Detach(objectStateEntry);
}

while I wanted the following

foreach (var objectStateEntry in objectStateEntries)
{
    this.objectContext.Detach(objectStateEntry.Entity);
}

and couldn't see it.

2 Comments

As an aside it's possible use bitwise flags to do var objectStateEntries = this._context.ObjectStateManager .GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged); to detach all the attached objects
Note that in some cases, objectStateEntry.Entity will be null (objectStateEntity describes a relationship to no entity), so you should probably put a null check in there.
5

Since EF 5.0 use ChangeTracker.Clear() which clears the DbContext of all tracked entities.

Comments

1

A little late to the party but have you considered the Unit of Work pattern?

In short, it would allow you to have a transaction which you could then rollback (dispose) or commit (savechanges)

NB: That's transaction as in grouping of changes into one operation, not as in a sql BEGIN TRAN. That's all still handled for you by the SaveChanges() method.

Something like:

Public Class UnitOfWork
    Implements IUnitOfWork
    Implements IDisposable
    Private ReadOnly Property ObjectContext As Interfaces.IObjectContext

    Public Sub New(ByVal objectContext As Interfaces.IObjectContext)
        _objectContext = objectContext
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        If _objectContext IsNot Nothing Then
            _objectContext.Dispose()
        End If
        GC.SuppressFinalize(Me)
    End Sub

    Public Sub Commit() Implements IUnitOfWork.Commit
        _objectContext.SaveChanges()
    End Sub
End Class

Every time you create a unit of work, it takes in an objectcontext - We're using Unity to generate these so that a new one is created if the old one has been disposed

3 Comments

Using an unit of work still does not detach objects from the context. It also does not answer the question.
@AgentShark Depends entirely on how you implement your Rollback method. Also, there's no point duplicating the correct code in the accepted answer which shows explicitly how to detach objects
I reread the question. Yes forcing updates through a unit of work would be useful. However in EF, you have to be careful when adding object with related entities. There exists different ways with different behaviours to add/save objects to the context.
1
var entries = ((DbContext)ef).ChangeTracker.Entries();
foreach (var entry in entries)
{
    entry.State = System.Data.EntityState.Detached;
}

Comments

1

For those using Entity Framework Core 1.0 RC2+, I updated Garrys' answer.

Reference

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;

Updated code

        /// <summary>
        /// Detaches all of the EntityEntry objects that have been added to the ChangeTracker.
        /// </summary>
        public void DetachAll()
        {
            foreach (EntityEntry entityEntry in this.Context.ChangeTracker.Entries().ToArray())
            {
                if (entityEntry.Entity != null)
                {
                    entityEntry.State = EntityState.Detached;
                }
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.