以正确的方式使用存储过程

时间:2014-11-08 16:37:04

标签: sql-server stored-procedures

我已经使用Entity Framework编写了一个Web应用程序来对数据库执行CRUD操作。

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

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

此外 - 我的网络应用程序是一个密码共享系统,允许管理员为个人用户提供访问个人密码的权限。因此,当创建密码时,存储过程会将密码写入数据库,管理员需要自动获得密码访问权限...因此,如果Add_Password SP包含某些内容,那么采用“保持业务逻辑不在SP”的方法用于为管理员创建相关记录以获取访问权限的逻辑,还是应该通过调用Add_Access SP的循环来完成?

2 个答案:

答案 0 :(得分:2)

与许多技术主题一样,基于理想主义,实用主义或无知的观点存在广泛的观点。只要问一下使用CURSOR是否可以,你会很快看到:)。

我觉得" No Business Logic"口头禅主要是理想主义,因为似乎相对较少的人花时间去辨别究竟是什么构成了实际的" Business Logic"与我所称的"数据逻辑"分开。数据逻辑正在尽最大努力使用RDMBS来有效地操纵数据。

我采取更务实的方法,看看实际做了什么,以及在哪里进行这项操作最有效率。在你通过循环在proc或app层中分配相关记录的特定情况下,我认为在app层中做这样的事情是愚蠢的,因为你没有获得1行注释无法获得的信息。已经完成但是你做了重复的数据库调用会产生巨大的成本。当然,可以通过将循环调用包装到事务中并使用相同的开放连接来减少这些成本,但这将永远不会像存储过程中的简单基于集合的语句那样高效(即扩展)。

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

例如,您的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();

在应用层中执行此操作意味着获取所有"管理员" UserID并为每个人调用AddAccess?这似乎是错综复杂的,也是效率低下的。

一般来说,尽量坚持做数据逻辑" (例如上面的例子)在数据库中,但是并没有陷入理想的过程中,而在数据库中没有业务逻辑。如果存在一些真正受益于将其中的一部分放入数据库中的情况,那么您的项目会受到影响。请务必在应用程序代码中添加评论(有用的评论,其中包含足够的信息/详细信息,以传达实际的想法,而不是一个谜),以确定正在进行的数据库调用中发生了什么。

修改
为了完成此类操作的完整性,有时您要添加新内容,但关联列表存储在应用层而不是数据库中的集合中。然后可以调用返回新AddPassword的{​​{1}}存储过程,然后在调用PasswordID Admins集合的AddAccess集合中循环使用它PasswordIDUserID一次?答案仍然是"没有"。在这种情况下,您只需循环遍历Admins集合,以格式化CSV字符串或基于属性的XML文档以传递到存储过程。如果从集合中提取的ID列表将会很大(即可能超过100k项),则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;

答案 1 :(得分:0)

我相信一个理想的世界,你会希望你的大部分业务逻辑都采用某种可配置的形式。 SP本身不能配置给最终用户,但可以调用包含配置信息的对象。有时基于要求,在SP中使用业务逻辑是有意义的,并且没有任何法律可以通过这样做来打破。

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

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