6

I am using SQL Server 2008 to store the sessions for my .NET4 application. The sessions will be stored in [DatabaseA]. There isn't any other custom configuration for this, so the ASPState database is exactly how it would come out of the box (using aspnet_regsql)

My main application runs on [DatabaseB] (same server). Within this database I have a 2 tables that record a some data along with the sessionID.

When the [DeleteExpiredSessions] stored procedure (on DatabaseA) is run via SQL Agent, the sessions are correctly removed from the ASPState, but I want to extend this to delete the rows based on the SessionID from [DatabaseB]

I have tried editing the [DeleteExpiredSessions] stored procedure to include the following SQL

    OPEN ExpiredSessionCursor

    FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID

    WHILE @@FETCH_STATUS = 0 
        BEGIN
            -- BEGIN MY ADDITIONS
            DECLARE @myRecordCount int
            SELECT @myRecordCount= COUNT(*) FROM [DatabaseB].dbo.Table1 WHERE [DatabaseB].dbo.Table1.SessionId = @SessionID -- AND [DatabaseB].dbo.Table1.DateEntered < @now
            SELECT @myRecordCount 
            DELETE FROM [DatabaseB].dbo.Table1 WHERE [DatabaseB].dbo.Table1.SessionId = @SessionID AND [DatabaseB].dbo.Table1.DateEntered < @now
            DELETE FROM [DatabaseB].dbo.Table2 WHERE [DatabaseB].dbo.Table2.SessionId = @SessionID AND [DatabaseB].dbo.Table2.DateEntered < @now
            -- END MY ADDITIONS

            DELETE FROM [DatabaseA].dbo.ASPStateTempSessions WHERE SessionID = @SessionID AND Expires < @now
            FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID
        END

    CLOSE ExpiredSessionCursor

    DEALLOCATE ExpiredSessionCursor

But @myRecordCount is returning 0 rows. There are no errors reported (the agent job runs correctly, and nothing in SQL Profiler), and @myRecordCount should be returning 4 in this instance.

The DECLARE/SELECT COUNT is there as a debugger.

UPDATE

so having debugged the sql and found that :

SELECT SessionId
        FROM [ASPState].dbo.ASPStateTempSessions WITH (READUNCOMMITTED)
        WHERE Expires < GETUTCDATE() 
--Returns SessionId = '3wj5nyrlz02ezw1vvjts4gjv28d8c075'

SELECT * FROM [DatabaseB].dbo.Table1 -- returns (4) results
SELECT * FROM [DatabaseB].dbo.Table1 WHERE [DatabaseB].dbo.Table1.SessionId = '3wj5nyrlz02ezw1vvjts4gjv28d8c075' -- returns (0) results

I have deduced that the SessionId is wrong. What is being stored in [DatabaseB].dbo.Table1 is '3wj5nyrlz02ezw1vvjts4gjv' - notice the truncation of the string.

Now my .NET code (using EF6.1.3) for storing the session variables are Table1.SessionId = HttpContext.Current.Session.SessionID this means that 3wj5nyrlz02ezw1vvjts4gjv is being stored. The Cookie ASP.NET_SessionId also has the same value.

The length of the SessionId column of Table1 is the same as the ASPState tmpSession table (88)

Updated Question Interestingly the ASPStateTempSessions.SessionId variable seems to be appending
28d8c075 to the end of it. So ASP.NET_SessionId and HttpContext.Current.Session.SessionID are 3wj5nyrlz02ezw1vvjts4gjv but ASPStateTempSessions.SessionId is 3wj5nyrlz02ezw1vvjts4gjv28d8c075

I have just cleared all session variables and cookies and I have a new session variable, but the 28d8c075 is still being removed/appended to the various cookies and data values.

I understand this is happening because the ASPStateTempSessions is appending a suffix of the application hash to the SessionId (https://msdn.microsoft.com/en-us/library/aa478952.aspx)

Column Name Column Type Description
SessionId   nvarchar(88)    Session ID + application ID

How do I return this to HttpContext.Current.Session.SessionID instead of just the SessionId?

leppie
  • 115,091
  • 17
  • 196
  • 297
kolin
  • 2,326
  • 1
  • 28
  • 46
  • What is ***28d8c075*** value ? Where I get that value ? I view in _ASPStateTempApplications.AppId_ but not same value that ***28d8c075*** – Kiquenet Oct 24 '16 at 10:11
  • @Kiquenet that value is particular to the webapplication. it will be different for each machine/website. – kolin Oct 24 '16 at 10:18
  • My **ASPStateTempApplications.AppId** are like *1871749830*, but in **ASPStateTempSessions.SessionId** value is like *mlzhyr3kop2wneubyyagczhm9fa38ee5*. IMHO, `AppId` can be in **HEX** in `SessionId` field? – Kiquenet Oct 24 '16 at 10:26
  • 1
    http://stackoverflow.com/a/40216799/206730 You can use `SUBSTRING(a.SessionId, 25, 8) AS AppIDHex` and convert AppId to HEX `SUBSTRING(sys.fn_varbintohexstr(CONVERT(VarBinary,b.AppId)), 3, 8)` – Kiquenet Oct 24 '16 at 11:06

3 Answers3

3

Solved

The issue (as I originally diagnosed) was that the application ID wasn't being passed to the SessionID variable. So I did the following steps:

1) Created a fallback variable within Web.Config (it is very unlikely that the AppName is going to change)

