如何处理用户界面中的数据库约束违规?


12

我们使用存储的特效在数据库中实现了我们的大部分业务规则。

我永远无法决定如何最好地将数据约束违规错误从数据库传递回用户界面。我谈论的约束与数据完整性相关的业务规则更多。

例如,诸如“不能插入重复键行”的数据库错误与业务规则“不能有多个具有相同名称的Foo”相同。但是我们已经在最常见的位置“实现”了它:作为一个唯一的约束,当违反规则时抛出异常。

其他规则(如“每天只允许使用100个Foos”)不会导致错误说明,因为它们可以通过自定义代码(如return empty dataset)进行优雅处理,以便应用程序代码检查并传递回UI层。

而其中的谎言就在于此。我们的UI代码看起来是这样的(这是AJAX.NET web服务的代码,但任何Ajax框架会做):

WebService.AddFoo("foo", onComplete, onError); // ajax call to web service 

function onComplete(newFooId) { 
    if(!newFooId) { 
     alert('You reached your max number of Foos for the day') 
     return 
    } 
    // update ui as normal here 
} 

function onError(e) { 
    if(e.get_message().indexOf('duplicate key')) { 
     alert('A Foo with that name already exists'); 
     return; 
    } 
    // REAL error handling code here 
} 

(作为一个方面说明:我注意到这是当你太快提交评论计算器做什么:服务器生成一个HTTP 500响应,并且ui捕获它。)

所以你看,我们在这里的两个地方处理业务规则违规,其中一个(即唯一的constaint错误)正在作为特殊情况处理该代码应该处理实际的错误(而不是业务规则违规),因为.NET将异常传播到onError()处理程序。

这感觉不对。我的选择,我认为是:

  1. 抓在应用程序服务器级别的“重复键冲突”的异常和转换它不管它是UI预计为标志的“违反业务规则”,
  2. 抢占错误(比如,用"select name from Foo where name = @Name"),并返回不管它是应用程序服务器预计为标志的“违反业务规则”,
  3. 在同一个球场为2):利用内置到数据库层的唯一约束和一味地insert into Foo,捕捉任何异常并将其转换为任何应用程序服务[R预计的“业务规则违反”国旗
  4. 盲目insert into Foo(如3),并让该异常传播到用户界面,有应用服务器加薪违反业务为真正的Exceptions(而不是1)。这样,所有的错误都在UI层onError()(或类似的)代码中处理。

我喜欢什么约2)和3)是业务规则违反的“抛出” 他们实现:在存储过程。我不喜欢的关于1)和3)是我认为它们涉及像"if error.IndexOf('duplicate key')"这样的愚蠢检查,就像当前ui层中的一样。

编辑:我喜欢4),但大多数人说,只有在特殊情况使用Exception秒。

那么,你们如何优雅地处理宣传违反商业规则的行为呢?

1

存储过程可能使用RAISERROR语句将错误信息返回给调用者。这可以以允许用户界面决定如何显示错误的方式使用,同时允许存储过程提供错误的详细信息。

可以使用msg_id,严重性和状态以及一组错误参数来调用RAISERROR。以这种方式使用时,必须使用sp_addmessage系统存储过程将具有给定的msg_id的消息输入到数据库中。这个msg_id可以作为调用存储过程的.NET代码中引发的SqlException中的ErrorNumber属性来检索。用户界面然后可以决定要显示什么类型的消息或其他指示。

将错误参数替换为生成的错误消息,类似于C语言中的语句如何工作。但是,如果您只想将参数传递回UI,以便UI可以决定如何使用它们,只需使错误消息没有文本,只是参数的占位符。一条消息可能是'“%s”|%d'来传回字符串参数(用引号引起来)和一个数字参数。 .NET代码可以拆分它们,然后在用户界面中使用它们。

RAISERROR也可用于存储过程中的TRY CATCH块。这将允许您捕获重复键错误,并将其替换为您自己的错误号,这意味着“插入时重复的键”到您的代码中,并且可以包含实际的键值。您的用户界面可以使用它来显示“订单号已存在”,其中“x”是提供的关键值。

+1

,最终调用'RAISERROR'将仍然需要执行一个“愚蠢的检查”像'CHARINDEX('重复键,@errorMessage)> 0',我发现这个问题,因为我的示例场景涉及*两个*可能的唯一密钥码违反约束条件,我很好奇是否有某种方法可以确定哪些键已被违反,而不搜索键的名称或相关列的名称。 27 7月. 112011-07-27 19:07:06


1

我见过很多的基于Ajax的应用程序做的字段,如用户名实时检查(看它是否已经存在),一旦用户离开编辑框。在我看来,一种比留在数据库中以基于db约束引发异常更好的方法 - 它更主动,因为你有一个真正的过程:获取值,检查它是否有效,如果不是,则显示错误,如果没有错误,允许继续。所以看起来选项2是一个很好的选择。

  0

