【问题标题】:EF: The instance of entity type X cannot be tracked because another instance of this type with the same key is already being trackedEF:无法跟踪实体类型 X 的实例,因为已在跟踪具有相同键的该类型的另一个实例
【发布时间】:2017-03-05 10:08:53
【问题描述】:

我在我的 http 请求中发送一个 Json 格式的用户实体,如下所示:

POST http://localhost:52054/api/Authentication/DeleteAccessToken HTTP/1.1
Host: localhost:52054
Content-Type: application/json

{"id":1,"userName":"mnoureldin","accessToken":{"id":1,"token":"123ABC456EFG","userId":1}}

而我的控制器(在 EF-core 中)会这样处理:

[HttpPost]
public IActionResult DeleteAccessToken([FromBody]User user)
{
    using (var Context = new UnitOfWork().Context)
    {
        var userEntity = Context.Users.Find(user.Id); // Get the real entity of the user received as json
        if (userEntity != null)
        {
            var accessTokenEntity = Context.AccessTokens.Find(userEntity.AccessToken.Id); // Find the entity of the accesstoken (I tried also directly by accessing the navigation property of user entity)
            Context.AccessTokens.Remove(accessTokenEntity);
            return Ok();
        }
        else
        {
            return Unauthorized();
        }
    }
}

但是Context.AccessTokens.Remove(accessTokenEntity);这一行抛出了这个异常:

“System.InvalidOperationException”类型的异常发生在 Microsoft.EntityFrameworkCore.dll 但未在用户代码中处理

附加信息:实体类型“AccessToken”的实例 无法跟踪,因为此类型的另一个实例具有相同的 密钥已被跟踪。添加新实体时,对于大多数关键 如果没有设置键,则将创建唯一的临时键值 (即,如果为键属性分配了其类型的默认值)。 如果您要为新实体显式设置键值,请确保它们 不与现有实体或生成的临时值发生冲突 对于其他新实体。附加现有实体时,请确保 只有一个具有给定键值的实体实例附加到 上下文。

我也尝试直接从 userEntity 访问 AccessToken 导航属性,但存在相同的异常。

这是我的 UnitOfWork 初始化:

public UnitOfWork()
{
    // Configure EF connection
    var optionsBuilder = new DbContextOptionsBuilder<CustomDbContext>();
    optionsBuilder
        .UseMySQL(@"server=192.168.1.35; port=3306; sslmode=none;
                    userid=root;
                    pwd=P@ssword;
                    database=dotnet;");

    Context = new CustomDbContext(optionsBuilder.Options);

    // Configure data loading method to explicit
    Context.AccessTokens.Load();
}

我的 CustomBdContext:

public class CustomDbContext : DbContext
{
    // Tell EF to map the entities to tables
    public DbSet<User> Users { get; set; }
    public DbSet<AccessToken> AccessTokens { get; set; }

    public CustomDbContext(DbContextOptions options) : base(options)
    {
    }
}

我有以下具有一对一关系的简单数据模型:

User ----- AccessToken

用户:

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual AccessToken AccessToken { get; set; }
}

访问令牌:

public class AccessToken
{
    public int Id { get; set; }
    public string Token { get; set; }

    [ForeignKey("User"), Required]
    public int UserId { get; set; }
    public virtual User User { get; set; }
}

有人可以帮我解决这个问题吗?我不明白到底发生了什么..

【问题讨论】:

  • 第一个问题:Context在哪里初始化?
  • @GertArnold 是的,通过调试从数据库中获取 userEntityaccessTokenEntity 变量。
  • 这不是我的问题的答案。你在哪里初始化 Context 本身?在查看此“已被跟踪”错误时,这始终是信息的第一个重要部分。
  • 我仍然觉得我们没有看到全貌。但我会开始将其建模为真正的 1:1 关系(即 AccessToken 的 PK 也是它对用户的 FK)。
  • EF 通过Find、LINQ 或Load 从数据库中获取的任何对象都存储在上下文的缓存中并跟踪更改(除非您主动禁用跟踪)。上下文是一个身份映射,因此每个实体只能跟踪一个副本。当 EF 获取实体时,它将自行处理此身份规则。手动将实体附加到上下文(例如通过添加它们)可能会导致您的问题。但这似乎并没有在可见代码中发生。也许这会提示您在哪里进一步寻找。

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


【解决方案1】:

EF 似乎已经在跟踪 user 和其中的 AccessToken。因此,让我们尽量避免获取同一实体的另一个实例并重用已跟踪的实例。

试试

[HttpPost]
public IActionResult DeleteAccessToken([FromBody]User user)
{
    // Requires System.Linq
    if (Context.Users.Any(u => u.Id == user.Id))
    {
        var accessTokenEntity = Context.AccessTokens.Find(user.AccessToken.Id); // Find the entity of the accesstoken (I tried also directly by accessing the navigation property of user entity)
        Context.AccessTokens.Remove(accessTokenEntity);

        // NOTE: You re not saving?
        return Ok();
    }
    else
    {
        return Unauthorized();
    }
}

或者尝试:

[HttpPost]
public IActionResult DeleteAccessToken([FromBody]User user)
{
    if (Context.Users.Any(u => u.Id == user.Id))
    {
        Context.AccessTokens.Remove(user.AccessToken);
        return Ok();
    }
    else
    {
        return Unauthorized();
    }
}

【讨论】:

  • 这看起来像是一种解决方法,对最初导致问题的原因的解释为零。
  • 我认为异常消息很好地解释了这个问题。似乎 EF 已经在跟踪实体,所以我试图避免获取同一(跟踪)实体的另一个实例。虽然,对我来说,EF 如何跟踪刚刚来自POST 的实体是一个谜。
  • 我同意 Evk 的观点,它是关于解开谜团的。不是为了避免它。
  • 是的,但为什么呢?在正常使用的情况下,OP 代码不应该发生这种情况。
  • @HristoYankov 第一个代码有效,但我不明白为什么会这样,我可以提供更多信息来了解为什么会这样吗?我没有尝试第二个代码,但我想它可能不起作用?因为user 只是一个从 Json 文本创建的对象,而不是引用数据库条目,对吗?
【解决方案2】:

尝试替换这一行:

var accessTokenEntity = Context.AccessTokens.Find(userEntity.AccessToken.Id);

到:

var accessTokenEntity = Context.AccessTokens.AsNoTracking().Find(userEntity.AccessToken.Id);

【讨论】:

    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 2023-01-03
    • 2018-06-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多