I found a workaround. I was having this issue with simple model classes.The Create
CRUD page would always fail because Enrollments
was always null
.
public class Course
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
[Display(Name = "Is Teacher?")]
public bool IsTeacher { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment
{
public int ID { get; set; }
public int CourseID { get; set; }
public int PersonID { get; set; }
public Course Course { get; set; }
public Person Person { get; set; }
}
- For
Course
and Person
, the Create
CRUD page would always fail because Enrollments
was always null
. There was a simple workaround for this problem: changing
public ICollection<Enrollment> Enrollments { get; set; }
to
public ICollection<Enrollment> Enrollments { get; set; } = new List<Enrollment>();
- The second issue I was having was with enrollments. I tried changing
public Course Course { get; set; }
public Person Person { get; set; }
to
public Course Course { get; set; } => new() { Name = "" };
public Person Person { get; set; } => new() { Name = "" };
That did not work because it would just add blank entries to the Courses and Person list. This told me it was using the Course and Person fields rather than CourseID and PersonID fields. The solution was to add the ID.
What ended up solving the problem was changing it to:
public Course Course { get => _course == null && CourseID != default ? new() { ID = CourseID, Name = "" } : _course; set => _course = value; }
public Person Person { get => _person == null && PersonID != default ? new() { ID = PersonID, Name = "" } : _person; set => _person = value; }
Course _course; Person _person;
This allowed both the Index
and Create
pages to work correctly.
The final code looked like:
public class Course
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Enrollment> Enrollments { get; set; } = new List<Enrollment>();
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
[Display(Name = "Is Teacher?")]
public bool IsTeacher { get; set; }
public ICollection<Enrollment> Enrollments { get; set; } = new List<Enrollment>();
}
public class Enrollment
{
public int ID { get; set; }
public int CourseID { get; set; }
public int PersonID { get; set; }
public Course Course { get => _course == null && CourseID != default ? new() { ID = CourseID, Name = "" } : _course; set => _course = value; }
public Person Person { get => _person == null && PersonID != default ? new() { ID = PersonID, Name = "" } : _person; set => _person = value; }
Course _course; Person _person;
}
As a summary, to make navigational properties, the following guides:
With a list, you can safely set it to be empty.
With a one-to-one relation, it is more complicated and should look something like this:
public int FieldID { get; set; }
public FieldType Field
{
get => _field == null && FieldID != default ? new() { ID = FieldID, ... } : _field;
set => _field = value;
}
FieldType _field;
where ...
just sets all of the nullable fields to a non-null value to make sure ModelState.IsValid
is true.
I personally disagree with the logic about model view classes. Following this logic every time I modify the functionality of the database (eg. a field name) I have to manually change all of the model view classes to match. I understand what was said about POST attacks but I am referring to situations where all of the fields of the relevant dataset can be modified. Usually, I would only give access to these CRUD models to administrators of the data in question.