修改
我刚刚意识到SaveChangesAsync返回0并不意味着它失败了,实体框架总会在出现故障时抛出异常,所以检查SaveChanges == 0是多余的!保存更改应始终在下面的示例中返回1,如果某些内容失败,则会抛出异常。
然而,有些情况下使用其他东西并且它不是实体框架,所以这个问题就是这样。
服务器可能会失败,当我将所有数据访问代码放入控制器时,我可以这样处理它:
[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
await _dbContext.AddAsync(item);
if (await _dbContext.SaveChangesAsync() == 0)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}
当我的数据访问封装在服务层中时,我该如何处理?
public class ItemsService
{
public async Task<Item> CreateAsync(Item item)
{
await _dbContext.AddAsync(item);
if (await _dbContext.SaveChangesAsync() == 0)
{
return null;
}
return item;
}
}
然后会像那样使用:
[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
// model state validation skipped for the sake of simplicity,
// that would return BadRequest or some more valuable information
var item = await _itemsService.CreateAsync(item);
if (item == null)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}
也许这适用于精细创建,因为只有2个状态代码,但让我们考虑更新可能存在两个以上可能错误的位置:
没有服务的代码:
[HttpPut("{id}")]
public async Task<ActionResult<Item>> UpdateAsync(int id, [FromBody] Item itemToUpdate)
{
var item = await _dbContext.Items.FindAsync(id);
if (item == null)
{
return NotFound();
}
// update item with itemToUpdate
//...
await _dbContext.Update(item);
if (await _dbContext.SaveChangesAsync() == 0)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
return item;
}
现在有了服务,这无法妥善处理:
public class ItemsService
{
public async Task<Item> UpdateAsync(Item updateItem)
{
var item = await _dbContext.Items.FindAsync(id);
if (item == null)
{
return null;
}
//change some properties and update
//...
_dbContext.Items.Update(item);
if (await _dbContext.SaveChangesAsync() == 0)
{
// what now?
}
return item;
}
}
因为它总是返回null,并且无法判断是否找不到该项或保存失败。
我该如何妥善处理?
注意:我没有添加DTO或类似的东西来保持这个例子的简单。
答案 0 :(得分:1)
您的服务负责捕获它知道如何处理和处理这些异常的所有异常。所有其他例外情况都应通过提供IServiceResult<T>
和bool IsSuccessful
的某种AggregatedException Exceptions { get; }
进行报告。所以,而不是扔你让最高级别来决定如何反应。从许多角度来看,它实际上是最好的方法,包括:功能的纯度(这是简单并行的关键概念,对服务器来说不重要吗?),清洁和可维护的代码(消费者的服务不应该知道/关心那么多:它们只是验证结果是否成功并消耗结果;否则挖掘聚合异常;可能有意义实现自己的异常类型,提供适合您项目需求的方法),能力在高阶函数上进行,这些函数负责实际的错误处理;以rX
作为一个受欢迎的例子:.OnError(Action<Exception> handler)
。除此之外还有很多,但是我不想让答案更长。
作为旁注。请务必阅读Maybe
编程语言Either
和Haskell
monad的介绍性杠杆文章;如果您更喜欢F#
,则可以尝试分别寻找Some
和Either
。在那里,您可以找到有用的方法,如.Map(...)
,并查看以有效方式组合/汇总这些方法的好方法。
答案 1 :(得分:1)
使用该服务的代码要好得多。控制器可以将业务逻辑委托给服务层。
为了更好地实现服务层,您应该:
ItemNotFoundExcption
而不是返回null
。更新失败时ItemUpdateException
。NotFound()
返回ItemNotFoundException
,为BadRequest()
返回ItemUpdateException
。 答案 2 :(得分:0)
它实际上是一个非常大的主题,并且有很多选项可以做到这一点。
我更喜欢抛出异常。您可以为此创建自己的类或使用.net的类。您可以拥有NotFoundException,InternalServerError等。每个类都可以有StatusCode和Message字段或任何您想要的。接下来,您需要为asp.net实现异常过滤器,它将处理所有异常并返回响应,其状态代码可以从异常类中检索。
另一种选择可能更简单。您可以创建一个包含StatusCode和object的类,这样您就可以从方法中返回它。它是一种从方法返回更多然后只是null或object的方法。
很少有好文章: Exceptions or error codes
https://www.hanselman.com/blog/GoodExceptionManagementRulesOfThumb.aspx
http://codebetter.com/karlseguin/2006/04/05/understanding-and-using-exceptions/