3

When I am trying to insert/update the records I am getting the below error.

The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.

Below is my code for the same. Here I am creating/generating the ID(Primary Key) by increment with 1. I am getting error at both Save & Update

public bool SaveDataCapDetails(List<TDataCapDetails> lstDataCapDetails)
        {
            bool IsSuccess = false;

            using (var dbContextTransaction = _objContext.Database.BeginTransaction())
            {
                try
                {

                    List<TDataCapDetails> lstDataCapDetailsRecords = null;

                    if (lstDataCapDetails.Where(x => x.Id == 0).Count() > 0)
                    {
                        lstDataCapDetailsRecords = new List<TDataCapDetails>();
                        lstDataCapDetailsRecords.InsertRange(0, lstDataCapDetails);

                        int? id = _objContext.TDataCapDetails.Max(x => (int?)x.Id);
                        id = id == null ? 0 : id;
                        foreach (var item in lstDataCapDetailsRecords.Where(x => x.Id == 0))
                        {
                            id = id + 1;
                            item.Id = (int)id;
                        }
                        _objContext.Entry(lstDataCapDetailsRecords).State = EntityState.Detached;
                        _objContext.AddRange(lstDataCapDetailsRecords);
                        _objContext.SaveChanges();
                    }

                    if (lstDataCapDetails.Where(x => x.Id > 0).Count() > 0)
                    {
                        lstDataCapDetailsRecords = new List<TDataCapDetails>();
                        lstDataCapDetailsRecords = lstDataCapDetails.Where(x => x.Id > 0).ToList();
                        _objContext.UpdateRange(lstDataCapDetailsRecords);
                        _objContext.SaveChanges();
                    }

                    dbContextTransaction.Commit();
                }
                catch (Exception ex)
                {
                    dbContextTransaction.Rollback();
                    throw ex;
                }
            }

            return IsSuccess;
        }

The above method I am calling from business layer like below

bool success = dal.SaveDataCapDetails(lstDataCapDetails)

I have tried with AsNoTracking and other options available, but still I am not able to resolve this issue.

Any help on this appreciated.

11
  • Sounds like you need to use Attach(). I don't sure if you can do _objContext.Attach(lstDataCapDetails), but at least you should attach to existing _objContext and set HasIndex() with IsUnique() for any unique indexes. Commented Oct 10, 2018 at 4:42
  • Ok.. Can you post the sample code or if you can modify in my existing code that will be fine Commented Oct 10, 2018 at 4:54
  • @AmirReza-Farahlagha Agree But I am not fetching any values and storing in object from DB. I am actually passing my object from Business layer to DAL. Commented Oct 13, 2018 at 6:49
  • @XamDev Did you set identity increase in your data base of your table?? Commented Oct 13, 2018 at 6:55
  • Nope.. I am setting the Primary Key from Code.. You can see the increment of Id Commented Oct 13, 2018 at 6:57

4 Answers 4

5

If you want to have table with Primary-Key and also with Identity-Incremental you have to create your Table after set ForeignKey you have to set Identity-Incremental for that ForeignKey. like:

enter image description here

With this issue, your code change to this:

    public bool SaveDataCapDetails(List<TDataCapDetails> lstDataCapDetails)
    {
        bool IsSuccess = false;

        using (var dbContextTransaction = _objContext.Database.BeginTransaction())
        {
            try
            {

                List<TDataCapDetails> lstDataCapDetailsRecords = null;

                if (lstDataCapDetails.Where(x => x.Id == 0).Count() > 0)
                {
                    lstDataCapDetailsRecords = new List<TDataCapDetails>();
                    _objContext.AddRange(lstDataCapDetailsRecords);
                    _objContext.SaveChanges();
                }

                if (lstDataCapDetails.Where(x => x.Id > 0).Count() > 0)
                {
                    lstDataCapDetailsRecords = new List<TDataCapDetails>();
                    lstDataCapDetailsRecords = lstDataCapDetails.Where(x => x.Id > 0).ToList();
                    _objContext.UpdateRange(lstDataCapDetailsRecords);
                    _objContext.SaveChanges();
                }

                dbContextTransaction.Commit();
            }
            catch (Exception ex)
            {
                dbContextTransaction.Rollback();
                throw ex;
            }
        }

        return IsSuccess;
    }
Sign up to request clarification or add additional context in comments.

6 Comments

OK... But I can not do without Identity set = true ?
@XamDev No you can not, you have to go to designer on table, and focus on column that you want to Increment, after that, down of page you have column properties, in Identity Specification set 'yes' and also you can set your Incremental as 1,2,3,... every number which you want.
So you are trying to say that if primary key column is not set to identity = true, then insert and update records in one single method will not work ?
@XamDev Exactly. This is one of that issues may can fix your problem.
@XamDev Dod you get result??
|
2

