代码之家  ›  专栏  ›  技术社区  ›  Crescent Fresh nosklo

如何处理用户界面中的db约束冲突?

  •  12
  • Crescent Fresh nosklo  · 技术社区  · 15 年前

    我们使用存储过程在数据库中实现大部分业务规则。

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

    其他规则,如“每天只允许100个foo”不会导致每次出现错误,因为它们由自定义代码(如 return empty dataset

    这就是问题所在。我们的ui代码如下所示(这是AJAX.NET webservices代码,但任何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
    }
    

    (作为旁注:我注意到当您提交评论太快时stackoverflow会这样做:服务器会生成一个 HTTP 500

    因此,您可以看到,我们在两个地方处理违反业务规则的行为,其中一个(即唯一constaint错误)作为应该处理的代码的特例处理 真实的 onError() 处理程序。

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

    1. 捕获应用程序服务器级别的“重复密钥冲突”异常,并 当“违反了业务规则”标志出现时,它将更改为ui期望的任何内容,
    2. 先发制人 错误(例如,带有 "select name from Foo where name = @Name" )并返回应用服务器期望的“违反业务规则”标志,
    3. 与第2部分相同):利用db层中内置的唯一约束,盲目地 insert into Foo ,捕获任何异常,并将其转换为应用程序服务器期望的“违反业务规则”标志
    4. 盲目地 让应用程序服务器将违反业务规则的情况视为真实情况 Exceptions onError() (或类似)代码。

    它们是在哪里实施的 :在存储的进程中。我不喜欢的是我 它们涉及愚蠢的支票,比如 "if error.IndexOf('duplicate key')" ,就像当前的ui层一样。

    编辑 当前位置我喜欢4),但大多数人都说使用 Exception 只有在 异常 情况。

    那么,你们如何处理将违反业务规则的行为优雅地传播到ui上呢?

    6 回复  |  直到 15 年前
        1
  •  5
  •   Tom H    14 年前

    我们不在数据库中执行业务逻辑,但我们有所有的验证服务器端,低级DB 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
  •  3
  •   Toby Hede    15 年前

    正当

    我认为2和3的组合可能是最好的选择。

    通过预防错误,您可以创建一组过程,这些过程可以从面向UI的代码中调用,以向用户提供详细的特定于实现的反馈。您不一定需要逐个字段地使用ajax来实现这一点,但您可以这样做。

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

        3
  •  2
  •   dkretz    15 年前

    为了防御#4,SQL Server预定义了一个相当有序的错误严重性级别层次结构。正如您所指出的,在逻辑所在的地方处理错误是很好的,因此我倾向于按照SP和UI抽象之间的约定来处理这个问题,而不是添加一些额外的耦合。尤其是因为可以同时使用值和字符串引发错误。

        4
  •  1
  •   Otávio Décio    15 年前

        5
  •  1
  •   John Saunders    15 年前

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

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

    错误参数被替换到生成的错误消息中,类似于 printf

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

        6
  •  1
  •   teedyay    15 年前

    这就是我做事的方式,尽管这对你来说可能不是最好的:

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

    当涉及到验证时,最容易的错误可能会出现在javascript中(格式、字段长度等),当然,您永远不会假设发生了这些错误检查。这些错误也会在更安全、更可控的服务器端代码世界中得到检查。

    因此,我的数据库只会保护自己不受简单的、以数据为中心的规则的影响,而这些规则是它能够很好地处理的;更多变量、面向业务的规则存在于对象的领域,而不是记录的领域。