XamDev
XamDev

Reputation: 3657

Error while insert/update records in asp.net core

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.

Upvotes: 3

Views: 2814

Answers (4)

AmirReza-Farahlagha
AmirReza-Farahlagha

Reputation: 1212

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;
    }

Upvotes: 5

LazZiya
LazZiya

Reputation: 5729

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;
    }

Upvotes: 2

Barr J
Barr J

Reputation: 10927

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.

Upvotes: 2

Siavash
Siavash

Reputation: 3009

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();
}

Upvotes: 0

Related Questions