Entity framework creates duplicate entities

asp.net-mvc-4 asp.net-web-api c# entity-framework

Question

Edit

I've recorded a screencast with my problem, you can find it here, please look if you have time.

I have the following code that should perform AddOrUpdate functionality, but instead all existing records are recreated, so as a result I have several New Yorks, several USA's. I'm transferring EntityState from client, so that if data changed on client, the client updates EntityState property accordingly and sends it to server.

    [HttpPost, HttpGet, HttpPut]
    public HttpResponseMessage SaveRecord(RecordViewModel record)
    {
        var model = Mapper.Map<Record>(record);

        if (!ModelState.IsValid)
        {
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

        db.Attach(model);

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
        }

        return Request.CreateResponse(HttpStatusCode.OK, Mapper.Map<RecordViewModel>(model));
    }

I'm attaching entities with the following function

    public void AttachAndMarkAs<T>(T entity, EntityState state, Func<T, object> id) where T : class
    {
        var entry = Entry(entity);

        if (entry.State == EntityState.Detached)
        {
            var set = Set<T>();

            T attachedEntity = set.Find(id(entity));

            if (attachedEntity != null)
            {
                var attachedEntry = Entry(attachedEntity);

                if (state != EntityState.Unchanged)
                {
                    attachedEntry.CurrentValues.SetValues(entity);
                    attachedEntry.State = state;
                }
            }
            else
            {
                entry.State = state;
            }
        }
    }

Which is relayed via the following ones:

    public void Attach(City entity)
    {
        if (entity != null)
        {
            Attach(entity.Country);

            AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
        }
    }

    public void Attach(Country entity)
    {
        if (entity != null)
        {
            AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
        }
    }

I don't understand which part of the code handles Adding entities instead of Updating them, because the EntityState values are correct...

1
2
6/24/2013 8:31:22 PM

Accepted Answer

If you are using a simple int for your id, you can use the following method.

public abstract class BaseEntity
{
    public int Id { get; set; }
}

public void AddOrUpdate<T> (T entity) where T : BaseEntity
{
   if(entity.Id > 0){
      Entry(entity).State = EntityState.Modified;
   }
   else
   {
      Set<T>().Add(entity);
   }
}

// 

var model = Mapper.Map<Record>(record);
db.AddOrUpdate(model);
db.SaveChanges();
3
6/24/2013 8:41:52 PM

Popular Answer

You need to pay attention to how Entity Framework deal with child/referenced entities when you're changing the state of the parent entity programmatically.

Have a look at this article that summarizes how things are working in many different cases.

In the example below, you might think that all the referenced entities will automatically be set as Modifiied:

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;
    // Do some more work... 
    context.SaveChanges();
}

But actually, they won't. As written on the article:

If you have multiple entities that need to be marked Modified you should set the state for each of these entities individually.

Finally, here is the way you need to handle the AddOrUpdate specific case when your PrimaryKey is an Int:

using (var context = new BloggingContext())
{
   context.Entry(blog).State = blog.BlogId == 0 ? EntityState.Added : EntityState.Modified;

   context.SaveChanges();
}


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