使用 AsNoTracking() 获取 'The instance of entity type cannot be tracked because another instance with same key value for is already being tracked'

Getting 'The instance of entity type cannot be tracked because another instance with same key value for is already being tracked' with AsNoTracking()

我正在使用 Entity Framework Core 创建我的 Telegram 机器人。我有这些型号:

public class User
{
    public int Id { get; set; }
    public long UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
    public ICollection<Chat> Chats { get; set;} = new List<Chat>();
    public ICollection<Team> CreatedTeams { get; set; } = new List<Team>();
    public ICollection<Team> Teams { get; set; } = new List<Team>();
}

public class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? ChatId { get; set; }
    public Chat Chat { get; set; }
    public int? CreatorId { get; set; }
    public User Creator { get; set; }
    public ICollection<User> Users { get; set; } = new List<User>();
}

当我处理传入消息时,我检查数据库中是否存在具有相同 UserIdUser。如果它存在,那么我就得到它,如果不存在 - 在 Telegram User 和 return 的基础上创建一个新的。

public async Task<User> GetNewOrExistingUserAsync(Telegram.Bot.Types.User telegramUser)
{
    var user = this.GetUserByUserId(telegramUser.Id);

    if (user is null)
    {
        await this.userRepository.AddAsync((User)telegramUser);
        return this.GetUserByUserId(telegramUser.Id);
    }

    return user;
}

private User GetUserByUserId(long userId)
{
    return this.userRepository.GetAll()
            .Include(user => user.Chats)
            .Include(user => user.Teams)
            .Include(user => user.CreatedTeams)
            .AsNoTracking()
            .FirstOrDefault(user => user.UserId == userId);
 }

之后,我通过消息中指定的名称获得 Team

public Team GetChatTeamByName(string name, long chatId)
{
    return this.teamRepository.GetAll()
         .Include(team => team.Creator)
         .Include(team => team.Users)
         .AsNoTracking()
         .FirstOrDefault(team => team.Chat.ChatId == chatId && team.Name.Equals(name));
}

最后我将 user 添加到 team(我检查它是否已经存在于 team 中,但即使没有这个检查我也会收到这个错误):

team.Users.Add(user);
await this.teamRepository.UpdateAsync(team);

这里我得到这个错误:'无法跟踪实体类型 'User' 的实例,因为另一个具有与 {'Id'} 相同键值的实例已经被跟踪'。 UpdateAsync() 使用 Entities.Update() 方法并保存更改。 Entities.Update() 调用后发生错误。我不明白,它从哪里开始跟踪用户 - 如您所见,我在所有相关查询中使用 AsNoTracking()。我可以假设,它在添加新用户后开始跟踪,但即使用户已经在数据库中也会发生错误。

我已经尝试按照人们的建议使用 Entry<User>(user).State = EntityState.Detached;,就在将用户添加到团队之前或之后,但错误是一样的。我的 ASP 应用程序中的上下文是 Transient,但即使 Scoped 也会出现此错误。你能帮我吗?或者有没有其他方法可以在不调用 Update() 方法的情况下将 user 添加到 team

整个方法是:

public async Task<Message> AddUserToTeamAsync(ITelegramBotClient botClient, Message message)
{
    var user = await this.userService.GetNewOrExistingUserAsync(message.From);

    var teamName = message.Text.SplitToWords()[1];
    var team = this.teamService.GetChatTeamByName(teamName, message.Chat.Id);

    team.Users.Add(user);
    await this.teamRepository.UpdateAsync(team);
    .....
}

更新。GetAll()方法:

public virtual IQueryable<T> GetAll()
{
    return this.Entities;
}

您似乎没有完全理解 AsNoTracking 的作用:它显式地将实体与数据库断开连接。因此,如果你想 Update 该实体,你不应该首先断开它。

这是一个关系数据库。如果要将现有的 user 添加到 team,则需要跟踪现有的对象。否则数据库将尝试创建一个具有完全相同值的新数据库,并且会发生冲突。

DEBATABLE 旁注:你为什么使用 Include,而不是延迟加载?我们公司 运行 有几个大型应用程序,几乎从来不用 Include

编辑:我做了一个小测试

namespace UserTeams
{
    public class User
    {
        public int Id { get; set; }
        public long UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public ICollection<Team> CreatedTeams { get; set; } = new List<Team>();
        public ICollection<Team> Teams { get; set; } = new List<Team>();
    }

    public class Team
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int? CreatorId { get; set; }
        public User Creator { get; set; }
        public ICollection<User> Users { get; set; } = new List<User>();
    }
}
using Microsoft.EntityFrameworkCore;

namespace UserTeams
{
    internal class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Team> Teams { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=localhost\SQLEXPRESS;Database=Test;Trusted_Connection=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>().HasMany(user => user.Teams).WithMany(team => team.Users);
            modelBuilder.Entity<User>().HasMany(user => user.CreatedTeams).WithOne(team => team.Creator);

            modelBuilder.Entity<Team>().HasOne(team => team.Creator)
                .WithMany(user => user.CreatedTeams)
                .HasForeignKey(team => team.CreatorId);
            modelBuilder.Entity<Team>().HasMany(team => team.Users).WithMany(user => user.Teams);
        }
    }
}
// See https://aka.ms/new-console-template for more information
using UserTeams;

using (var myContext = new MyContext())
{
    myContext.Users.Add(new User
    {
        UserId = 123,
    });

    myContext.Teams.Add(new Team
    {
        Name = "myTeam",
    });

    myContext.SaveChanges();
}

using (var myContext = new MyContext())
{
    var user = myContext.Users.First(u => u.UserId == 123);

    var team = myContext.Teams.First(t => t.Name == "myTeam");

    team.Users.Add(user);
    user.Teams.Add(team);
    myContext.SaveChanges();
}

我没有遇到碰撞。