Sto scrivendo una semplice "App Todo" utilizzando ASP.NET WebApi 2 e Entity Framework 6.1.0-alpha1. Il mio obiettivo è limitare l'accesso, ogni utente può solo visualizzare / modificare i propri Todos.
Esempio:
// GET api/Todo/5
[ResponseType(typeof(Todo))]
public async Task<IHttpActionResult> GetTodo(int id)
{
var todo = await _db.Todos.FindAsync(id);
if (todo == null)
{
return NotFound();
}
if (todo.CreatorId != _currentUser.Id)
{
return StatusCode(HttpStatusCode.Forbidden);
}
return Ok(todo);
}
Questo va bene. Un controllo simile aggiunto per eliminare e al momento della creazione imposta CreatorId sull'ID dell'utente corrente. Tuttavia, ho un problema con l'aggiornamento.
Ho provato questo:
// PUT api/Todo/5
public async Task<IHttpActionResult> PutTodo(int id, Todo todo)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != todo.Id)
{
return BadRequest();
}
// --- No exception if I remove this block - BEGIN ---
var original = await _db.Todos.FindAsync(id);
if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
{
return StatusCode(HttpStatusCode.Forbidden);
}
// --- No exception if I remove this block - END ---
_db.Entry(todo).State = EntityState.Modified; // Exception thrown here
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
Tuttavia, un System.InvalidOperationException
ha gettato sulla linea segnata:
System.InvalidOperationException
Il collegamento di un'entità di tipo "ModernWeb.Domain.Models.Todo" non è riuscito perché un'altra entità dello stesso tipo ha già lo stesso valore di chiave primaria. Ciò può accadere quando si utilizza il metodo "Allega" o si imposta lo stato di un'entità su "Non modificato" o "Modificato" se alcune entità nel grafico hanno valori di chiavi in conflitto. Ciò potrebbe essere dovuto al fatto che alcune entità sono nuove e non hanno ancora ricevuto valori chiave generati dal database. In questo caso, utilizzare il metodo "Aggiungi" o lo stato dell'entità "Aggiunta" per tracciare il grafico e quindi impostare lo stato delle entità non nuove su "Invariato" o "Modificato" a seconda dei casi.
Se rimuovo il blocco con FindByAsync (), non genererà un'eccezione.
Ho anche provato a usare _db.Entry(todo).OriginalValue
, ma non sono riuscito a trovare una sintassi funzionante.
Come posso superare questo problema? Qualche buona pratica per situazioni come questa?
Quando invochi FindAsync
, l'istanza dell'entità restituita è già associata al contesto. Quindi non esiste alcun motivo per _db.Entry(todo).State = EntityState.Modified;
Aggiornare
Penso di vedere cosa stai cercando di fare qui. Prova questo invece:
var original = await _db.Todos.AsNoTracking()
.SingleOrDefaultAsync(x => x.Id == id);
if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
{
return StatusCode(HttpStatusCode.Forbidden);
}
// --- No exception if I remove this block - END ---
_db.Entry(todo).State = EntityState.Modified;
Quando invochi .AsNoTracking().SingleOrDefaultAsync
anziché FindAsync
, l'entità original
restituita non verrà allegata al contesto. Quindi puoi impostare quello passato all'azione del controller come Modified
, e poiché il contesto non sta già rintracciando un'entità diversa con lo stesso id, non dovresti più ottenere quell'eccezione.
Come nota secondaria, poiché l'entità Todo passata nel tuo argomento ha già una proprietà Id, non ci dovrebbe essere la necessità di passarla come argomento separato nell'azione del controller. Dovresti essere in grado di farlo:
public async Task<IHttpActionResult> PutTodo(Todo todo)
{
if (!ModelState.IsValid || todo == null)
{
return BadRequest(ModelState);
}
var original = await _db.Todos.AsNoTracking()
.SingleOrDefaultAsync(x => x.Id == todo.Id);
if (original == null) return NotFound();
if (original.CreatorId != _currentUser.Id || original.CreatorId != todo.CreatorId)
{
return StatusCode(HttpStatusCode.Forbidden);
}
_db.Entry(todo).State = EntityState.Modified; // Exception thrown here
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoExists(todo.Id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}