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

79

我正在使用asp.net core 2.0中的通用存储库模式,无法处理存储库对象的释放问题。当我要更新实体时,它可以成功地更新一次,但当我尝试进行多次更新时,会抛出以下异常:

由于另一个具有相同键值 {'ID'} 的实例已被跟踪,因此无法跟踪实体类型“Company”的实例。在附加现有实体时,请确保只附加具有给定键值的一个实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'来查看冲突的键值。

public ActionResult Edit(Int64 id, Company collection)
{
    try
    {
        // TODO: Add update logic here
        interfaceobj.updateModel(collection);
        interfaceobj.Save();
        return RedirectToAction(nameof(Index));
    }
    catch(Exception ex)
    {
        throw ex;
    }
}

1
阅读[ask]并创建[mcve]。您不应在请求之间重用DbContext。 - CodeCaster
如果你提供了一些代码而不是说“我已经实现了Repository Pattern”,那就太好了。问题似乎存在于你如何实现它上面!这个问题并没有帮助其他遇到同样问题的人。 - Shahryar Saljoughi
代码中没有显示EF的操作,所有操作都隐藏在自定义代码后面。这个问题不可能被回答。interfaceobj是什么?代码的状态是什么?为什么有TODO?等等。 - Gert Arnold
10个回答

79

您的DB Context被多个请求共享,这意味着您正在编辑的实体已经被跟踪。

这很可能是因为您的仓储服务是一个Singleton而不是Scoped,因此当它被提取出来并放回到相同的DB Context实例中时,您的DB Context将被重用,并且实体也会被跟踪。

请使用Scoped Repository代替Singleton repository,这意味着每个请求都将创建一个新实例。同样,您还将拥有每个请求的DB Context。

当您注册服务时,它将使用services.AddSingleton<..>

将其更改为services.AddScoped<..>,当您将其注入到控制器中时,您将获得每个请求的新实例,然后您的更新应该可以正常工作。


1
我的错,我正在为单例服务注册服务。services.AddSingleton<IRepository<Company>, GenericRepository<Company>>(); 现在我改成了 service.AdScoped,现在对我来说完美运行了 :) - Ali Raza
@RudiVisser 不,实际上只是在启动时...我真的不知道该怎么解决...也许我应该在这里问问,但我想人们会说这是一个重复的问题... - Andre
2
实际上我已经在使用Scoped了,问题已经解决,看起来是EF的一个bug... - Andre
3
@JonathanPeel,在我的情况下,据我所知,这是一个EF的bug...为了解决这个问题,首先我进行了一些更改,以便像这里https://dev59.com/P3M_5IYBdhLWcg3wWRyV那样查看查询,之后我发现EF正在做一些非常奇怪的事情,它在某种程度上试图放置已经存在的数据,并且我注意到它总是有同样的属性问题,最后我发现EF由于我在许多类中都有“name”属性而迷失了方向,所以我只需将此属性更改为字符串“foo”,它就可以正常工作,再也不会崩溃了。 - Andre
1
谢谢!您提供的信息帮助我深入了解了我的问题。 - Roslin Mahmud Joy
显示剩余6条评论

52

这将对您有所帮助!

AsNoTracking()

默认情况下,EntityFramework会跟踪查询的实体的更改,并在调用上下文中的SaveChanges()时保存更改。

如果它已经正在跟踪product:42,并且您添加或附加了一个带有Id:42的产品,它还没有看到,那么就存在哪个代表真相的歧义。

AsNoTracking生成不添加到更改跟踪机制的对象,如果您不使用更改跟踪功能,则应该使用该功能,因为它更快并使您的意图更明确。

大多数情况下,这是作用域问题。您不希望缓存中有任何东西,但出乎意料地有 - 通常是因为某些全局变量或未处置的上下文 - 这两者都非常常见。当您找到有问题的DbContext时,请将其包装在“using”中或添加AsNoTracking。

.AsNoTracking()有什么区别?


4
如果您想操纵实体实例并使用SaveChanges()将更改持久化到数据库,则不应禁用更改跟踪。https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontextoptionsbuilder.usequerytrackingbehavior?view=efcore-3.1 - cickness

19
在Dotnet 6中,您只需将此内容添加到program.cs文件中的dbcontext选项中:
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
});

2
你可以这样做很长时间。但这不是一个好主意。通常你需要跟踪。 - JHBonarius
1
这在 dotnet 3.1 中也适用。 - Diego Montania
1
很好,我希望这是默认设置。更加功能强大,副作用更少。 - Dax Fohl

13

就像Rudi Visser 建议的那样,当在多个数据上下文中使用同一实体时,您可能会遇到此问题。在我的情况下,类似于以下内容:

