使用持久表注入连接上下文信息

Ale*_*xei 4 sql-server connections

我正在开发一个应用程序,该应用程序具有多个严重依赖存储过程的遗留模块(没有 ORM,因此所有获取和数据持久性都是通过存储过程完成的)。

遗留模块的安全性依赖于SUSER_NAME()获取当前用户并应用安全规则。

我将其迁移为使用 ORM(实体框架),并且 SQL 连接器将使用通用用户连接到数据库(SQL Server),因此我必须向许多过程提供当前用户名。

为了避免 .NET 代码发生更改,我想到在建立新连接时以某种方式在上下文中“注入”当前用户:

CREATE TABLE dbo.ConnectionContextInfo 
(
    ConnectionContextInfoId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_ConnectionContextInfo PRIMARY KEY,
    Created DATETIME2 NOT NULL CONSTRAINT DF_ConnectionContextInfo DEFAULT(GETDATE()),
    SPID INT NOT NULL,
    AttributeName VARCHAR(32) NOT NULL, 
    AttributeValue VARCHAR(250) NULL,
    CONSTRAINT UQ_ConnectionContextInfo_Info UNIQUE(SPID, AttributeName)
)
GO
Run Code Online (Sandbox Code Playgroud)

当连接打开(或重用,因为使用连接池)时,将使用以下命令:

exec sp_executesql N'
    DELETE FROM dbo.ConnectionContextInfo WHERE SPID = @@SPID AND AttributeName = @UsernameAttribute;
    INSERT INTO dbo.ConnectionContextInfo (SPID, AttributeName, AttributeValue) VALUES (@@SPID, @UsernameAttribute, @Username);
',N'@UsernameAttribute nvarchar(8),@Username nvarchar(16)',@UsernameAttribute=N'Username',@Username=N'domain\username'
go
Run Code Online (Sandbox Code Playgroud)

(0 CPU,~15 次读取,<6 毫秒)

标量函数可以轻松获取当前用户:

alter FUNCTION dbo.getCurrentUser()
RETURNS VARCHAR(250)
AS
BEGIN
    DECLARE @ret VARCHAR(250) = (SELECT AttributeValue FROM ConnectionContextInfo where SPID = @@SPID AND AttributeName = 'Username')
    -- fallback to session current, if no data is found on current SPID (i.e. call outside of the actual application)
    RETURN ISNULL(@ret, SUSER_NAME())
END
GO
Run Code Online (Sandbox Code Playgroud)

从数据层的角度来看,这种方法是否有任何注意事项(稳健性、性能等)?

谢谢。

Dan*_*man 5

在性能方面,每次打开连接时都会产生DELETE和的开销。INSERT或者,您可以使用内置连接 CONTEXT_INFO 来实现此目的。下面的示例将信息存储在固定长度的 48 字节结构中。

EXEC sp_executesql N'
    DECLARE @ContextInfo binary(48);
    SET @ContextInfo = CAST(CAST(@UsernameAttribute AS nchar(8)) + CAST(@Username AS nchar(16)) AS binary(48));
',N'@UsernameAttribute nvarchar(8),@Username nvarchar(16)',@UsernameAttribute=N'Username',@Username=N'domain\username'
GO


CREATE FUNCTION dbo.getCurrentUser()
RETURNS VARCHAR(250)
AS
BEGIN
DECLARE
      @ContextInfo binary(48) = CONTEXT_INFO()
    , @Username nvarchar(16);
    SET @Username = RTRIM(CAST(SUBSTRING(@ContextInfo, 17, 32) AS nvarchar(16)));

    RETURN ISNULL(@Username, SUSER_NAME());

END
GO
Run Code Online (Sandbox Code Playgroud)

此外,sp_set_session_contextSESSION_CONTEXT()在 SQL Server 2016 和 Azure SQL 数据库中可用。如果您可以的话,这是一种更干净的方法。