C
C#3w ago
Zoli

Resolving Entity Framework Core Tracking Issues with Shared Relationships in .NET MAUI and SQLite

Context: I am developing a .NET MAUI application using Entity Framework Core with SQLite as the local database provider. The application involves managing workouts, exercises, and exercise variants. Below is a detailed explanation of my workflow and the issue I am encountering: Workflow Saving Default Exercise Variants Locally Upon user login, I fetch a predefined list of exercise variants (with fixed IDs) and save them locally using the following code:
await _dbContext.AddRangeAsync(exerciseVariants, cancellationToken);
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
await _dbContext.AddRangeAsync(exerciseVariants, cancellationToken);
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
Retrieving All Exercise Variants To retrieve all locally stored exercise variants, I use the following query:
var allExerciseVariants = await dbContext.ExerciseVariant
.AsNoTracking()
.ToListAsync(cancellationToken);
var allExerciseVariants = await dbContext.ExerciseVariant
.AsNoTracking()
.ToListAsync(cancellationToken);
Creating and Saving a Workout When creating a workout, I define it with associated exercises as shown below:
var workout = new Workout()
{
Title = "Test"
};

var exercises = new List<Exercise>()
{
new Exercise()
{
Note = "Test note",
ExerciseVariant = allExerciseVariants.FirstOrDefault(), // Selecting the first variant for testing purposes
}
};

workout.Exercises.AddRange(exercises);

await dbContext.Workout.AddAsync(workout);
await dbContext.SaveChangesAsync();
var workout = new Workout()
{
Title = "Test"
};

var exercises = new List<Exercise>()
{
new Exercise()
{
Note = "Test note",
ExerciseVariant = allExerciseVariants.FirstOrDefault(), // Selecting the first variant for testing purposes
}
};

workout.Exercises.AddRange(exercises);

await dbContext.Workout.AddAsync(workout);
await dbContext.SaveChangesAsync();
Continue in the comment section
4 Replies
Zoli
ZoliOP3w ago
Entity Structure: Here is the class structure for my entities and their relationships:
public class Workout
{
[Key]
public Guid Id { get; set; }

public required string Title { get; set; }

public string? Note { get; set; }

public ICollection<Exercise> Exercises { get; } = new List<Exercise>();
}

public class Exercise
{
[Key]
public Guid Id { get; set; }

public Guid? ExerciseVariantId { get; set; }
public ExerciseVariant ExerciseVariant { get; set; }

public string? Note { get; set; }

// Foreign keys
public Guid? WorkoutId { get; set; }
public Workout? Workout { get; set; }
}

public class ExerciseVariant
{
[Key]
public Guid Id { get; set; }

public string Name { get; set; }

public BodyPart BodyPart { get; set; }

public Category Category { get; set; }

public string? Video { get; set; }

public bool IsCustom { get; set; }

public ICollection<Exercise> Exercises { get; } = new List<Exercise>();
}
public class Workout
{
[Key]
public Guid Id { get; set; }

public required string Title { get; set; }

public string? Note { get; set; }

public ICollection<Exercise> Exercises { get; } = new List<Exercise>();
}

public class Exercise
{
[Key]
public Guid Id { get; set; }

public Guid? ExerciseVariantId { get; set; }
public ExerciseVariant ExerciseVariant { get; set; }

public string? Note { get; set; }

// Foreign keys
public Guid? WorkoutId { get; set; }
public Workout? Workout { get; set; }
}

public class ExerciseVariant
{
[Key]
public Guid Id { get; set; }

public string Name { get; set; }

public BodyPart BodyPart { get; set; }

public Category Category { get; set; }

public string? Video { get; set; }

public bool IsCustom { get; set; }

public ICollection<Exercise> Exercises { get; } = new List<Exercise>();
}
Data Context Configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure Exercise -> ExerciseVariant relationship
modelBuilder.Entity<Exercise>()
.HasOne(e => e.ExerciseVariant)
.WithMany(ev => ev.Exercises)
.HasForeignKey(e => e.ExerciseVariantId);

// Configure Workout -> Exercise relationship
modelBuilder.Entity<Workout>()
.HasMany(w => w.Exercises)
.WithOne(e => e.Workout)
.HasForeignKey(e => e.WorkoutId)
.OnDelete(DeleteBehavior.Cascade);

base.OnModelCreating(modelBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure Exercise -> ExerciseVariant relationship
modelBuilder.Entity<Exercise>()
.HasOne(e => e.ExerciseVariant)
.WithMany(ev => ev.Exercises)
.HasForeignKey(e => e.ExerciseVariantId);

// Configure Workout -> Exercise relationship
modelBuilder.Entity<Workout>()
.HasMany(w => w.Exercises)
.WithOne(e => e.Workout)
.HasForeignKey(e => e.WorkoutId)
.OnDelete(DeleteBehavior.Cascade);

base.OnModelCreating(modelBuilder);
}
The Issue: When I attempt to create a workout and associate it with an existing ExerciseVariant, I encounter the following error: The instance of entity type 'ExerciseVariant' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. I have already tried using AsNoTracking when retrieving the ExerciseVariant, but the issue persists. How can i solve this issue? Is there a recommended way to handle such scenarios where an entity with shared relationships needs to be reused?
Sehra
Sehra3w ago
it's likely being tracked from await _dbContext.AddRangeAsync(exerciseVariants, cancellationToken);, you can clear it with _dbContext.ChangeTracker.Clear() or detach entities individually. keep in mind DbContext is supposed to be short lived
Zoli
ZoliOP3w ago
Shall I call _dbContext.ChangeTracker.Clear() after each await dbContext.SaveChangesAsync();?
Joschi
Joschi3w ago
Not really. Most of the time your dbcontext should not outlive a SaveChanges call. One dbContext should more or less be used for one unit of work.

Did you find this page helpful?