User user = await DataContext.Users.SingleOrDefaultAsync(u => u.Id == "SomeId");

// Switch to UserManager *_*
var res = await UserManager.ResetPasswordAsync(user, token, password); // Exception thrown

一个简单的解决方案是尽量使用同一个DataContext,因为UserManager似乎会在后台执行一些附加和分离的操作(也许)。例如:
User user = await UserManager.FindByIdAsync("SomeId");
var res = await UserManager.ResetPasswordAsync(user, token, password);

1
经过3个小时的调试,我找到了你的解决方案!在我的情况下,我正在使用来自UserStore的用户实体,并将其传递给UserManager的ResetPasswordAsync。问题在我使用UserManager后得到了解决。谢谢,伙计!非常感谢你的帮助! - Farkhod

12

我遇到了这个问题,但我的DbContext已经被注册为Scoped。我的问题是在一个嵌套的导航属性中跟踪了一个属性。将该导航属性设为Null(保留外键)解决了我的问题。


谢谢@Nicholas Smith,对我来说这个解决方案也起作用了,不过想一想...这难道不是Entity Framework的一个bug吗? - Neias
谢谢,这帮助我找到了问题所在。我有两行EF查询。这两个查询正在查询不同的表(因此是不同的EF实体),但其中一个表具有FK,这使得EF在EF中添加了一个嵌套导航属性。因此,问题基本上是由于嵌套导航属性而意外地出现了双重跟踪。对我来说,解决方案是将其中一个查询更改为AsNoTracking()。 - bobt

9
您需要分离式条目 -
Entry<CustomerBaseInfo>(data).State = EntityState.Detached;

它对我有效!在更新情况下,我们需要在更改属性之前放置此语句。 - Arthur Cabral
我认为你不应该设置detached。这样做只是在掩盖问题。 - Rui Santos

2

我也遇到了同样的问题并通过谷歌搜索得到了一些想法。

实体框架拥有变更跟踪器,将实体存储并引用在变更跟踪器中使用它们的主键。更准确地说,dbcontext拥有一个秘密字典,它只允许存储相同类型和相同主键的一个对象。它不能跟踪两个相同类型和相同主键的对象。这是我的示例代码。

var model = new EaEnrollment()
        {
            Id = 1,
            UserId = 2,
            AllocationType = "Individual",
            Status = "In Progress"
        };
        _repoEaEnrollment.Update(model);
        //here is the some other code that meets my businees requirement
        //and changes the status to Completed and trying to update type a

        model.Status = "Completed";
        _repoEaEnrollment.Update(model); // exception .ie. it can not track 
                                         //  two distinct  instance. They are distinct becuase  
                                         //they have same typeand same primary key

我通过将第一个实例从更改跟踪器中分离来解决了这个问题。以下是解决方案代码...

 var model = new EaEnrollment()
        {
            Id = 1,
            UserId = 2,
            AllocationType = "Individual",
            Status = "In Progress"
        };
        _repoEaEnrollment.Update(model);
        _repoEaEnrollment.SaveChanges();
        //here is the some other code that meets my businees requirement
        //and changes the status to Completed and trying to update type a

        //detach first instance from change tracker
        _repoEaEnrollment.Detach(model);

        //Update again
        model.Status = "Completed";
        _repoEaEnrollment.Update(model); // no exception  

0

我也遇到了同样的问题。我正在使用 .Net 6。

我试图获取实体并在同一个方法中进行更新。您可以在查询中添加 `AsNoTracking` 并像这样更新:

public async Task<Student> GetStudentById(long id, long schoolId)
{
    return await _context.Students.AsNoTracking()
                .FirstOrDefaultAsync(student => student.Id == id && student.SchoolId== schoolId);
}

public async Task<long> UpdateStudent(Student student)
{
    long updatedStudentId = 0;
    var studentInDb = await GetStudentById(student.Id, student.SchoolId);
    if(studentInDb != null)
    {
        _context.Students.Update(student);                
        updatedStudentId = await _context.SaveChangesAsync();
    }
    return updatedStudentId;
}

0
在我的情况下,我在一个表中有三个组合键,但是在配置中我只添加了其中两个而错过了第三个。
当我添加第三个键时,这个问题得到了解决!

-1

对我来说,以下方法在 .net 6 中有效:

dbcontext.Remove(EntityInstance);
dbcontext.model.Update(EntityInstance);

以上已生成更新语句并按预期成功运行。

以下也已经工作:

dbcontext.model.Remove(EntityInstance);
dbcontext.model.Update(EntityInstance);

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