1

I have this class that I use for DB operations:

public class EntityService<TEntity> : IRepository<TEntity> where TEntity : BaseModel
{

     ApplicationDbContext _context;
     private DbSet<TEntity> _entities;

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

     public virtual void Update(TEntity entity)
     {
          if (entity == null)
               throw new ArgumentNullException(nameof(entity));

          try
          {
                var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();

                dbEnt = entity;
                dbEnt.UpdatedBy = GetCurrentUser();
                dbEnt.DateUpdated = DateTime.Now;
                _context.SaveChanges();
           }
           catch (DbUpdateException exception)
           {
                throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
           }

           //-----other methods for insert and get working fine----
}

There are also other methods in this class for insert and get are working fine. Only this update method is not updating the entity and not throwing the exception.

UPDATE

I am facing similar problem but opposite in functioning here: Add() method adding duplicate rows for linked models in Code-First Entity Framework

I think these two have same reason of Change Tracking. But one is adding other is not updating.

Aishwarya Shiva
  • 3,460
  • 15
  • 58
  • 107
  • 1
    if you want update `entity` why you again get it and put `entity` in `dbEnt`? – hassan.ef May 25 '19 at 07:20
  • @hassan.ef It was not working when I did that. So I thought if I get the database entity again from the database then _context will have its db reference. Because `entity` is just method parameter. – Aishwarya Shiva May 25 '19 at 07:22
  • I think at first you should update parameter of `dbEnt` with parameter of `entity` like this: for example: `edbEnt.Name = entity.Name` and dont use `dbEnt = entity`. – hassan.ef May 25 '19 at 07:26
  • @hassan.ef This is a common method to update all the entities. I can't explicitly assign individual property here. – Aishwarya Shiva May 25 '19 at 07:29
  • Before saving, you could try calling `context.Entry(entity).State=EntityState.Modified;`. – bolkay May 25 '19 at 07:43
  • @bolkay I tried it but it's showing me this error `Attaching an entity of type '' 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. ` ` – Aishwarya Shiva May 25 '19 at 11:51
  • @bolkay continued from above comment `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.'` – Aishwarya Shiva May 25 '19 at 11:52
  • @bolkay How to use the solution suggested by the error? – Aishwarya Shiva May 25 '19 at 11:53
  • This is a tracking issue. Try something like: `_context.Set().Add(dbEnt);` Then : `_context.Entry(dbEnt).State=EntityState.Modified;` Then `SaveChanges();` You can read up on the different entity states. – bolkay May 25 '19 at 12:21
  • @bolkay After doing that it is showing this error `Saving or accepting changes failed because more than one entity of type '' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. ` – Aishwarya Shiva May 25 '19 at 12:42
  • Your posted code is fine except for `dbEnt = entity;`. Instead, copy the changed fields in `dbEnt.Prop1 = entity.Prop1;` etc. We use Automapper to do all fields in 1 step: `Mapper.Map(entity, dbEnt);` – Steve Greene May 25 '19 at 14:38
  • You started a bounty because the question didn't receive enough attention. But what's not clear/sufficient in my answer? I think I exactly described the cause of the failing update. And proposed working alternatives. It would have much cheaper for you to respond to my answer if it wasn't clear enough. – Gert Arnold Jun 03 '19 at 15:01
  • @GertArnold I already tried your answer as I mentioned in previous comments. Bolkay suggested me this. I tried every way to attach the entity to the change tracker but it's not working. Even I tried initializing ApplicationContext object again in this method. But I am still trying some other ways and looking for any more suggestions so that I can get some hint. That's why I started the bounty. I will come back to your answer once I tried everything I can find on the Internet. – Aishwarya Shiva Jun 03 '19 at 16:00
  • It's not clear what *exactly* you tried because it's only a fragment in a comment. But whatever it is, it doesn't make sense to use `Add()` for an entity you just fetched from the database and only want to update. It is already attached to the context! You only have to update its values and save changes. That's the gist of my second suggestion. – Gert Arnold Jun 03 '19 at 19:25

4 Answers4

4

The line...

var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();

...attaches an entity object to the context and returns a reference to this entity.

Then the line...

dbEnt = entity;

...replaces this reference by a reference to the entity variable that enters the method. That is not the tracked entity object. You basically lost the reference to the tracked entity and it's impossible to change it any longer.

You should either attach entity to the context and mark it as modified, or get dbEnt as you already do and modify and save that object. Both methods have pros and cons, see here.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
1

After getting entity from _context, update all fields from new detail and set entity state to modified

var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();
dbEnt.Name = entity.Name;
...
...
...
dbEnt.UpdatedBy = GetCurrentUser();
dbEnt.DateUpdated = DateTime.Now;
_context.Entry(dbEnt).State = EntityState.Modified;
_context.SaveChanges();
Colonel Software
  • 1,401
  • 1
  • 11
  • 16
0

If you have found your entity via Id

var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();

then why this line?

dbEnt = entity;

Remove the above line as it will remove the reference to the tracked object.

Azhar Khorasany
  • 2,712
  • 16
  • 20
-1

Thank you everyone. I received a lots of hint from your answers. As @GertArnold and @Colonel Software's answer hinted me I modified my code like this and it worked:

//Assigning BaseModel properties
entity.CreatedBy = dbEnt.CreatedBy;
entity.UpdatedBy = GetCurrentUser();
entity.DateUpdated = DateTime.Now;
entity.DateCreated = dbEnt.DateCreated;

//Changing entity states
_context.Entry(dbEnt).State = EntityState.Detached;
_context.Entry(entity).State = EntityState.Modified;

_context.SaveChanges();
Aishwarya Shiva
  • 3,460
  • 15
  • 58
  • 107
  • Why not just modify and save `dbEnt` as happens in the answer you refer to? – Gert Arnold Jun 05 '19 at 19:30
  • @GertArnold Tried that but it is showing duplicate primary key values exception. I will try solving that later but for now it is working and I need to deliver the project soon so I have to go for this solution. Thank you for your help I learned a lot from your answer and +1 to it. – Aishwarya Shiva Jun 05 '19 at 19:57
  • Downvote for what? This solves my issue. May be it's not the best solution but it is a solution. – Aishwarya Shiva Jun 05 '19 at 19:59
  • Then you did more than you show. As said in another answer (and mine), all you need is remove `dbEnt = entity;`. – Gert Arnold Jun 05 '19 at 19:59
  • The downvote is because your code barely *happens to work*. Using `AddOrUpdate` here is pretty weird. – Gert Arnold Jun 05 '19 at 20:02
  • @GertArnold The solution was a mixture of all the answers. There were two problems. One mentioned by you that it was because the tracking was lost and other mention by Colonel Software that hinted that I need to assign all properties of base model. – Aishwarya Shiva Jun 05 '19 at 20:05
  • @GertArnold anyway I am marking your answer instead of mine. Please retract the downvote because it is one of the solutions. – Aishwarya Shiva Jun 05 '19 at 20:06
  • Sorry, my downvote stands, because it's a bad method to update an entity. You really shouldn't use `AddOrUpdate` until you know what it does (and does *not*). And when you understand that you won't use it any longer. – Gert Arnold Jun 05 '19 at 20:10
  • @GertArnold Ok so can you explain me or refer me some link where I can find its disadvantages? And can you please modify my code and update your answer? – Aishwarya Shiva Jun 05 '19 at 20:22
  • @GertArnold Somehow I was able to do it without `AddOrUpdate`. Please check the update to my answer and retract downvote if it's fine. – Aishwarya Shiva Jun 05 '19 at 20:48
  • Remove `dbEnt = entity;` and modify `dbEnt`'s properties! – Gert Arnold Jun 05 '19 at 20:59