Check if EF entity already exist in current context

c# ef-code-first entity-framework-6

Question

I am aware of the fact that there a several questions about this already, but none of them really solves my problem:

APPLICATION BACKGROUND

I have a web application that uses Entity Framework 6.1 Code first. I'm using Repository pattern within my DAL, which means, I have Repo's that queries my context and returns the results to Services, which then uses automapper to map my entity to a view model, and then returns a VM. Services are called by Web API methods.

Based of the above architecture, its clear that I'm using detached entities.

The issue i'm having is with updating (PUT) existing entities. The flow looks like this:

Angular Issue HTTP PUT to WebAPI method passing VM as parameter. WebAPI then calls a Service method, passing the VM as parameter. Services method converts VM to EF entity using automapper Service method then calls respective repository, passing new instance of detached EF entity as parameter.

Example of Service method:

    public async Task<List<DependantViewModel>> SaveDependants(int customerId, List<DependantViewModel> dependantViewModels)
    {
        var dependantEntities = Mapper.Map<List<DependantViewModel>, List<Dependant>>(dependantViewModels);

        bool result = false;
        foreach (var dependantEntity in dependantEntities)
        {
            result = await _dependantRepository.InsertOrUpdate(dependantEntity);
            if (result != true)
            {
                // log errror
            }
        }

        return Mapper.Map<List<Dependant>, List<DependantViewModel>>(dependantEntities);
    }

BaseRepo:

    public virtual async Task<bool> InsertOrUpdate(TE entity)
    {
        if (entity.Id == 0 || entity.Id == ModelState.New)
        {
            // insert new item
            _context.Entry<TE>(entity).State = EntityState.Added;
        }
        else
        {
            // update existing item
            _context.Entry<TE>(entity).State = EntityState.Modified;
            _context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
            _context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
        }
        return await _context.SaveChangesAsync() > 0;
    }

Exception happens when trying to set:

_context.Entry(entity).State = EntityState.Modified;

Attaching an entity of type 'Business.Data.Entities.Customer.Dependant' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

As far as I understand, this happens because the detached entity needs to be attached first before setting the entity state. The issue with this is, I can't attach the entity, because its already in the context.Dependant.local collection. For some reason, entity framework is already tracking the entity.

Is there a way to first check if an entity exists in the context, and if it doesn't, then attach, otherwise, retrieve the already attached entity, and apply the changes from the modified entity to the attached entity, if that makes sense.

Appreciate any feedback

1
2
7/8/2015 4:28:19 AM

Accepted Answer

This worked for me:

public virtual async Task<bool> InsertOrUpdate(TE entity)
{
    if (entity.Id == 0 || entity.Id == ModelState.New)
    {
        // insert new item
        _context.Entry<TE>(entity).State = EntityState.Added;
    }
    else
    {           
        var attachedEntity = _context.ChangeTracker.Entries<TE>().FirstOrDefault(e => e.Entity.Id == entity.Id);
        if (attachedEntity != null)
        {
            // the entity you want to update is already attached, we need to detach it and attach the updated entity instead
            _context.Entry<TE>(attachedEntity.Entity).State = EntityState.Detached;
        }

        _context.Entry<TE>(entity).State = EntityState.Modified; // Attach entity, and set State to Modified.
        _context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
        _context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
    }
    return await _context.SaveChangesAsync() > 0;
}
4
7/8/2015 11:41:48 PM

Popular Answer

In the beginning I should save that you have use SaveChanges() only when all entites is being Added or Updated to EF Context(it's not an answer to your question but I need to mention it). Example:

 public async Task<List<DependantViewModel>> SaveDependants(int customerId, List<DependantViewModel> dependantViewModels)
{
    var dependantEntities = Mapper.Map<List<DependantViewModel>, List<Dependant>>(dependantViewModels);

    bool result = false;
    foreach (var dependantEntity in dependantEntities)
    {
        // Also InsertOrUpdate don't need to be async in that case
        _dependantRepository.InsertOrUpdate(dependantEntity);
        if (result != true)
        {
            // log errror
        }
    }
    // Let the SaveChanges be async
    result = await _dependantRepository.SaveChanges();

    return Mapper.Map<List<Dependant>, List<DependantViewModel>>(dependantEntities);
}

BaseRepo:

public async Task<bool> SaveChanges(){
   return await _context.SaveChangesAsync() != 0;
}

It will make only one query to DB instead of several.

And about your question, that should help:

public virtual void InsertOrUpdate(TE entity)
{
    var entitySet = _context.Set<TE>();
    if(entity.Id.Equals(default(int))){
        entitySet.Add(entity);
        return;
    }
    entitySet.Update(entity);
}

Thats it. Hope this will help.(I didn't tested code, so probably there can be some mistake)



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow