我正在使用ASP.NET WebApi 2和Entity Framework 6.1.0-alpha1编写一个简单的“Todo App”。我的目标是限制访问,每个用户只能查看/编辑自己的待办事项。
示例:
// 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);
}
没关系。为删除添加了类似的检查,并在创建时将CreatorId设置为当前用户的ID。但是,我有更新问题。
我试过了:
// 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);
}
但是,它会在标记的行上抛出System.InvalidOperationException
:
System.InvalidOperationException
附加“ModernWeb.Domain.Models.Todo”类型的实体失败 因为同一类型的另一个实体已经具有相同的主要实体 核心价值。使用“附加”方法或设置时可能会发生这种情况 如果有任何实体,则实体的状态为“未更改”或“已修改” 图表具有冲突的键值。这可能是因为一些 实体是新的,尚未收到数据库生成的密钥 值。在这种情况下,使用“添加”方法或“已添加”实体状态 跟踪图形,然后将非新实体的状态设置为 “视情况而定”或“修改”。
如果我使用FindByAsync()删除块,它将不会抛出异常。
我也尝试使用_db.Entry(todo).OriginalValue
,但找不到有效的语法。
我该如何克服这个问题?这种情况的最佳做法是什么?
答案 0 :(得分:2)
当您调用FindAsync
时,返回的实体实例已附加到上下文。所以没有理由_db.Entry(todo).State = EntityState.Modified;
<强>更新强>
我想我知道你在这里想做什么。试试这个:
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;
当您调用.AsNoTracking().SingleOrDefaultAsync
而不是FindAsync
时,返回的original
实体将不会附加到上下文。然后,您可以将传递给控制器操作的那个设置为Modified
,并且由于上下文尚未跟踪具有相同ID的其他实体,因此您不应再获得该异常。
作为次要说明,由于传入您的参数的Todo实体已经具有Id属性,因此不应该将其作为控制器操作中的单独参数传递。你应该能够做到这一点:
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);
}