与许多技术主题一样,存在基于理想主义、实用主义或无知的广泛意见。只需询问是否可以使用 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 调用中发生了什么。
编辑:
为了此类操作的完整性,有时您会添加新内容,但关联列表存储在应用程序层的集合中,而不是数据库中。那么是否可以调用返回新的PasswordID 的AddPassword 存储过程,然后在调用AddAccess 的Admins 集合的循环中使用它,传入新的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;