INSERT INTO ContextView ([Key],[Value])
SELECT 'MyUserID', '1234'
UNION
SELECT 'MyUserName', 'kEk100a'
UPDATE ContextView
SET [Value] = 'kEk1000a'
WHERE [Key] = 'MyUserName'
SELECT * FROM ContextView
DELETE FROM ContextView
WHERE [Key] = 'MyUserName'
SELECT * FROM ContextView
IMPLEMENTATION
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create function [dbo].[getContextFieldDelimiter]()
returns char
WITH SCHEMABINDING
as
begin
return char(2); -- ','
end
GO
create function [dbo].[getContextRowDelimiter]()
returns char
WITH SCHEMABINDING
as
begin
return char(3); -- '|'
end
GO
Next, another UDF to simplify the retrieval and conversion of the raw CONTEXT_INFO data into a string:
GO
create function [dbo].[getContextBuffer]()
returns varchar(128)
WITH SCHEMABINDING
as
begin
return rtrim( replace( convert(varchar(128),CONTEXT_INFO()), char(0), char(32) ));
end
GO
Now to the heart of the matter - a function to parse our doubly-delimited string and return it as a table.
GO
CREATE function [dbo].[getContextTable]()
RETURNS @Context TABLE([Key] varchar(126), [Value] varchar(126))
WITH SCHEMABINDING
as
begin
declare @buffer varchar(128) = dbo.getContextBuffer();
declare @fPtr int = CHARINDEX(dbo.getContextFieldDelimiter(),@buffer);
declare @rPtr int = CHARINDEX(dbo.getContextRowDelimiter(),@buffer);
while @rPtr > 0
begin
insert into @Context
select SUBSTRING(@buffer,1,abs(@fPtr-1)), SUBSTRING(@buffer,@fPtr+1,@rPtr-@fPtr-1)
where @rPtr > nullif(@fPtr,0)+1
select @buffer = SUBSTRING(@buffer,@rPtr+1,128)
, @fPtr = CHARINDEX(dbo.getContextFieldDelimiter(),@buffer)
, @rPtr = CHARINDEX(dbo.getContextRowDelimiter(),@buffer)
end
return;
end
GO
Next, a user-friendly View of this table:
GO
CREATE view [dbo].[ContextView]
WITH SCHEMABINDING
as
select top (32) [Key], [Value]
from dbo.getContextTable()
order by [Key]
GO
And this is where the magic happens - the triggers to format and manipulate the context string. The simplest of the two is the DELETE trigger:
GO
CREATE TRIGGER [dbo].[tContextView_Delete]
ON [dbo].[ContextView]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
declare @buffer varchar(128) = '';
select @buffer += [Key] + dbo.getContextFieldDelimiter() + [Value] + dbo.getContextRowDelimiter()
from ContextView
where [Key] not in (select [Key] from deleted)
declare @varbin varbinary(128) = convert(varbinary(128),@buffer)
SET CONTEXT_INFO @varbin
END
GO
Then the combined insert/update trigger, which is a bit more involved but still rather simple:
GO
CREATE TRIGGER [dbo].[tContextView_Insert]
ON [dbo].[ContextView]
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
declare @buffer varchar(129)=''
select @buffer += [Key] + dbo.getContextFieldDelimiter() + [Value] + dbo.getContextRowDelimiter()
from ContextView
where [Key] not in (select [Key] from inserted where len([Key]) > 0) -- Exclude the values for these keys (if any) since we are about to change them.
select @buffer += ltrim(rtrim([Key])) + dbo.getContextFieldDelimiter() + max(ltrim(rtrim([Value]))) + dbo.getContextRowDelimiter()
from inserted
where LEN(ltrim(rtrim([Key]))) > 0
and LEN(ltrim(rtrim([Value]))) > 0
and LEN(@buffer) < 129
group by [Key] -- just to make sure each Key is unique
if LEN(@buffer) > 128
raiserror('Context buffer overflow',11,1);
declare @varbin varbinary(128) = convert(varbinary(128),@buffer)
SET CONTEXT_INFO @varbin
END TRY
BEGIN CATCH
declare @ErrMsg nvarchar(4000)=isnull(ERROR_MESSAGE(),'Error caught in ContextView'), @ErrSeverity int=ERROR_SEVERITY();
END CATCH
FINALLY:
if @ErrSeverity > 0 raiserror(@ErrMsg, @ErrSeverity, 1);
END
GO
Now we are ready to run the sample queries from the
ARTICLE SUMMARY section (above)!
GO
CREATE function [dbo].[getContextValue](@Key varchar(126))
returns varchar(126)
WITH SCHEMABINDING
as
begin
return (
select Value
from dbo.ContextView
where [Key] = @Key
)
end
GO
The
setter is implemented as a stored procedure because CONTEXT_INFO cannot be set in a UDF.
GO
CREATE procedure [dbo].[setContextValue](@Key varchar(126), @Value varchar(126)=null)
as
BEGIN
BEGIN TRY
select @Key = rtrim(ltrim(@Key)), @Value = rtrim(ltrim(@Value))
if ISNULL(len(@Key),0) = 0
raiserror('Context Key may not by null or empty.',11,1)
declare @buffer varchar(128) = '';
select @buffer += [Key] + dbo.getContextFieldDelimiter() + [Value] + dbo.getContextRowDelimiter()
from ContextView
where [Key] != @Key -- Exclude the current value for this key (if it exists) since we are about to change it.
if LEN(@buffer) + LEN(@Key) + LEN(@Value) > 126
raiserror('Context buffer overflow.',11,1)
if ISNULL(len(@Value),0) > 0
select @buffer += dbo.getContextRowDelimiter() + @Key + dbo.getContextFieldDelimiter() + @Value
declare @varbin varbinary(128) = convert(varbinary(128),@buffer)
SET CONTEXT_INFO @varbin
END TRY
BEGIN CATCH
declare @ErrMsg nvarchar(4000)=isnull(ERROR_MESSAGE(),'Error caught in setContextValue'), @ErrSeverity int=ERROR_SEVERITY();
END CATCH
FINALLY:
if @ErrSeverity > 0 raiserror(@ErrMsg, @ErrSeverity, 1);
return isnull(len(@buffer),0);
END
GO
GO
CREATE TABLE [dbo].[tbl_Student](
[StudentID] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](62) NOT NULL,
[LastName] [varchar](62) NOT NULL,
[BirthDate] [date] NOT NULL,
[GradeLevel] [tinyint] NULL,
CONSTRAINT [PK_tbl_Student] PRIMARY KEY CLUSTERED
(
[StudentID] ASC
)WITH (PAD_INDEX=OFF, STATISTICS_NORECOMPUTE=OFF, IGNORE_DUP_KEY=OFF, ALLOW_ROW_LOCKS=ON, ALLOW_PAGE_LOCKS=ON) ON [PRIMARY]
) ON [PRIMARY]
GO
GO
CREATE procedure [dbo].[usp_SaveStudent](
@StudentID int = null OUTPUT,
@FirstName varchar(62),
@LastName varchar(62),
@BirthDate date,
@GradeLevel tinyint = null
)
as
BEGIN
SET NOCOUNT ON;
declare @rc int = 0;
IF @StudentID IS NULL -- then insert a new student record
BEGIN
INSERT INTO tbl_Student (FirstName,LastName,BirthDate,GradeLevel)
SELECT @FirstName, @LastName, @BirthDate, @GradeLevel
SELECT @rc = @@ROWCOUNT
SELECT @StudentID = SCOPE_IDENTITY() -- get the new StudentID for output
END
ELSE -- update the existing record
BEGIN
UPDATE tbl_Student SET
FirstName = @FirstName,
LastName = @LastName,
BirthDate = @BirthDate,
GradeLevel = isnull(@GradeLevel,GradeLevel)
WHERE StudentID = @StudentID
SELECT @rc = @@ROWCOUNT
END
return @rc;
END -- usp_SaveStudent
GO
Now let's run the procedure to create a record:
declare @StudentID int = null;
exec [usp_SaveStudent] @StudentID=@StudentID OUTPUT, @FirstName='John', @LastName='Doe', @BirthDate='2001-01-01'
select @StudentID as studentid
select * from tbl_Student
GO
create TRIGGER [dbo].[tStudentInsert]
ON [dbo].[tbl_Student]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO tbl_Student (FirstName,LastName,BirthDate,GradeLevel)
SELECT FirstName, LastName, BirthDate, ISNULL(GradeLevel,DATEDIFF(year,BirthDate,GETDATE())-5)
FROM inserted
END
GO
Now let's create another Student record:
declare @StudentID int = null;
exec [usp_SaveStudent] @StudentID=@StudentID OUTPUT, @FirstName='Jane', @LastName='Doe', @BirthDate='2002-02-02'
select @StudentID as studentid
select * from tbl_Student
insert into ContextView
select 'New_Student_ID', SCOPE_IDENTITY()
-- SELECT @StudentID = SCOPE_IDENTITY() -- get the new StudentID for output
SELECT @StudentID = [Value]
FROM ContextView
WHERE [Key] = 'New_Student_ID'
declare @StudentID int = null;
exec [usp_SaveStudent] @StudentID=@StudentID OUTPUT, @FirstName='Joe', @LastName='Doe', @BirthDate='2003-03-03'
select @StudentID as studentid
select * from tbl_Student
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (0)