Reputation: 160
EF Core throws an exception:
System.InvalidOperationException
: 'The instance of entity type 'Appointment
' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
when trying to insert a model even though its primary key = 0.
Appointment.cs
public class Appointment
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public Doctor Doctor { get; set; }
public Patient Patient { get; set; }
public DateTime DateTime { get; set; }
public Appointment()
{
}
public Appointment(Doctor doctor, Patient patient)
{
this.Doctor = doctor;
this.Patient = patient;
this.DateTime = DateTime.Now;
}
public Appointment(Doctor doctor, Patient patient, DateTime dateTime)
{
this.Doctor = doctor;
this.Patient = patient;
this.DateTime = dateTime;
}
}
How I create my Appointment instance:
internal async Task AddAppointmentAsync(int doctorId, int patientId, DateTime dateTime)
{
Doctor doctor = null;
Patient patient = null;
//Retrieve doctor from the db
using (var doctorController = new DoctorController())
await Task.Run(() => doctor = doctorController.GetDoctor(doctorId));
if (doctor == null)
throw new KeyNotFoundException("Doctor with the specified id doesn't exist.");
//Retrieve patient from the db
using (var patientController = new PatientController())
await Task.Run(() => patient = patientController.GetPatient(patientId));
if (patient == null)
throw new KeyNotFoundException("Patient with the specified id doesn't exist.");
//Create and insert appointment into the db
Appointment appointment = new Appointment(doctor, patient, dateTime);
using (_controller = new AppointmentController())
await Task.Run(() => _controller.AddAppointment(appointment));
}
Add appointment method
public void AddAppointment(Appointment appointment)
{
if (appointment == null) throw new ArgumentNullException(nameof(appointment));
if (appointment.Doctor == null) throw new ArgumentNullException(nameof(appointment.Doctor));
if (appointment.Patient == null) throw new ArgumentNullException(nameof(appointment.Patient));
Doctor doctor = appointment.Doctor;
Patient patient = appointment.Patient;
appointment.Doctor = null;
appointment.Patient = null;
this._context.Appointments.Add(appointment);
appointment.Doctor = doctor;
appointment.Patient = patient;
this._context.SaveChanges(); //Exception is thrown here
}
My context:
public class HospitalContext : DbContext
{
private const string _connectionString = "Server=(localdb)\\mssqllocaldb;Database=ClientDb;Trusted_Connection=True;";
public DbSet<Doctor> Doctors { get; private set; }
public DbSet<Patient> Patients { get; private set; }
public DbSet<Appointment> Appointments { get; private set; }
public DbSet<MedicalRecord> MedicalRecords { get; private set; }
public HospitalContext()
: base()
{
}
public HospitalContext(DbContextOptions<HospitalContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Doctor>()
.HasMany(d => d.Appointments)
.WithOne(a => a.Doctor)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Doctor>()
.HasMany(w => w.WorkDays)
.WithOne(a => a.Doctor)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Patient>()
.HasMany(p => p.Appointments)
.WithOne(p => p.Patient)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Patient>()
.HasMany(p => p.MedicalRecords)
.WithOne(m => m.Patient)
.OnDelete(DeleteBehavior.Cascade);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_connectionString);
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
optionsBuilder.EnableSensitiveDataLogging();
}
}
}
What I'm passing to the method:
What I already have in my Appointments table:
Update
It looks like my table cannot contain multiple entries with the same DoctorId
and PatientId
foreign keys.
e.g.: Exception thrown when trying to add an entry with DoctorId=3, PatientId=1:
Upvotes: 0
Views: 3888
Reputation: 160
Assigning entity's State
to Added
before adding it to the context solved the issue.
Add appointment method
public void AddAppointment(Appointment appointment)
{
if (appointment == null) throw new ArgumentNullException(nameof(appointment));
if (appointment.Doctor == null) throw new ArgumentNullException(nameof(appointment.Doctor));
if (appointment.Patient == null) throw new ArgumentNullException(nameof(appointment.Patient));
this._context.Entry(appointment).State = EntityState.Added;
this._context.Appointments.Add(appointment);
this._context.SaveChanges();
}
Upvotes: 1
Reputation: 2654
If the entity you try to insert has the ID of an entity already in the database, then very likely it IS the entity already in the database.
Maybe you do something like:
var item = YourCollection.First();
item.ID = 0;
item.DateTime = DateTime.Now
YourCollection.Add(item);
This is not going to work and will produce your behaviour. You have to physically create a new Item with new Appointment()
The same would also happen, if you insert it twice
YourCollection.Add(item);
YourCollection.Add(item);
That's rather not your case, cause you have the ID of an existing item assigned.
The tracking by ID is only one part of Entity Framework, but it also tracks the Entities by reference.
Upvotes: 1