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?

Daniel Brückner
  • 59,031
  • 16
  • 99
  • 143
  • 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. – Alex James Mar 18 '10 at 05:56

6 Answers6

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;
            }
        }
    }
jjxtra
  • 20,415
  • 16
  • 100
  • 140
Garry English
  • 5,070
  • 1
  • 36
  • 23
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.

Daniel Brückner
  • 59,031
  • 16
  • 99
  • 143
  • 8
    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 – Crab Bucket Nov 18 '11 at 13:11
  • 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. – Mark Hildreth Feb 18 '14 at 19:41
5

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

Jess
  • 23,901
  • 21
  • 124
  • 145
d.marzo
  • 319
  • 4
  • 5
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

Basic
  • 26,321
  • 24
  • 115
  • 201
  • 2
    Using an unit of work still does not detach objects from the context. It also does not answer the question. – Agent Shark Jul 15 '15 at 11:24
  • @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 – Basic Jul 15 '15 at 14:29
  • 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. – Agent Shark Jul 22 '15 at 15:04
1
var entries = ((DbContext)ef).ChangeTracker.Entries();
foreach (var entry in entries)
{
    entry.State = System.Data.EntityState.Detached;
}
oɔɯǝɹ
  • 7,219
  • 7
  • 58
  • 69
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;
                }
            }
        }
Arthur Silva
  • 878
  • 1
  • 10
  • 15