0

我正在使用 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,但找不到有效的语法。

我该如何克服这个问题?对于这种情况有什么最佳实践吗?

4

1 回答 1

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而不是 时FindAsyncoriginal返回的实体将不会附加到上下文。然后您可以将传递给控制器​​操作的那个设置为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);
}
于 2014-01-25T00:22:17.497 回答