实体类型的实例无法被跟踪,因为已经有相同主键的另一个实例正在被跟踪。

179

我有一个 Service Object 叫做 Update

public bool Update(object original, object modified)
{
    var originalClient = (Client)original;
    var modifiedClient = (Client)modified;
    _context.Clients.Update(originalClient); //<-- throws the error
    _context.SaveChanges();
    //Variance checking and logging of changes between the modified and original
}

这里是我从哪里调用此方法的地方:

public IActionResult Update(DetailViewModel vm)
{
    var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
    var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
    // Changing the modifiedClient here
    _service.Update(originalClient, modifiedClient);
}

这里是GetAsNotTracking方法:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}

Fetch 方法:

public object Fetch(string id)
{
   long fetchId;
   long.TryParse(id, out fetchId);
   return GetClientQueryableObject(fetchId).FirstOrDefault();
}

GetClientQueryableObject:

private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .Include(x => x.Industry)
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType);
 }

有什么想法吗?

我查阅了以下文章/讨论,但都没有结果:ASP.NET GitHub 问题3839

更新:

这是对GetAsNoTracking的更改:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}

GetClientQueryableObjectAsNoTracking

private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .AsNoTracking()
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .AsNoTracking()
        .Include(x => x.Industry)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType)
        .AsNoTracking();
}

5
我认为你放置 AsNoTracking() 的位置可能有些晚了,因为 _context.Clients 已经被跟踪了。尝试直接在 _context.Clients 调用上放置 AsNoTracking()。除非你计划跟踪它们,否则所有的 Include 应该都使用 AsNoTracking() - Robert Harvey
我可以问一下你为什么要获取原始数据和修改后的数据吗?你为什么不只获取原始数据,进行修改,然后调用更新函数呢?我不理解获取修改后的数据的意义。 - garethb
@garethb 我需要做一个审计日志。因此,我有一个差异检查器,用于检查两个对象中的更改并将其记录到数据库中。 - Rijnhardt
1
如果您对修改后和原始数据都调用GetAsNoTracking会发生什么? - garethb
3
记录一下:只需要一个AsNoTracking()调用,无论在哪里。 - Gert Arnold
显示剩余7条评论
24个回答

0

正如错误信息所说,有两个实体正在被跟踪。

如果您正在进行更新操作:
  1. 执行 Get 操作并获取该实体
  2. 通过设置该实体的属性来更新该实体。不要在相关实体上执行 new 操作。这会创建两个实体。

0
如果你设置了两个或更多带有相同列名“Id”的表,最简单的方法是在上下文类中更改OnModelCreating方法。
在这种情况下,我必须将“Id”更改为“AbandonedCartId”,并告诉实体对象具有列名“Id”。
entity.Property(e => e.AbandonedCartId).HasColumnName("Id");

例子

public partial class AbandonedCart
    {
        public int AbandonedCartId { get; set; }
        public double? CheckoutId { get; set; }
        public int? AppId { get; set; }
        public double? CustomerId { get; set; }
        
    }

protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
            modelBuilder.Entity<AbandonedCart>(entity =>
            {
                entity.Property(e => e.AbandonedCartId).HasColumnName("Id");
                entity.Property(e => e.CreatedAt).HasColumnType("datetime");

                entity.HasOne(d => d.App)
                    .WithMany(p => p.AbandonedCart)
                    .HasForeignKey(d => d.AppId)
                    .HasConstraintName("FK_AbandonedCart_Apps");
            });
}

0
在我的情况下,这是一个保存工作单元更改两次的错误。我使用的是saveChangesAsync(),所以它并没有立即发生,而是稍后发生。

0

我的后台服务出现了这个错误。我通过创建一个新的作用域来解决了它。

                using (var scope = serviceProvider.CreateScope())
                {
                      // Process
                }

-1

我遇到了同样的问题,但问题非常愚蠢,我不小心给出了错误的关系,我在两个ID之间建立了关系。


但有时您需要两个ID之间的关系,例如当您想要一个唯一索引时。 - fullStackChris

-1

无法更新数据库行。我曾经遇到过同样的错误。现在使用以下代码进行操作:

_context.Entry(_SendGridSetting).CurrentValues.SetValues(vm);
await _context.SaveChangesAsync();

-1

如果您有重复的条目/实体并运行SaveChanges(),则可能会出现此错误消息。


5
这并没有回答问题。 - Gert Arnold
1
这是一个真实的注释,我插入了唯一的值,错误消失了。 - Atahan Ceylan
1
很高兴能帮到你@AtahanCeylan :) - johnbt

-1

我自己也遇到过这个问题。Entity Framework 会跟踪每个插入到数据库中的对象。因此,当您插入一个与已有记录相同但某些字段被更改的重复对象时,EF 将抛出此错误。我通过深度克隆我要重新插入的对象来解决这个问题,然后就成功了。

    public static T DeepClone<T>(this T a)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, a);
            stream.Position = 0;
            return (T)formatter.Deserialize(stream);
        }
    }

然后:

var cloned = objectYouAreTryingToReinsert.deepClone();
context.objects.add(cloned);
await context.SaveChangesAsync();

这并没有解决问题。如果一个具有相同键的对象已经附加到上下文中,那么您无法附加任何其他具有相同键的对象(无论是克隆的还是不克隆的),即使它们属于同一类。 - Gert Arnold

-1

扩展方法,用于将实体从任何键类型中分离。

public static void Detach<TEntry, TId>(this DbContext context, Func<TEntry, TId> idReader, TId id) 
    where TEntry : class
    where TId : IEquatable<TId>
{
    var local = context.Set<TEntry>()
        .Local
        .FirstOrDefault(entry => idReader(entry).Equals(id));
    if (local != null)
    {
        context.Entry(local).State = EntityState.Detached;
    }
}

Guid键的用法:

dbContext.Detach<EntryType, Guid>(e => e.Id, myEntity.Id);
dbContext.Attach(myEntity);

-1

我之前也遇到过同样的问题,通过将 AddDbContext 替换为 AddDbContextFactory 添加到服务容器中解决了它。

这是我的解决方法:

我没有注册 AddDbContext,而是注册了 AddDbContextFactory,像这样:

public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<YourApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("YourDatabaseConnectionString")));
}

重要提示:

不要同时注册AddDbContextAddDbContextFactory,否则会出现System.AggregateException: 'Some services are not able to be constructed...' 异常。请改用AddDbContextFactory

您的ApplicationDbContext类必须公开暴露一个带有DbContextOptions<YourApplicationDbContext>参数的公共构造函数,就像这样:

public class YourApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<YourApplicationDbContext> options): base(options){}
}

DbContextFactory 工厂可以通过构造函数注入来使用,如下所示:

private readonly IDbContextFactory<YourApplicationDbContext> dbContextFactory;

public YourConstructor(IDbContextFactory<YourApplicationDbContext> dbContextFactory)
{
    dbContextFactory = dbContextFactory;
}

或者

public YourController(IDbContextFactory<YourApplicationDbContext> dbContextFactory)
{
    dbContextFactory = dbContextFactory;
}

注入的工厂可以用于在服务代码中构建 DbContext 实例,如下所示:
   using (var context = dbContextFactory.CreateDbContext())
        {
            // your database CRUD code comes in here... for example:
            context.DatabaseTable.Update(suppliedModel);
            await context.SaveChangesAsync();
        }
当你可能考虑这个选项时: 注册一个工厂而不是直接注册上下文类型,可以方便地创建新的DbContext实例。这也是推荐在Blazor应用程序中使用的。

希望这对于遇到这个问题的人有所帮助。干杯!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接