【问题标题】:how can I validate if there are no errors when executing SaveChangesAsync?如何验证执行 SaveChangesAsync 时是否没有错误?
【发布时间】:2020-08-21 21:19:12
【问题描述】:

我正在测试一个我试图保存重复元素的场景,它向我显示以下错误,因为该元素必须是唯一的(我的表的名称和字段是西班牙语,对于我的代码示例,我最好用英文写):

`Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert duplicate key row in object 'dbo.Generos' with unique index 'IX_Generos_Nombre'.` **The duplicate key value is (Terror).**`
The statement has been terminated.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__164_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---`

如果有任何错误要保存,我想这样做,我可以自定义输出消息而不显示错误消息。

我也不明白为什么result 变量被视为int

[HttpPost]  // I am sending { Nombre: 'Terror'}
public async Task<ActionResult> Post([FromBody] GenreCreationDTO genreCreationDTO)
{
    var entity = mapper.Map<Genre>(genreCreationDTO);
    context.Add(entity);
    var result = await context.SaveChangesAsync(); ///////////////////// problem in this line
    //result is taken as int, why?
    if (!result)
    {
     //show my custom message
    }
     return Ok();
}

【问题讨论】:

  • @GSerg 是唯一的方法吗?我是 c# 的新手,我认为还有另一种方法。当我遇到与数据库相关的某种问题时,您建议放置哪种异常?
  • SaveChangesAsync 方法返回受影响的行数。
  • 我猜你的 GenreCreationDTO 类和 Genre 类中都有一个 Id 属性。如果这是例如一个 int 或 long 且 value > 0,这将提示您下一步该做什么。

标签: c# entity-framework


【解决方案1】:

对于一个简单的应用程序,您可以在插入之前查询数据库以检查名称(或您希望唯一的任何其他字段)是否已经存在,如果有符合该条件的结果,则返回某种类型的回应这么说。像这样的

[HttpPost]
public async Task<ActionResult> Post([FromBody] GenreCreationDTO genreCreationDTO)
{
    var entity = mapper.Map<Genre>(genreCreationDTO);
    var matches = await context.Where(e => e.Name == entity.Name).Select(e => 1).ToListAsync();
   if(matches.Count == 0)
   {
       context.Add(entity);
       var result = await context.SaveChangesAsync();
       return Ok();
   }
   else
   {
     // show custom message
   }  
}

但是,请记住,上述方法不会 100% 地适用于许多客户同时使用的应用程序。问题是,在检查该名称是否已经存在但在插入完成之前,不能保证正在运行请求的线程将被挂起,并且当该线程被挂起时,另一个具有相同名称的请求进入并插入数据进入数据库。除了在插入之前检查该数据是否已经存在之外,最好的办法可能是处理您当前收到的异常。异常应该很少见,应该只发生在与上面描述的情况类似的情况下,所以,你可以选择不处理,让客户端收到 500 错误,然后当重试请求时,他们会看到之前的原因请求失败,或者处理它并确定是否确实由于重复数据而发生了异常。

至于为什么SaveChangesAsync的结果是int,表示受影响的行数。在您的情况下,插入了多少行。

编辑:修改查询以包含要执行 SELECT 1 .... 而不是 SELECT * 的投影

【讨论】:

  • var matches = await context.Where(e =&gt; e.Name == entity.Name).ToListAsync(); 想象一下有一个有 500000 行的表 - 这行代码将在 SQL 服务器上做什么?即使数据库只有一个基于验证的记录 - 它仍然需要检查每一行是否有 e.Name == entity.Name,然后 .ToListAsync() 会将所有这些加载到内存中。
  • @DawoodAwan。我明白您的意思,但是您将如何验证名称是否存在以返回自定义响应?抛出的异常有点通用。不必加载数据,可以为每个匹配的行投影并加载一个 1,如果数据增长这么多,可能会为 Name 编制索引以避免检查每一行。或者你会建议什么?
  • var matches = await context.Where(e =&gt; e.Name == entity.Name).ToListAsync(); 会执行类似SELECT * FROM table WHERE name = 'entity.name' 的操作,并且可以由所有常见的 SQL 服务器轻松快速地处理。特别是如果 name 列被索引...
  • @IvanVargas 最好使用 ANY - 生成的 SQL 是基于 TOP (1) - stackoverflow.com/questions/648795/…
  • @abto 对于列数和行数较少的小型表,它可能会很快 - 但是一旦这个数字上升,您的应用程序的性能就会受到影响
猜你喜欢
  • 1970-01-01
  • 2015-01-03
  • 1970-01-01
  • 1970-01-01
  • 2012-10-21
  • 2018-08-16
  • 2010-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多