这对用户体验来说很不错,但是有一个很大的竞争条件,那就是用户界面仍然需要处理数据提交。 22 2月. 092009-02-22 03:38:27

  0

我们通过在提交时进行完整的服务器端验证来解决竞争条件。每场验证对用户来说只是一个方便 22 2月. 092009-02-22 15:53:12

  0

@Mark:当然。我只是想知道当违规*发生时,人们如何处理ui中的“完整服务器端验证”(假设ajax提交数据) - 您在其他答案中回答了问题,谢谢。 22 2月. 092009-02-22 16:42:06


3

该问题实际上是您的系统体系结构中的一个限制。通过将所有逻辑推入数据库,您需要在两个地方处理它(而不是建立一个将UI与数据库链接起来的业务逻辑层)。再次,当您拥有一层业务逻辑时,您将失去所有在存储特效有逻辑的好处。不主张一方或另一方。两个吸大约相等,或者不吸,取决于你如何看待它。

我在哪儿? 权。

我认为2和3的组合可能是要走的路线

通过预占错误,您可以创建一组可以从面向UI的代码中调用的过程来提供详细的实现针对用户的特定反馈。你不一定需要在字段的基础上用ajax来做这件事,但你可以。

数据库中的唯一约束和其他规则将成为最终的所有数据的完整性检查,并且可以假设数据在发送前是好的,并且抛出异常是理所当然的(前提是应始终使用有效数据调用这些过程,因此无效数据是例外情况)。

  0

这实际上是2和4的组合,调整到4,而不是在应用程序服务器上引发错误,而是在存储过程中引发错误。所以存储过程会抛出所有违反业务规则的异常(有些是自己发现的,有些是为它捕获的)。我喜欢。 22 2月. 092009-02-22 13:13:52

  0

你是对的... 23 2月. 092009-02-23 01:47:53


2

为了防御#4,SQL Server具有预定义的错误严重级别的相当有序的层次结构。既然你指出在逻辑处理错误的时候很好,我倾向于按照惯例在SP和UI抽象之间处理这个问题,而不是增加一些额外的耦合。特别是因为你可以用一个值和一个字符串来引发错误。


5

我们不在数据库中执行业务逻辑,但是我们确实拥有所有的验证服务器端,将低级别的数据库CRUD操作与高级业务逻辑和控制器代码分开。

我们试图在内部做的是传递一个验证对象,其功能类似于Validation.addError(message,[fieldname])。的各种应用层附加该对象在其验证结果,然后我们称之为Validation.toJson()以产生结果,看起来像这样:

{ 
    success:false, 
    general_message:"You have reached your max number of Foos for the day", 
    errors:{ 
     last_name:"This field is required", 
     mrn:"Either SSN or MRN must be entered", 
     zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible" 
    } 
} 

这可以很容易地处理的客户端侧显示与个别字段以及一般消息消息。

关于约束违规,我们使用#2,即我们在插入/更新之前检查潜在违规并将错误追加到验证对象。

  0

我注意到这是SO为像投票这样的写操作所做的事情。答案有“成功”和“消息”字段。但是,如果你提交的评论太快,他们会抛出一个500服务器异常,并且ui会捕获它(即我的代码示例所做的工作,我不喜欢)。 22 2月. 092009-02-22 16:32:47

  0

虽然我确实喜欢这种方法,但即使使用db中处理的业务规则检查,也不可能做到这一点。应用程序服务器只需将所有响应转换为ui可以处理的标准。无论如何,这不是一件坏事。 22 2月. 092009-02-22 16:38:28


1

这是我做的事情,虽然它可能不是最适合你:

我一般去的先发制人的模型,虽然它在很大程度上取决于你的应用程序体系结构。

对我来说(在我的环境中)检查中间(业务对象)层中的大多数错误是有意义的。这是所有其他业务特定逻辑发生的地方,所以我尽量保留其余的逻辑。我认为数据库是坚持我的对象的地方。

当谈到验证时,最简单的错误可能会被困在JavaScript(格式化,字段长度等)中,尽管您从不假定这些错误检查发生了。这些错误也会在更安全,更受控制的服务器端代码世界中检查。

业务规则(例如“您每天只能拥有如此多的foos”)会在业务对象层的服务器端代码中进行检查。

只有数据规则在数据库中被检查(参照完整性,唯一字段约束等)。我们也先在中间层检查所有这些,以避免不必要地触碰数据库。因此,我的数据库只能保护自己免受简单的,以数据为中心的规则,它可以很好地处理;更多变量的,面向商业的规则生活在物体的土地上,而不是记录的土地上。

  0

不错。奇怪的是:你对中间层检查通过的竞争条件做了什么,但是在数据层失败了(因为另一个线程击败了它)? 22 2月. 092009-02-22 23:30:30

  0

是的,这完全是这种方法的弱点。在我特殊的环境中,这是一个罕见的情况,我们不需要担心。这会导致一个相当丑陋的例外,这是真的,但数据库将保持不变。 23 2月. 092009-02-23 06:16:09