2

I'm running into an InvalidOperationException because "An entity object cannot be referenced by multiple instances of IEntityChangeTracker." on the first line of EntityFrameWorkRepository.Create().

I know this is due to having multiple database contexts, but in this case I'm a bit lost as the code has no obvious second context since all database access goes through a designated object whose sole purpose is managing database contexts. This was done as the web application in question is fairly interactive and so the user is constantly creating new objects which must be saved in the database. This was causing issues with the previous design, which used locking and a single context, thus the code was refactored and works, except for the method in question.

EF class:

public class EntityFrameWorkRepository<TKey, TEntity> : IDisposable, IRepository<TKey,TEntity> where TEntity: class
{
    private readonly IDbContext _context;
    private IDbSet<TEntity> _entities;

    public EntityFrameWorkRepository()
    {
        _context = new ApplicationDbContext();
    }

    private IDbSet<TEntity> Entities
    {
        get { return _entities ?? (_entities = _context.Set<TEntity>()); }
    }

    public void Create(TEntity entity)
    {
        Entities.Add(entity);
        _context.SaveChanges(); 
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

The service object used for all DB access:

public class Service : IService
{
    public const string Persistance = "Persist";
    public const int CacheTaskSeconds = 300; //Check every 5 minutes
    public const double IdleMinutes = 30.0; 

    private readonly IKvpRepository<int, SimulationCollection> _simulationCollectionAppStateRepository;
    private readonly UserManager<ApplicationUser> _userManager;

    public Service(IKvpRepository<int, SimulationCollection> simulationCollectionAppStateRepository)
    {
        _userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
        AddTaskToCache(Persistance, CacheTaskSeconds);
    }

    public SimulationCollection CreateCollection(Guid userId, string name, string description)
    {
        using (var _simulationCollectionEFRepository = new EntityFrameWorkRepository<int, SimulationCollectionEntity>())
        {
            var applicationUser = _userManager.FindById(userId.ToString());
            if (applicationUser == null)
                throw new ArgumentOutOfRangeException("ApplicationUser matching userId doesn't exist");
            var collectionEntity = new SimulationCollectionEntity(applicationUser, name, description);
            _simulationCollectionEFRepository.Create(collectionEntity);
            return collection; 
        }
    }
}

The object I'm trying to add to the database:

public class SimulationCollectionEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int Id { get; set; }
    public string Name { get; set; }
    public virtual ApplicationUser User { get; set; }
    public DateTime DateCreated { get; set; }
    public string Description { get; set; }

    [ForeignKey("SimulationCollectionEntityId")]
    public virtual ICollection<SimulationEntity> Simulations { get; set; }

    [Obsolete("Only needed for serialization and materialization", true)]
    public SimulationCollectionEntity() {}

    public SimulationCollectionEntity(ApplicationUser currentUser, string name, string description)
    {
        User = currentUser;
        Name = name;
        Description = description;
        DateCreated = DateTime.Now;
    }
}

Is there an easy way to view what contexts a given object might be attached to? I already checked to see if collectionEntity is attached to _userManager since it has a dbContext, but its state is detached. Does EF maybe expect me to add objects in a different way than I am? I suspect that the attributes in SimulationCollectionEntity might be causing me trouble but I'm new to Entity Framework and I'm not sure. Should I maybe be going for a different design instead like this?

Community
  • 1
  • 1
CalumMcCall
  • 1,665
  • 4
  • 24
  • 46

1 Answers1

1

You might want to consider a unit of work like approach where one context is shared among multiple repositories. The accepted answer for this post is a good example. I have seen ContextPerRequest solutions like the one in your example, but I've never been crazy about them. Ideally you want a short lived context that does one thing like add an invoice and two invoice items - a single unit of work. You could then wrap the whole operation in a TransactionScope and have it succeed or fail as a unit.

Community
  • 1
  • 1
Miniver Cheevy
  • 1,667
  • 2
  • 14
  • 20
  • I solved this issue by using the linked solution, thanks! I removed the context from userManager and simply create the userManager upon each request. Just out of curiosity, as I understand it, all caching is done by contexts, do you not want to be trying to persist them for that reason? – CalumMcCall Oct 02 '14 at 10:46
  • 1
    That is a reason, with a very long lived context you could conceivably end up with everything in the database in memory in the context, but the dbContext is a very light weigh object so there is no reason not to spin it up when you need it and dispose it when you're done. You're assured that the underlying sql connection is disposed and released back to the pool. I also think it makes you think about your code a little more and structure it a little better, the slowest mvc view I've seen involved a ContextPerRequest and a lazy loaded collection which resulted in a database call for each item. – Miniver Cheevy Oct 02 '14 at 17:48