<add key="AppId" value="685293685"/>

Why? - this is the id returned from the ASPState [TempGetAppID] for the appName

2) Created a stored procedure [GetApplicationIdForSession] within DatabaseB

DECLARE @appId int

    EXEC    [ASPState].dbo.[TempGetAppID]
        @appName = @iisName,
        @appId = @appId OUTPUT

    SELECT  @appId as N'AppId'

Why? - This utilises the built in SQL State SP that hashes the AppName to the correct AppId to maintain consistency. (i.e. i'm not writing a separate .net function to try and get the same value as SQL)

3) Within Global.asax.vb

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        Using db As New SqlConnection(ConfigurationManager.ConnectionStrings("LocalServer").ConnectionString)
            db.Open()
            Using cmd As SqlCommand = db.CreateCommand()
                cmd.CommandText = "GetApplicationIdForSession"
                cmd.CommandType = CommandType.StoredProcedure
                cmd.Parameters.AddWithValue("iisName", HttpRuntime.AppDomainAppId.ToLower)
                Using dr As SqlDataReader = cmd.ExecuteReader()
                    If dr IsNot Nothing AndAlso dr.Read() Then
                        Application("AppId") = CType(dr("appId"), Integer)
                    Else
                        Application("AppId") = CType(ConfigurationManager.AppSettings("AppId"), Integer)
                    End If
                End Using
            End Using
            db.Close()
        End Using
        ' Fires when the application is started
    End Sub

Why? - The HttpRuntime.AppDomainAppId is the same value that is used within the ASPState Database (DatabaseA). so I pass that into the stored procedure created in step 2 and then store the output in an application variable so I don't have to hit the database everytime to get the sessionid.

4) Created this function in the base class that my pages inherit from

Public Function GetAppIdHash() As String

        Dim hashCode As Integer = CType(Application("AppId"), Integer)
        Return ((hashCode).ToString("X2")).ToLower

    End Function

Why? - this takes the application variable ("AppId") and returns the Hex output

5) Altered the .net code that stores the Session id variable from: Table1.SessionId = HttpContext.Current.Session.SessionID to Table1.SessionId = HttpContext.Current.Session.SessionID & GetAppIdHash()

6) the DeleteExpiredSessions SP is updated to the -happy medium- version listed here - http://sqlperformance.com/2013/01/t-sql-queries/optimize-aspstate and the additional SQL commands are appended to it.

ALTER PROCEDURE [dbo].[DeleteExpiredSessions]
  @top INT = 1000
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @now DATETIME, @c INT;
  SELECT @now = GETUTCDATE(), @c = 1;

 /* START Additional SQL */
    CREATE TABLE #tblExpiredSessions 
    ( 
        SessionId nvarchar(88) NOT NULL PRIMARY KEY
    )
    INSERT #tblExpiredSessions (SessionId)
        SELECT SessionId
        FROM [ASPState].dbo.ASPStateTempSessions WITH (READUNCOMMITTED)
        WHERE Expires < @now
    /* END Additional SQL */

  BEGIN TRANSACTION;

  WHILE @c <> 0
  BEGIN
    ;WITH x AS 
    (
      SELECT TOP (@top) SessionId
        FROM [ASPState].dbo.ASPStateTempSessions
        WHERE Expires < @now
        ORDER BY SessionId
    )
    DELETE x;

    SET @c = @@ROWCOUNT;

    IF @@TRANCOUNT = 1
    BEGIN
      COMMIT TRANSACTION;
      BEGIN TRANSACTION;
    END
  END

  IF @@TRANCOUNT = 1
  BEGIN
    COMMIT TRANSACTION;
  END

  /* START Additional SQL */
  DELETE FROM DatabaseB.dbo.Table1 WHERE DatabaseB.dbo.Table2.SessionId in (SELECT * FROM #tblExpiredSessions)
  DELETE FROM DatabaseB.dbo.Table2 WHERE DatabaseB.dbo.Table2.SessionId in (SELECT * FROM #tblExpiredSessions)

  DROP TABLE #tblExpiredSessions
  /* END Additional SQL */
END

Sidenote - I'm not an SQL wizard so if anyone could suggest improvements to the DELETE FROM DatabaseB.dbo.Table1 etc part, that would be appreciated. (such as location within the query, can it work with the CTE), it currently seems kind of clunky to me.

kolin
  • 2,326
  • 1
  • 28
  • 46
  • _Why not use_ `SET @now = GETUTCDATE()` and `DELETE [NSI_PROVIDERS].dbo.ASPStateTempSessions WHERE Expires < @now` ? ***not high performance?*** – Kiquenet Oct 27 '16 at 12:56
0

My experience of having multiple session state databases are that SessionID is unique between applications. We've had to customise the code to get them to share one session state database and change the behaviour based on application name.

  • I only have one session state database. Data that is stored in DatabaseB only has the sessionID stored to be used as a key for removal when sessions are expired. – kolin Feb 11 '16 at 12:17
0

why you need cusrors ,assuming you have session id common between two tables,you can try below

delete t1
 from database1.table1 t1
join
database2.table2 t2
on t1.sessionid=t2.sessionid
TheGameiswar
  • 27,855
  • 8
  • 56
  • 94
  • 1) why do you need cursors? - As I mentioned the stored procedure is "out of the box", the cursors were part of it. 2) If my initial Select statement is not bringing back any rows then a join is not going to help? – kolin Feb 11 '16 at 12:43