Ho un campo URL nella mia tabella per ciascuno dei corsi. E lo sto usando come parametro del percorso. L'ho fatto per rendere gli Url facili da usare e, a mio avviso, questo potrebbe anche aiutarmi in SEO (correggimi se ho torto). Con un setup come questo, non sono in grado di capire come creare azioni Edit / Delete.
Course.cs: il modello del corso
public partial class Course
{
public int Id { get; set; }
public string Title { get; set; }
// This is set as Unique Key in the table.
public string Url { get; set; }
public string InnerHtml { get; set; }
}
CourseController.cs: il controller e l'azione Edit per il nostro riferimento.
[HttpPost("Edit/{courseUrl}")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrator")]
public async Task<IActionResult> Edit(string courseUrl, [Bind("Id,Title,Url,InnerHtml")] Course course)
{
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(course);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CourseExists(course.Url))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(course);
}
Il problema: sto ottenendo il seguente errore su questa azione
InvalidOperationException: l'istanza del tipo di entità "Corso" non può essere tracciata perché un'altra traccia con lo stesso valore chiave per {Id "è già tracciata. Quando si allegano entità esistenti, assicurarsi che sia collegata solo un'istanza di entità con un determinato valore di chiave. Potresti utilizzare 'DbContextOptionsBuilder.EnableSensitiveDataLogging' per visualizzare i valori delle chiavi in conflitto.
The WorkAround: commentando il codice sottostante nell'azione, ottieni l'applicazione funzionante. Ma il codice qui sotto serve a verificare se il modello che si sta modificando è contenuto nel DB.
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
Qual è il modo corretto di gestire questo scenario?
Come spiega il messaggio di errore, esiste già un modello caricato dalla ricerca che viene tracciato dall'ORM. È necessario copiare le proprietà desiderate sul modello tracciato se si intende salvarlo.
//...code removed for brevity
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id) {
return NotFound();
}
if (ModelState.IsValid) {
try {
Populate(OriginalCourse, course);
_context.Update(OriginalCourse);
await _context.SaveChangesAsync();
} catch (DbUpdateConcurrencyException) {
if (!CourseExists(course.Url)) {
return NotFound();
} else {
throw;
}
}
return RedirectToAction(nameof(Index));
}
//...code removed for brevity
Dove potrebbe apparire Populate
void Populate(Course original, Cource source) {
original.Title = source.Title;
original.Url = source.Url;
original.InnerHtml = source.InnerHtml;
}
Un'altra opzione potrebbe essere quella di non caricare un'istanza non selezionando / restituendo un elemento dal contesto
//...code removed for brevity
var exists = await _context.Courses.AnyAsync(m => m.Url == courseUrl);
if (!exists) {
return NotFound();
}
//...code removed for brevity
e quindi aggiornare il corso fornito