4

我正在努力理解一些事情。我有一个 .Net Core 2.2 Web API,带有一个 MySQL 8 数据库,并使用 Pomelo 库连接到 MySQL 服务器。

我有一个看起来像这样的 PUT 操作方法:

// PUT: api/Persons/5
[HttpPut("{id}")]
public async Task<IActionResult> PutPerson([FromRoute] int id, Person person)
{
    if (id != person.Id)
    {
        return BadRequest();
    }

    _context.Entry(person).State = EntityState.Modified;

    try
    {
        _context.SaveChanges(); // Works
        // await _context.SaveChangesAsync(); // Doesn't work
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!PersonExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

根据我在上面代码片段中的评论,当我调用_context.SaveChanges()时,它可以工作(即它更新 MySQL 数据库中的相关记录,并返回 1)但是当我调用await _context.SaveChangesAsync()时,它不起作用(它不更新记录,并且返回 0)。它不会抛出异常或任何东西 - 它只是不会更新记录。

有任何想法吗?

4

2 回答 2

4

正如我在上面的评论中所说,EF Core 没有真正的同步方法。同步方法(例如SaveChanges)仅仅阻塞异步方法(例如SaveChangesAsync)。因此,SaveChanges如果不可行,那是不可能的SaveChangesAsync,因为前者只是后者的代理。这里还有一些其他问题在起作用,这从您提供的代码中并不明显。

然而,我写这个作为答案的原因是你这样做的方式,一般来说,是错误的,我相信如果做得对,问题可能会消失。您永远不应该,我的意思是永远不要直接将从请求正文创建的实例直接保存到您的数据库中。这提供了一个攻击向量,允许恶意用户以不希望的方式更改您的数据库。您已经通过检查 id 未被修改来部分覆盖了这一点,但是用户仍然可以更改他们不应该被允许的内容。

抛开安全漏洞不谈,不这样做是有实际理由的。API 用作反腐败层,但前提是您将实体与客户端交互的对象分离。当您直接使用实体时,您将数据库与 API 层紧密耦合,因此数据库级别的任何更改都需要新版本的 API,更糟糕的是,没有机会弃用以前的版本。所有客户端必须立即更新,否则它们的实现将中断。通过向客户端公开 DTO 类,数据库可以独立于 API 发展,因为您可以添加任何必要的反腐败逻辑来弥合两者之间的差距。

总而言之,这就是您的方法的结构:

// PUT: api/Persons/5
[HttpPut("{id}")]
public async Task<IActionResult> PutPerson([FromRoute] int id, PersonModel model)
{
     // not necessary if using `[ApiController]`
    if (!ModelState.IsValid)
        return BadRequest();

    var person = await _context.People.FindAsync(id);
    if (person == null)
        return NotFound();

    // map `model` onto `person`

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        // use an optimistic concurrency strategy from:
        // https://docs.microsoft.com/en-us/ef/core/saving/concurrency#resolving-concurrency-conflicts
    }

    return NoContent();
}

我想让代码保持直截了当,但为了处理乐观并发,我实际上建议使用 Polly 异常处理库。您可以设置重试策略,以在纠错后继续尝试进行更新。否则,您需要在 try/catch 中的 try/catch 中使用 try/catch,等等。此外,DbUpdateConcurrencyException您应该始终以某种方式处理它,因此重新抛出它没有任何意义。

于 2019-09-10T12:48:23.670 回答
4

对于在这个问题上浪费时间的人,我真的很抱歉。我发现了问题,这是我在 dbContext 中犯的一个愚蠢的错误。我有一个审计跟踪设置,所以我覆盖了SaveChangesAsync,OnBeforeSaveChangesOnAfterSaveChanges. 该代码中有一个错误。但是,我并没有压倒一切SaveChanges,这就是为什么它仍然有效。对不起!

于 2019-09-30T09:35:03.663 回答