Imports Microsoft.VisualBasic
Imports System.Configuration

Public Class XrCache
    ''' <summary>
    ''' 缓存依赖类型:Absolute绝对过期 ；Relative相对过期；File文件名
    ''' </summary>
    ''' 
    Public Enum CacheLifeType
        Absolute = 1 '绝对
        Relative = 2 '相对
        File = 3
        None = 0
    End Enum

    ''' <summary>
    ''' 计算缓存优先级
    ''' </summary>
    ''' <param name="KeyName">缓存名称</param>
    ''' <returns>优先级</returns>
    ''' <remarks></remarks>

    Public Shared Function ComputePriority(ByVal KeyName As String) As Web.Caching.CacheItemPriority
        Dim mObj As Object = System.Web.HttpRuntime.Cache.Get("CacheDict")
        Dim mDictionaries As Dictionary(Of String, clsCache)

        If Not IsNothing(mObj) Then
            mDictionaries = CType(mObj, Dictionary(Of String, clsCache))
            Dim KeyCache As clsCache = (From d In mDictionaries _
                                        Where d.Key = KeyName _
                                        Select d.Value).FirstOrDefault

            If Not IsNothing(KeyCache) Then
                Dim StepBY As Single = 0
                Dim MaxOrder() As Integer = (From d In mDictionaries Select d.Value.Hits).ToArray
                If MaxOrder IsNot Nothing Then
                    StepBY = MaxOrder.Max / 7
                End If

                If KeyCache.Hits <= StepBY Then
                    Return Web.Caching.CacheItemPriority.Low
                Else
                    Return CType((KeyCache.Hits / StepBY), Integer)
                End If
            Else
                Return Web.Caching.CacheItemPriority.BelowNormal
            End If
        Else
            Return Web.Caching.CacheItemPriority.BelowNormal
        End If
    End Function
    Private Shared Function GetCacheItem(ByVal CacheName As String) As clsCache
        Dim mObj As Object = System.Web.HttpRuntime.Cache.Get("CacheDict")
        Dim mDictionaries As New Dictionary(Of String, clsCache)
        Dim mClsCache As clsCache = Nothing
        '查找更新字典对象中缓存状态
        If Not IsNothing(mObj) Then
            mDictionaries = CType(mObj, Dictionary(Of String, clsCache))
            If mDictionaries.ContainsKey(CacheName) Then
                mClsCache = mDictionaries.Item(CacheName)
                If Not IsNothing(mClsCache) Then
                    mClsCache.Hits += 1
                    mClsCache.CachePriority = ComputePriority(CacheName)

                Else
                    mClsCache = New clsCache With {.CachePriority = Web.Caching.CacheItemPriority.Normal, .Hits = 1, .IsUpdate = True, .Count = 0}
                End If
            Else
                mClsCache = New clsCache With {.CachePriority = Web.Caching.CacheItemPriority.Normal, .Hits = 1, .IsUpdate = True, .Count = 0}
            End If
        Else
            mClsCache = New clsCache With {.CachePriority = Web.Caching.CacheItemPriority.Normal, .Hits = 1, .IsUpdate = True, .Count = 0}
        End If
        Return mClsCache
    End Function
    Public Shared Sub SaveCacheItem(ByVal CacheItem As clsCache, ByVal CacheName As String)
        Dim mObj As Object = System.Web.HttpRuntime.Cache.Get("CacheDict")
        Dim mDictionaries As New Dictionary(Of String, clsCache)
        Dim mClsCache As clsCache = Nothing
        If Not IsNothing(mObj) Then
            mDictionaries = CType(mObj, Dictionary(Of String, clsCache))
            mDictionaries.Item(CacheName) = mClsCache
        Else
            mDictionaries.Add(CacheName, mClsCache)
        End If
        SyncLock (GetType(XrCache))
            System.Web.HttpRuntime.Cache.Insert("CacheDict", mDictionaries, Nothing, Web.Caching.Cache.NoAbsoluteExpiration, Web.Caching.Cache.NoSlidingExpiration, Web.Caching.CacheItemPriority.NotRemovable, New Web.Caching.CacheItemRemovedCallback(AddressOf OnDictionaryRemove))
        End SyncLock
    End Sub

    ''' <summary> 获取缓存并根据缓存命中率设置缓存级别</summary>
    ''' <param name="dbContext">ObjectContext实例</param>
    ''' <param name="CacheName">缓存键值</param>
    ''' <param name="LinqQuery">Linq查询语句,分页查询需要提供查询全部的LINQ</param>
    ''' <param name="CacheLife">缓存依赖类型</param>
    ''' <param name="DependOf">绝对过期和相对过期值。或一组文件名</param>
    ''' <param name="dbCount">可选参数：需返回的查询记录集总数</param>
    ''' <param name="TakeNum">可选参数：返回记录集数量，默认返回所有查询</param>
    ''' <param name="SkipNum">可选参数：跳过多少条记录</param>
    ''' <param name="OpenSecondCache">可选参数：是否使用二级缓存，当主缓存被移除后，可从二级缓存加载，同时启动新线程更新主缓存</param>
    ''' <returns>查询结果集</returns>
    Public Shared Function GetList(Of T)(ByVal dbContext As Data.Linq.DataContext, ByVal CacheName As String, ByRef LinqQuery As IQueryable(Of T), ByVal CacheLife As CacheLifeType, ByVal DependOf As String, Optional ByRef dbCount As Integer = -1, Optional ByVal TakeNum As Integer = 0, Optional ByVal SkipNum As Integer = 0, Optional ByVal OpenSecondCache As Boolean = False) As List(Of T)
        Dim mClsCache As clsCache = GetCacheItem(CacheName)
        If dbCount >= 0 Then dbCount = mClsCache.Count
        '根据字典中缓存状态确定如何读取缓存
        Dim MainCache As Object = System.Web.HttpRuntime.Cache.Get(CacheName)
        If Not IsNothing(MainCache) Then
            If dbCount >= 0 Then
                If mClsCache.Count > 0 Then
                    dbCount = mClsCache.Count
                Else
                    dbCount = LinqQuery.Count
                    mClsCache.Count = dbCount
                End If
            End If
            SaveCacheItem(mClsCache, CacheName)
            Return CType(MainCache, List(Of T))
        Else
            Return UpdateMainCache(dbContext, CacheName, LinqQuery, CacheLife, DependOf, dbCount, TakeNum, SkipNum, mClsCache.CachePriority, OpenSecondCache, mClsCache)
        End If
        
    End Function

    ''' <summary>
    ''' 更新主缓存，并从二级缓存加载数据
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="CacheName"></param>
    ''' <param name="LinqQuery"></param>
    ''' <param name="CacheLife"></param>
    ''' <param name="DependOf"></param>
    ''' <param name="dbCount"></param>
    ''' <param name="TakeNum"></param>
    ''' <param name="SkipNum"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function UpdateMainCache(Of T)(ByVal dbCTX As Data.Linq.DataContext, ByVal CacheName As String, ByRef LinqQuery As IQueryable(Of T), ByVal CacheLife As CacheLifeType, ByVal DependOf As String, ByRef dbCount As Integer, ByVal TakeNum As Integer, ByVal SkipNum As Integer, ByVal CachePriority As Web.Caching.CacheItemPriority, ByVal OpenSecondCache As Boolean, ByVal CacheItem As clsCache) As List(Of T)
        Dim ReTurnValue As List(Of T)
        Dim mclsThread As New clsCacheThread(Of T)
        With mclsThread
            .ctxContext = dbCTX
            .CacheName = CacheName
            .CacheLife = CacheLife
            .CacheDependOf = DependOf
            If dbCount >= 0 Then
                .ReadCount = True
            Else
                .ReadCount = False
            End If
            .QuaryLinq = LinqQuery
            .TakeCount = TakeNum
            .SkipCount = SkipNum
            .CacheItem = CacheItem
        End With
        '判断是否从二级缓存读取
        If Not OpenSecondCache Then
            ReTurnValue = mclsThread.UpdateMainCache()
            dbCount = mclsThread.QuaryCount
        Else
            '从二级缓存中获取
            If dbCount >= 0 Then
                dbCount = LinqQuery.Count
            End If
            Dim SecCache As Object = System.Web.HttpRuntime.Cache.Get("SecCache_" & CacheName)
            If Not IsNothing(SecCache) Then
                Dim mTheard As New System.Threading.Thread(AddressOf mclsThread.UpdateMainCache)
                mTheard.Start()
                ReTurnValue = CType(SecCache, List(Of T))
            Else
                ReTurnValue = mclsThread.UpdateMainCache()
            End If
        End If
        Return ReTurnValue
    End Function

    Public Shared Sub OnDictionaryRemove(ByVal CacheKey As String, ByVal CacheValue As Object, ByVal RemoveReason As Web.Caching.CacheItemRemovedReason)
        If Not RemoveReason = Web.Caching.CacheItemRemovedReason.Removed Then
            SyncLock (GetType(XrCache))
                System.Web.HttpRuntime.Cache.Insert("CacheDict", CacheValue, Nothing, Web.Caching.Cache.NoAbsoluteExpiration, Web.Caching.Cache.NoSlidingExpiration, Web.Caching.CacheItemPriority.NotRemovable, New Web.Caching.CacheItemRemovedCallback(AddressOf OnDictionaryRemove))
            End SyncLock
        End If

    End Sub

    Public Shared Sub OnCacheRemove(ByVal CacheKey As String, ByVal CacheValue As Object, ByVal RemoveReason As Web.Caching.CacheItemRemovedReason)
        Dim mObj As Object = System.Web.HttpRuntime.Cache.Get("CacheDict")
        Dim mDictionaries As New Dictionary(Of String, clsCache)
        If mDictionaries.ContainsKey(CacheKey) Then
            mDictionaries.Item(CacheKey).IsUpdate = True
        End If
        SyncLock (GetType(XrCache))
            System.Web.HttpRuntime.Cache.Insert("CacheDict", mDictionaries, Nothing, Web.Caching.Cache.NoAbsoluteExpiration, Web.Caching.Cache.NoSlidingExpiration, Web.Caching.CacheItemPriority.NotRemovable, New Web.Caching.CacheItemRemovedCallback(AddressOf OnDictionaryRemove))
        End SyncLock
    End Sub

    ''' <summary> 缓存数据</summary>
    ''' <param name="CacheName">缓存键值</param>
    ''' <param name="CountCommandText">查询总记录数的语句</param>
    ''' <param name="MainCommandText">主查询语句</param>
    ''' <param name="CacheLife">缓存依赖类型</param>
    ''' <param name="DependOf">绝对过期和相对过期值。或一组文件名</param>
    ''' <param name="dbCount">可选参数：需返回的查询记录集总数</param>
    ''' <param name="OpenSecondCache">可选参数：是否使用二级缓存，当主缓存被移除后，可从二级缓存加载，同时启动新线程更新主缓存</param>
    ''' <returns>返回查询DataTable</returns>
    Public Shared Function GetList(ByVal CacheName As String, ByVal CountCommandText As String, ByRef MainCommandText As String, ByVal CacheLife As CacheLifeType, ByVal DependOf As String, Optional ByRef dbCount As Integer = -1, Optional ByVal OpenSecondCache As Boolean = False) As Data.DataTable
        Dim mClsCache As clsCache = GetCacheItem(CacheName)
        '根据字典中缓存状态确定如何读取缓存
        Dim MainCache As Object = System.Web.HttpRuntime.Cache.Get(CacheName)
        If Not IsNothing(MainCache) Then
            If dbCount >= 0 Then
                If mClsCache.Count > 0 Then
                    dbCount = mClsCache.Count
                Else
                    Using mConn As New Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings("XrenCRMConnString").ConnectionString)
                        mConn.Open()
                        Dim mCMD As New Data.SqlClient.SqlCommand
                        mCMD.Connection = mConn
                        mCMD.CommandText = CountCommandText
                        dbCount = CType(mCMD.ExecuteScalar, Integer)
                        mConn.Close()
                    End Using
                    mClsCache.Count = dbCount
                End If
            End If
            SaveCacheItem(mClsCache, CacheName)
            Return CType(MainCache, Data.DataTable)
        Else
            Return UpdateMainCache(CacheName, CountCommandText, MainCommandText, CacheLife, DependOf, mClsCache, dbCount, OpenSecondCache, mClsCache)
        End If
    End Function
    ''' <summary>
    ''' 更新主缓存，并从二级缓存加载数据
    ''' </summary>
    ''' <param name="CacheName"></param>
    ''' <param name="CountCommandText"></param>
    ''' <param name="MainCommandText"></param>
    ''' <param name="CacheLife"></param>
    ''' <param name="DependOf"></param>
    ''' <param name="dbCount"></param>
    ''' <param name="OpenSecondCache"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function UpdateMainCache(ByVal CacheName As String, ByVal CountCommandText As String, ByRef MainCommandText As String, ByVal CacheLife As CacheLifeType, ByVal DependOf As String, ByVal clsCacheDic As clsCache, ByRef dbCount As Integer, ByVal OpenSecondCache As Boolean, ByVal CacheItem As clsCache) As Data.DataTable
        Dim ReTurnValue As Data.DataTable
        Dim mclsThread As New clsDataCacheThread
        With mclsThread
            .CacheName = CacheName
            .CacheLife = CacheLife
            .DependOf = DependOf
            .CountCommandText = CountCommandText
            .dbCount = dbCount
            .MainCommandText = MainCommandText
            .CacheItem = CacheItem
        End With
        '判断是否从二级缓存读取
        If Not OpenSecondCache Then
            ReTurnValue = mclsThread.UpdateMainCache()
            dbCount = mclsThread.dbCount
        Else
            '从二级缓存中获取
            If dbCount >= 0 Then
                If clsCacheDic.Count > 0 Then
                    dbCount = clsCacheDic.Count
                Else
                    Using mConn As New Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings("XrenCRMConnString").ConnectionString)
                        mConn.Open()
                        Dim mCMD As New Data.SqlClient.SqlCommand
                        mCMD.Connection = mConn
                        mCMD.CommandText = CountCommandText
                        dbCount = CType(mCMD.ExecuteScalar, Integer)
                        mConn.Close()
                    End Using
                End If
            End If
            Dim SecCache As Object = System.Web.HttpRuntime.Cache.Get("SecCache_" & CacheName)
            If Not IsNothing(SecCache) Then
                Dim mTheard As New System.Threading.Thread(AddressOf mclsThread.UpdateMainCache)
                mTheard.Start()
                ReTurnValue = CType(SecCache, Data.DataTable)
            Else
                ReTurnValue = mclsThread.UpdateMainCache()
            End If
        End If
        Return ReTurnValue
    End Function
    ''' <summary>
    ''' HTML片段缓存
    ''' </summary>
    ''' <param name="SQLID">查询序号</param>
    ''' <param name="SQLName">查询名称</param>
    ''' <param name="MainSQL">主查询</param>
    ''' <param name="CountSQL">记录总数查询</param>
    ''' <param name="FunctionNote">XML节点</param>
    ''' <param name="ControlID">控件序号</param>
    ''' <returns>输出HTML代码片段</returns>
    ''' <remarks></remarks>
    Public Shared Function GetList(ByVal SQLID As Integer, ByVal SQLName As String, ByVal CacheLife As XrCache.CacheLifeType, ByVal CacheDependOf As String, ByVal MainSQL As clsSQLAndCacheName, ByVal CountSQL As clsSQLAndCacheName, ByVal FunctionNote As XElement, ByVal ControlID As String, ByVal OpenSecondCache As Boolean) As clsHTML
        Dim mClsCache As clsCache = GetCacheItem(MainSQL.CacheName)
        '根据字典中缓存状态确定如何读取缓存
        Dim MainCache As Object = System.Web.HttpRuntime.Cache.Get(MainSQL.CacheName)
        If Not IsNothing(MainCache) Then
            SaveCacheItem(mClsCache, MainSQL.CacheName)
            Return CType(MainCache, clsHTML)
        Else
            Return UpdateMainCache(SQLID, SQLName, CacheLife, CacheDependOf, MainSQL, CountSQL, FunctionNote, ControlID, OpenSecondCache, mClsCache)
        End If
    End Function
    ''' <summary>
    ''' 更新主缓存，并从二级缓存加载数据
    ''' </summary>
    ''' <param name="SQLID"></param>
    ''' <param name="SQLName"></param>
    ''' <param name="MainSQL"></param>
    ''' <param name="CountSQL"></param>
    ''' <param name="FunctionNote"></param>
    ''' <param name="ControlID"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function UpdateMainCache(ByVal SQLID As Integer, ByVal SQLName As String, ByVal CacheLife As XrCache.CacheLifeType, ByVal CacheDependOf As String, ByVal MainSQL As clsSQLAndCacheName, ByVal CountSQL As clsSQLAndCacheName, ByVal FunctionNote As XElement, ByVal ControlID As String, ByVal OpenSecondCache As Boolean, ByVal CacheItem As clsCache) As clsHTML
        Dim ReTurnValue As clsHTML
        Dim mclsThread As New clsHTMLThread
        With mclsThread
            .SQLID = SQLID
            .MainSQL = MainSQL
            .CountSQL = CountSQL
            .FunctionNote = FunctionNote
            .ControlID = ControlID
            .CacheLife = CacheLife
            .CacheDependOf = CacheDependOf
            .OpenSecondCache = OpenSecondCache
            .CacheItem = CacheItem
        End With
        '判断是否从二级缓存读取
        If Not OpenSecondCache Then
            ReTurnValue = mclsThread.UpdateMainCache()
        Else
            '从二级缓存中获取
            Dim SecCache As Object = System.Web.HttpRuntime.Cache.Get("SecCache_" & MainSQL.CacheName)
            If Not IsNothing(SecCache) Then
                Dim mTheard As New System.Threading.Thread(AddressOf mclsThread.UpdateMainCache)
                mTheard.Start()
                ReTurnValue = CType(SecCache, clsHTML)
            Else
                ReTurnValue = mclsThread.UpdateMainCache()
            End If
        End If
        Return ReTurnValue
    End Function
End Class
