I'm working with VS 2022 for Mac 17.6. My current project is Razor web app with .NET 7.0 and Entity Framework with SQLite.
This is what I want to achieve: in my data model I have, among others, two tables EventInstance
and Note
. Several rows in EventInstance
are related to one row in Note
. Whenever user edits one EventInstance
, changes to the linked Note
are supposed to appear as well in all other EventInstances
related to the same row.
This is part of my data model as of now:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// ...
modelBuilder.Entity<EventInstance>()
.HasOne(i => i.Note)
.WithMany()
.HasForeignKey(i => i.NoteId);
}
public class EventInstance
{
public int Id { get; set; }
[Required]
[Display(Name = "Spezifikation")]
public required string Specification { get; set; }
// ...
public int? NoteId { get; set; }
[Display(Name = "Notiz")]
public Note? Note { get; set; }
// ...
}
public class Note
{
public int Id { get; set; }
[Display(Name = "Notiz"), DataType(DataType.MultilineText)]
public string? Content { get; set; }
}
In my Create.cshtml.cs
everything seem to work fine. I have manually coded these lines:
Note note = new Note() { Content = "" };
_context.Note.Add(note);
_context.SaveChanges();
int noteId = note.Id;
After creating a new EventInstance
, I can check in the database that a new row in Event
has been added and assigned an Id
.
This is my EXISTING EditModel / PageModel für EventInstance:
public class EditModel : PageModel
{
private readonly RazorPagesSchedule.Data.ScheduleDbContext _context;
public EditModel(RazorPagesSchedule.Data.ScheduleDbContext context)
{
_context = context;
}
[BindProperty]
public EventInstance EventInstance { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null || _context.EventInstance == null)
{
return NotFound();
}
var eventinstance = await _context.EventInstance
.Include(i => i.EventTemplate)
.Include(i => i.ResponsiblePerson)
.Include(i => i.Note)
.FirstOrDefaultAsync(m => m.Id == id);
if (eventinstance == null)
{
return NotFound();
}
EventInstance = eventinstance;
var list = new SelectList(_context.EventTemplate, "Id", "Description").ToList();
list.Insert(0, new SelectListItem { Text = "--- Keine Aufgabenvorlage ---", Value = "" });
ViewData["EventTemplateId"] = list;
var persons = new SelectList(_context.Person.OrderBy(p => p.Sn), "Id", "Sn");
ViewData["ResponsiblePersonId"] = persons;
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(EventInstance).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EventInstanceExists(EventInstance.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool EventInstanceExists(int id)
{
return (_context.EventInstance?.Any(e => e.Id == id)).GetValueOrDefault();
}
}
EXPECTED BEHAVIOUR: When editing an Event, no new row in Note is to be created, but the existing linked row ought to be updated.
I feel like you should be loading the entity from the DB and modify it instead of taking it from the client verbatim, but the reason it’s creating a new Note is that you don’t tell it it’s modified. Add this:
_context.Attach(EventInstance.Note).State = EntityState.Modified;
To be safe, probably do a null check for Note
first and be sure its Id is > 0. Really, you need to check that the Id actually exists, or you’ll get a foreign key exception.
You also need the Note’s Id in your form:
<input asp-for="@Model.EventInstance.Note.Id" hidden />
Be aware it must be Note.Id
not NoteId
, as the latter would try to save a Note with Id 0, which would normally add a new row, but will crash when set to EntityState.Modified
.