EF Core“另一个实例已被跟踪”

时间:2019-05-21 14:41:13

标签: c# entity-framework asp.net-core .net-core entity-framework-core

我在使用EF Core 2.2.3更新.Net Core 2.2.0中的实体时遇到问题。

  

保存更改时发生错误。错误详情:   无法跟踪实体类型'Asset'的实例,因为已经跟踪了具有相同的{'Id'}键值的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用

这是注册数据库上下文的方式:

services.AddDbContext(options =>

options.UseSqlServer(Configuration.GetConnectionString("DbConnection")), ServiceLifetime.Scoped);

Scoped的生存期是默认设置的,但我写它的目的是为了更易于理解。

Anomaly对象如下所示:

public IQueryable<Anomaly> GetAll()
    {return _context.Anomalies.Include(a => a.Asset).Include(a => a.Level)
}

public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
    var anomaly = await GetAll()
        .FirstOrDefaultAsync(a => a.Id == anomalyId);

    return anomaly;
}

Update()方法如下:

using (var transaction = _context.Database.BeginTransaction())
{
    try
    {
        _context.Anomalies.Update(anomaly);
        _context.SaveChanges();

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

它在此事务之前包含一些检查,但在此情况下没有足够的相关性。

这是我已经跟踪到实例的错误的地方。我无法理解这种情况是怎么发生的。如果上下文为Scoped,则

  

...“在这种情况下,将为每个请求创建一个新的服务实例”

如果我在PUT请求上的上下文与GET请求的上下文不同,那么如何跟踪实体?在最基本的级别上如何工作?

使其生效的唯一方法是设置从ChangeTrackerEntityState.Detached的所有条目的状态。然后它起作用了..但这至少没有意义,至少就我目前的知识而言。

我发现this question,但没有有效答案,只有关于EF如何进行跟踪的变通办法和假设。


  

更新   这是一个链接到bitbucket的链接,其中包含重新创建此问题的示例:EF Core Update Sample

我序列化了从上下文中检索到的对象。

在左侧跟踪时<====>在右侧没有跟踪时 With Tracking on the LEFT <====> With NO tracking on the RIGHT

5 个答案:

答案 0 :(得分:1)

默认情况下,当您检索被跟踪的实体时,由于已跟踪它们,因此您可以仅调用SaveChanges而不能调用Update。您还可以使用.AsNoTracking()

检索实体而无需跟踪它们

如果尚未跟踪,则需要调用Update,因此,如果您使用AsNoTracking,则确实需要在SaveChanges之前使用Update

public IQueryable<Anomaly> GetAll()
{    return _context.Anomalies
    .Include(a => a.Asset)
    .Include(a => a.Level);
}

public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
    var anomaly = await GetAll()
        .AsNoTracking()
        .FirstOrDefaultAsync(a => a.Id == anomalyId);

    return anomaly;
}

您还可以检查是否已跟踪实体以知道是否调用Update:

using (var transaction = _context.Database.BeginTransaction())
{
    try
    {

        bool tracking = _context.ChangeTracker.Entries<Anomaly>().Any(x => x.Entity.Id == anomaly.Id);
        if (!tracking)
        {
            _context.Anomalies.Update(anomaly);
        }

        _context.SaveChanges();

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

答案 1 :(得分:1)

我试图更新相同数据而没有注意到。我花了十个小时才意识到这一点。如果您有像我这样的重复值,我建议删除该数据...阅读此答案的人可能像我一样尝试了互联网上的所有解决方案,破坏了项目,遇到了相同的错误,并且忽略了重复< / strong>数据,就像我一样。你不孤单,我的朋友。

model.GroupBy(gb => gb.ID).Select(s=>s.First()).ToList();//remove duplicates!!!!!

答案 2 :(得分:0)

因此,最后,我们最终使用了自定义的UpdateEntity方法将所做的更改保存在某些实体上。此方法遍历实体的每个属性,导航属性和集合属性,确保不存在链,并一次更新对象。

它适用于大型物体,我们仅在这种情况下使用。对于简单的操作,我们继续使用简单的更新

Here is the link to a bitbucket repository with the source code

要使用此方法,必须首先获取db实体。然后,使用数据库实体和请求收到的您的实体调用UpdateEntity方法。

我希望它能对您有所帮助。干杯!

答案 3 :(得分:0)

我遇到了同样的问题,但是试图向同一Entiy添加第二行。就我而言,这是因为我假设主键是一个Int Identity并且是自动生成的。 由于我没有为其分配任何值,因此第二行具有相同的ID,这是默认值。 经验教训,当您在EF中执行Add()操作时看到此错误时,请确保您的密钥是IDENTITY。

答案 4 :(得分:0)

在我的例子中的问题来自这样一个事实,即更新 1:many 关系中的列表时缺少类定义中的实体。

public class Exercise
{
  public Guid Id { get; set; } 
  //other props...
  public List<EntityInExercise> Entities { get; set; } = new();
}

public class EntityInExercise
{
  public Guid Id { get; set; } 
  //other props...

  //?this line was missing ?
  public Exercise Exercise{ get; set; }= null!;
  //☝️
}

在这两种情况下(有和没有丢失的行)它都会创建相同的数据库。我遇到的唯一问题是尝试使用 Automapper 及其 Collections 更新该列表时。