Timo Falter
Timo Falter

Reputation: 67

Entity Framework DbContext is trying to insert data, that already exists

I have two DbContext classes. The ServerContext has foreign key to UserContext.

ServerContext:

public ServerContext(DbContextOptions<ServerContext> options)
  : base(options) {
}

public DbSet<Server> Server { get; set; }

protected override void OnModelCreating(ModelBuilder builder) 
{
    base.OnModelCreating(builder);
    builder.Entity<Server>().ToTable("Server");

    builder.Entity<User.User>().HasMany(x => x.Servers).WithOne(x => x.User);
}

Server:

public class Server 
{
    public string ServerId { get; set; }

    [Required]
    [InverseProperty("User")]
    public string UserId { get; set; }
    public virtual User.User User { get; set; }
}

UserContext:

public class UserContext : IdentityDbContext<User> 
{
    public UserContext(DbContextOptions<UserContext> options)
      : base(options) 
    {
    }

    public DbSet<User> User { get; set; }

    protected override void OnModelCreating(ModelBuilder builder) 
    {
        base.OnModelCreating(builder);
        builder.Entity<User>().ToTable("User");
    }
}

User:

public class User : IdentityUser 
{
    public virtual ICollection<Server.Server> Servers { get; set; }
}

On startup I'm fetching all Server data into the cache and updating it every minute.

The error happens in the model. On the Model I'm using the server data. After it, I'm checking if the server data contains a non null user data. Is it null, I'm loading it from the database (in this step the error is thrown).

public async Task OnGetAsync() 
{
    Servers = await this.ServerManager.GetServersAsync();

    foreach (var server in Servers) 
    {
        if(server == null) 
            continue;

        // This code triggers the error
        var user = await UserManager.FindByIdAsync(server.UserId);

        if(user == null) 
           continue;

        server.User = user;
    }
}

User:

  public class User : IdentityUser {

    public virtual ICollection<IdentityRole> Roles { get; set; }

    public virtual ICollection<Server.Server> Servers { get; set; }
  }

6 queries for one user data: https://pastebin.com/ud9eeKKe

After every minute I'm saving the data and I'm getting the following error:

DbUpdateException: An error occurred while updating the entries. See the inner exception for details. MySql.Data.MySqlClient.MySqlException: Duplicate entry '2f2469d5-b35e-42d8-a90c-a6cc3b899b5a' for key 'PRIMARY' MySql.Data.MySqlClient.MySqlException: Duplicate entry '2f2469d5-b35e-42d8-a90c-a6cc3b899b5a' for key 'PRIMARY'.

Upvotes: 0

Views: 839

Answers (1)

Nikolaus
Nikolaus

Reputation: 1869

As you mentioned, that you could change the code of the GetServersAsync method, I expect the DbContext-Call looks similar to this:

return ServerContext.Servers;

You could change it to:

return ServerContext.Servers.Include(u=>u.User);

This way, you could skip the error causing code outside, because the User is already populated.

The error actually occurs, because you set the User-Property to a tracked Entity.

Alternatively, you can set the User-Property null, before saving. Or you could set the User as EntityEntry as unchanged before setting the User as User-Property (for Performance-issues, I wouldn’t recommend this).

As another option you could integrate Lazy-Loading, which was introduced with EF Core 2.1 as described here.

Update:

You could load your servers NotTracked to prevent the saving issue:

return ServerContext.Servers.AsNoTracking();

Then you could add the User, like you already do... Later, before saving the servers, you have to do the following steps:

  1. Set the User null again

  2. Add the Server-Object as Updated to the Context

  3. Call SaveChanges

Upvotes: 1

Related Questions