1

I'm just getting started with EF Core (on .NET 8). I have two classes Student and StudyYear:

public class Student : Person
{
    public int StartYear { get; set; }
    public StudyYear StartingStudyYear { get; set; } = new();
    public int Class { get; set; }
}

public class StudyYear
{
    public int Year { get; set; }
    [JsonIgnore]
    public ICollection<Student> NewStudents { get; set; } = new HashSet<Student>();
}

I've created a many-to-one relationship:

public static ModelBuilder StudyYear(this ModelBuilder modelBuilder)
{
    var entity = modelBuilder.Entity<StudyYear>();

    entity.HasKey(sy => sy.Year);
    entity
      .HasMany(sy => sy.NewStudents)
      .WithOne(s => s.StartingStudyYear)
      .HasForeignKey(s => s.StartYear)
      .IsRequired();

    entity.Property(sy => sy.Year).ValueGeneratedNever();

    return modelBuilder;
}

When I create the first Student like this:

var student = new() 
{
    Class = 1,
    StartingStudyYear = new() 
    {
        Year = 2023,
    },
}

everything gets created just fine.

The database then looks like this:

Students:

--other data-- Class StartYear
... 1 2023

StudyYears:

Year
2023

But when I add another Student in 2023, I get this exception:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.

MySqlConnector.MySqlException (0x80004005): Duplicate entry '0' for key 'StudyYears.PRIMARY'

I understand that it's trying to add the year 2023 twice like this, but how can I tell it to use the existing one instead of just failing?

I've also tried to load the year from the database if it already exists, like this:

if (context.StudyYears.FirstOrDefault(x => x.Year == model.StartYear) 
      is not StudyYear startYear)
{
    startYear = new StudyYear
    {
        Year = model.StartYear,
    };
}

var entity = context.Add(new Student()
{
    StartingStudyYear = startYear,
    Class = 1,
});

This does not throw an error anymore, but when I load it from the database now, the StartingStudyYear is just an empty StudyYear object.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jonathan
  • 330
  • 1
  • 10
  • 1
    The last try is the correct one. However remove `= new();` from `public StudyYear StartingStudyYear { get; set; } = new();`. And never do that for reference navigation properties because as you've seen, it causes unexpected behaviors. – Ivan Stoev Aug 26 '23 at 06:20
  • How are you "loading it from database"? Are you using `Include` or other method for [Loading Related Data](https://learn.microsoft.com/en-us/ef/core/querying/related-data/)? – Ivan Stoev Aug 26 '23 at 08:47
  • 1
    I'm sorry, it just worked correctly. It's null to prevent a self-referencing loop of course. Thank you. – Jonathan Aug 26 '23 at 09:23

1 Answers1

0

You set a key in StudyYear to be a Year. Key should be unique, so is it possible that you want to insert more than one StudyYear object with the same key? It does not matter if those objects belong to different students.

quain
  • 861
  • 5
  • 18
  • Yes that's exactly what's happening. But how can I properly 'bind' it to the StudyYear if it already exists? – Jonathan Aug 25 '23 at 18:46
  • Simplest way would be to add a property such as `StudentId` to the study year object and Configure binding by that property. – quain Aug 25 '23 at 18:52
  • Also `StudyYear` must have unique key or be keyless entity. Simplest way is to ad its own `Id` property that will be created by the db/ef. – quain Aug 25 '23 at 18:53
  • I use the `Year` as the key in the `StudyYear` class. And I can't add the `StudentId` to the `StudyYear`, as that would make it a one to one relationship – Jonathan Aug 25 '23 at 18:56
  • No, one to many binds on single id on the "many" side - one of many objects points to the same parent id - try it. – quain Aug 25 '23 at 18:57
  • And as I mentioned - key must be unique, so in your case `Year` wont do the trick, because you have more than one entries with the same year. – quain Aug 25 '23 at 18:59
  • If you literally cannot add Id field to `StudyYear`, then research "shadow properties" and configure it that way. Mind that you will need to add this column to the db either way. – quain Aug 25 '23 at 19:00
  • The `StudyYear` has a one-to-many relationship with the `NewStudents`, so shouldn't it be possible? – Jonathan Aug 25 '23 at 19:01
  • try `context.StudyYears.FirstOrDefault(x => x.Year == model.StartYear) == null`. also add student to year. Then add year to db context and save if year received from db was null. if not, just save changes. – quain Aug 25 '23 at 19:17