First of all, alot of people are missing those checks, thus result in runtime exceptions. So you always should check the state of your entity:

context.YourEntities.Local.Any(e => e.Id == id);

Or

context.ChangeTracker.Entries<YourEntity>().Any(e => e.Entity.Id == id);

To make sure your entity is 'safe to use', if you want a convenient method to use, you can use this:

/// <summary>
    /// Determines whether the specified entity key is attached is attached.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="key">The key.</param>
    /// <returns>
    ///   <c>true</c> if the specified context is attached; otherwise, <c>false</c>.
    /// </returns>
    internal static bool IsAttached(this ObjectContext context, EntityKey key)
    {
        if (key == null)
        {
            throw new ArgumentNullException("key");
        }

        ObjectStateEntry entry;
        if (context.ObjectStateManager.TryGetObjectStateEntry(key, out entry))
        {
            return (entry.State != EntityState.Detached);
        }
        return false;
    }

and then:

if (!_objectContext.IsAttached(entity.EntityKey))
{
   _objectContext.Attach(entity);
}

This will save you the trouble of overriding the SaveChanges() method, which is not recommended at any case, unless you really have to.

Comments

2

I see that you are using same method for Add and Update entities, my first suggestion is separate concerns and make different methods one for for Add, and another one for Update if it is possible.

On the other hand, it is not clear if the list of records "lstDataCapDetails" may contain all new records or a mix of new and existing records for update, in the second case your code will give you error because you may try to assign Id to an existing record, or you may try to update a totally new record.

Re the error you can overcome by checking if the entity is being tracked and detach it, then attach the modified entity and update it.

here you can see a modified version of your method:

public bool SaveDataCapDetails(List<TDataCapDetails> lstDataCapDetails)
    {
        bool IsSuccess = false;

        using (var dbContextTransaction = _objContext.Database.BeginTransaction())
        {
            try
            {
                int? id = _objContext.TDataCapDetails.Max(x => (int?)x.Id);
                id = id == null ? 0 : id;

                // entities with Id == 0 --> new entities
                // you may need to check if Id == null as well (depends on your data model)
                var entitiesToAdd = lstDataCapDetails.Where(x => x.Id == 0);
                foreach(var entity in entitiesToAdd)
                {
                    entity.Id = id++;

                    // new entities is not tracked, its state can be changed to Added
                    _objContext.Entry(entity).State = EntityState.Added;
                }

                // entities with Id > 0 is already exists in db and needs to be updated
                var entitiesToUpdate = lstDataCapDetails.Where(x => x.Id > 0);
                foreach (var entity in entitiesToUpdate)
                {
                    // check if entity is being tracked
                    var local = _objContext.Set<TDataCapDetails>().Local.FirstOrDefault(x => x.Id.Equals(entity.Id));

                    // if entity is tracked detach it from context
                    if (local != null)
                        _objContext.Entry<TDataCapDetails>(local).State = EntityState.Detached;

                    // attach modified entity and change its state to modified
                    _objContext.Attach(entity).State = EntityState.Modified;
                }

                // optional: assign value for IsSuccess
                IsSuccess = _objContext.SaveChanges() > 0;                    

                dbContextTransaction.Commit();
            }
            catch (Exception ex)
            {
                dbContextTransaction.Rollback();
                throw ex;
            }
        }

        return IsSuccess;
    }

5 Comments

Yes.. The list will contain mix of new and existing records ? In this case above code will work ?
Its giving me error Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. Now I am passing mix records
Error while adding new records or while updating?
Can you try once with all new records, and once with all updating ?
Its mix records.. I can think it might be for update records... s this is the IsSuccess = _objContext.SaveChanges() > 0; only line in the code which saving/updating the records
0

This error happens when you want to track the changes made to the model, not to actually keep an untracked model in memory. I have a little suggestion or actually an alternative suggestion approach which will fix your problem.

EntityFramework will automatically track changes. But you can Ovverride SaveChanges() in your DbContext.

public override int SaveChanges()
{
    foreach (var ent in ChangeTracker.Entries<Client>())
    {
        if (ent.State == EntityState.Modified)
        {
            // Get the changed values
            var modifiedProps = ObjectStateManager.GetObjectStateEntry(ent.EntityKey).GetModifiedProperties();
            var currentValues = ObjectStateManager.GetObjectStateEntry(ent.EntityKey).CurrentValues;
            foreach (var propName in modifiedProps)
            {
                var newValue = currentValues[propName];
                //log your changes
            }
        }
    }

    return base.SaveChanges();
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.