【问题标题】:Using Stored Procedures the right way正确使用存储过程
【发布时间】:2014-11-08 16:37:03
【问题描述】:

我使用实体框架编写了一个 Web 应用程序来对数据库执行 CRUD 操作。

我想更改此设置,以便将存储过程用于 CUD 操作。尽管我在其他项目中广泛使用了存储过程;在阅读之后,我可以看到有一种思想流派,其中存储过程不应该包​​含任何业务逻辑(我之前一直在这样做)。

在使用存储过程构建应用程序时,有没有人采用过这种方法?这种方法有效吗?假设这种方法是正确的,那么我假设存储过程应该只做基本的 CRUD 操作?

另外 - 我的 Web 应用程序是一个密码共享系统,它允许管理员授予个人用户访问个人密码的权限。因此,当创建密码时,存储过程会将密码写入数据库,管理员需要自动获得对密码的访问权限......因此,如果 Add_Password SP 包含一些创建相关记录以供管理员访问的逻辑,还是应该通过调用 Add_Access SP 的循环来完成?

【问题讨论】:

    标签: sql-server stored-procedures


    【解决方案1】:

    与许多技术主题一样,存在基于理想主义、实用主义或无知的广泛意见。只需询问是否可以使用 CURSOR,您就会很快看到 :)。

    我觉得“没有业务逻辑”的口头禅主要是理想主义,因为似乎相对较少的人花时间去辨别真正构成实际“业务逻辑”的内容,而不是我所谓的“数据逻辑”。 Data Logic 正在尽其所能使用 RDMBS 来有效地操作数据。

    我采取更务实的方法,看看实际在做什么,以及在哪里执行该操作最有效。在您通过循环在proc或应用程序层中分配相关记录的特定情况下,我认为在应用程序层中做这样的事情是愚蠢的,因为您没有获得单行注释无法完成的任何事情但是你确实会因为重复的数据库调用而产生巨大的成本。当然,可以通过将循环调用包装到事务中并使用相同的开放连接来降低这些成本,但这永远不会像存储过程中简单的基于集合的语句那样高效(即规模也一样)。

    不要误会我的意思,我绝对同意算法应该在应用层,但是一旦你知道应该持久化哪些数据,就应该将其交给数据库来完成工作。

    例如,您的Add_Password proc 可能类似于:

    CREATE PROCEDURE dbo.Add_Password
    (
      @UserID       INT,
      @Password     NVARCHAR(50),
      @PasswordID   INT OUTPUT = -1
    )
    AS
    SET NOCOUNT ON;
    
    BEGIN TRY
    
       BEGIN TRAN;
    
       INSERT INTO dbo.Passwords (UserID, [Password])
       VALUES (@UserID, @Password);
    
       SET @PasswordID = SCOPE_IDENTITY();
    
       INSERT INTO dbo.PasswordAccess (UserID, PasswordID)
          SELECT  usr.UserID, @PasswordID
          FROM    dbo.Users usr
          WHERE   usr.UserType = 1; -- Assuming 1 = Admin
    
       COMMIT TRAN;
    
    END TRY
    BEGIN CATCH
       ROLLBACK TRAN;
    
       THROW;
    END CATCH;
    

    在应用程序代码中,只需:

    // Insert new password and assign access to all Admin-level Users (i.e. UserType = 1)
    SqlCommand.ExecuteNonQuery();
    

    在应用层中完成这一切意味着获取所有“管理员”用户 ID 并为每个用户 ID 调用 AddAccess?这看起来既复杂又低效。

    一般来说,尽量坚持只在数据库中做“数据逻辑”(例如上面的例子),但是不要陷入“没有业务逻辑”的理想中如果存在真正受益于将其中一些放入数据库的情况,那么您让您的项目遭受损失。只需确保将 cmets(具有足够信息/细节的有用 cmets 以传达实际想法而不是神秘信息)放在应用程序代码中,以了解正在进行的 DB 调用中发生了什么。

    编辑:
    为了此类操作的完整性,有时您会添加新内容,但关联列表存储在应用程序层的集合中,而不是数据库中。那么是否可以调用返回新的PasswordIDAddPassword 存储过程,然后在调用AddAccessAdmins 集合的循环中使用它,传入新的PasswordID 和一个UserID一次?答案仍然是“不”。在这种情况下,您将循环遍历 Admins 集合,仅格式化 CSV 字符串或基于属性的 XML 文档以传递到存储过程。如果从集合中提取的 ID 列表很大(即可能超过 10 万个项目),则 TVP(表值参数)可能更有效,但对于小列表,TVP 可以 过度设计(显然为每种情况做最好的事情)。因此,上面的示例 proc 会稍微改变为:

    CREATE PROCEDURE dbo.Add_Password
    (
      @UserID       INT,
      @Password     NVARCHAR(50),
      @AdminIDs     VARCHAR(MAX), -- CSV list of UserIDs that are Admin / UserType = 1
      @PasswordID   INT OUTPUT = -1
    )
    AS
    SET NOCOUNT ON;
    
    BEGIN TRY
    
       BEGIN TRAN;
    
       INSERT INTO dbo.Passwords (UserID, [Password])
       VALUES (@UserID, @Password);
    
       SET @PasswordID = SCOPE_IDENTITY();
    
       INSERT INTO dbo.PasswordAccess (UserID, PasswordID)
          SELECT  split.[Value], @PasswordID
          FROM    dbo.SqlClrOrXmlStringSplitter(@AdminIDs, ',') split;
    
       COMMIT TRAN;
    
    END TRY
    BEGIN CATCH
       ROLLBACK TRAN;
    
       THROW;
    END CATCH;
    

    【讨论】:

    • @binks : 随时 :)。我只是稍微更新了 SQL,因为它确实应该有 TRY / CATCH 和事务处理。此外,添加了一个编辑部分,希望能画出更清晰的画面。点赞? :D
    • +1 以获得如此详细的答案,@binks 当有人花这么多时间为你解决问题时,这个人值得一票,它鼓励人们提供高质量的答案。
    • @M.Ali 抱歉,我只是假设我需要点击“已回答”的勾号。正式注明。
    • @srutzky 谢谢,这有助于“编辑权限”位:)
    【解决方案2】:

    我相信在理想的世界中,您会希望您的大部分业务逻辑都采用某种可配置的形式。 SP 本身不可配置给最终用户,但可以调用包含配置信息的对象。有时基于需求,将业务逻辑放在 SP 中是有意义的,而且这样做不会违反任何法律。

    如果硬性规定是管理员始终需要查看密码,则可能有其他方法可以绕过它,而不是授予对密码的访问权限。访问可能始终隐含于用户类型,因此无需调用添加访问。如果对管理员的访问是选择性的,那么您也可以在表格中围绕它构建配置信息。

    如果这是对现有系统的改造,那么确保您在存储过程中添加访问权限的方法会很好,但我确实喜欢打破该功能以使其更加模块化的想法。调用另一个 SP 可以正常工作,或者可能会根据系统的具体情况从触发器更新。

    【讨论】:

      猜你喜欢
      • 2016-07-07
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 2019-05-29
      • 1970-01-01
      • 1970-01-01
      • 2014-02-16
      • 1970-01-01
      相关资源
      最近更新 更多