在 asp.net core 中插入/更新记录时出错

Posted

技术标签:

【中文标题】在 asp.net core 中插入/更新记录时出错【英文标题】:Error while insert/update records in asp.net core 【发布时间】:2019-03-14 21:52:21 【问题描述】:

当我尝试插入/更新记录时,出现以下错误。

无法跟踪实体类型的实例,因为另一个实例 'Id' 具有相同键值的已被跟踪。

下面是我的代码。在这里,我以 1 为增量创建/生成 ID(主键)。我在保存和更新时都遇到错误

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;
        

我从下面的业务层调用上述方法

bool success = dal.SaveDataCapDetails(lstDataCapDetails)

我已尝试使用 AsNoTracking 和其他可用选项,但仍无法解决此问题。

对此的任何帮助表示赞赏。

【问题讨论】:

听起来你需要使用Attach()。我不确定你是否可以使用_objContext.Attach(lstDataCapDetails),但至少你应该附加到现有的_objContext 并设置HasIndex()IsUnique() 以获得任何唯一索引。 好的..你能发布示例代码吗,或者如果你可以在我现有的代码中修改就可以了 @AmirReza-Farahlagha 同意但我没有从数据库中获取任何值并存储在对象中。我实际上是在将我的对象从业务层传递给 DAL。 @XamDev 你在你的表的数据库中设置了身份增加吗?? Nope..我正在从代码中设置主键..你可以看到Id的增量 【参考方案1】:

如果你想拥有Primary-KeyIdentity-Incremental 的表,你必须在设置ForeignKey 之后创建Table,你必须为ForeignKey 设置Identity-Incremental。喜欢:

有了这个问题,您的代码更改为:

    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;
    

【讨论】:

好的...但是我不能没有 Identity set = true ? @XamDev 不,你不能,你必须去桌子上的设计师,并专注于你想要增加的列,然后,在页面的下方你有列属性,在身份规范集'是的,您还可以将增量设置为 1,2,3,... 您想要的每个数字。 所以你想说如果主键列没有设置为identity = true,那么在一个方法中插入和更新记录将不起作用? @XamDev 没错。这是可以解决您的问题的问题之一。 @XamDev 你得到结果了吗??【参考方案2】:

首先,很多人缺少这些检查,从而导致运行时异常。 所以你总是应该检查你的实体的状态:

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

或者

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

为了确保你的实体是'安全使用',如果你想要一个方便的使用方法,你可以使用这个:

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

然后

if (!_objectContext.IsAttached(entity.EntityKey))

   _objectContext.Attach(entity);

这将为您省去重写SaveChanges() 方法的麻烦,在任何情况下都不建议这样做,除非您真的必须这样做。

【讨论】:

【参考方案3】:

我看到您对 Add 和 Update 实体使用相同的方法,我的第一个建议是分开关注点,并为 Add 使用不同的方法,如果可能的话,为 Update 使用另一种方法。

另一方面,不清楚记录列表“lstDataCapDetails”是否可能包含所有新记录或新记录和现有记录的混合以进行更新,在第二种情况下,您的代码会给您错误,因为您可以尝试将 Id 分配给现有记录,或者您可以尝试更新全新的记录。

您可以通过检查实体是否被跟踪并分离它来克服错误,然后附加修改后的实体并更新它。

在这里您可以看到您的方法的修改版本:

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;
    

【讨论】:

是的.. 该列表将包含新记录和现有记录的混合?在这种情况下,上面的代码可以工作吗? 它给了我错误数据库操作预计会影响 1 行,但实际上影响了 0 行。自加载实体以来,数据可能已被修改或删除。现在我正在传递混合记录 添加新记录或更新时出错? 您可以尝试一次所有新记录,然后一次更新所有记录吗? 它的混合记录..我认为它可能是用于更新记录...这是 IsSuccess = _objContext.SaveChanges() > 0;仅在代码中保存/更新记录的行【参考方案4】:

当您想要跟踪对model 所做的更改,而不是在memory 中实际保留未跟踪的model 时,会发生此错误。我有一个小建议或实际上可以解决您的问题的替代建议方法。

EntityFramework 将自动跟踪更改。但你可以在你的DbContextOvverride SaveChanges()

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

【讨论】:

以上是关于在 asp.net core 中插入/更新记录时出错的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ASP.NET Core 更新由依赖表组成的记录 [关闭]

更新文本框 ASP.NET 时出现问题

当在 asp.net 中插入更新的记录而不刷新页面时,如何自动更新 Gridview?

在 ASP.NET Core 中呈现 RDLC 报告时出现索引超出范围异常

在 ASP.NET core 3.0 中,当我应用 except() 方法时出现以下异常

尝试实现登录时出现 ArgumentException(ASP.NET Core 项目)