Partager via


$null строки в Powershell и переиспользование сессий в Analysis Services

В Analysis Services различаются понятия соединения (connection) и сессии (session, в русских BOL - сеанс). Между ними существует отношение многие-ко-многим, в частности, несколько соединений могут шарить одну сессию. Сессия поддерживает контекст состояния: текущий каталог, вычисления и т.д. По умолчанию, каждое новое соединение открывает новую сессию, и по его закрытию она закрывается. Можно закрыть соединение без закрытия сессии, если вызвать перегруженый метод Close соединения AdomdConnection с параметром false. Осиротевшую сессию, т.е.не имеющую связанных с ней открытых соединений, SSAS прибирает после IdleOrphanSessionTimeout. Время жизни неактивной сессии определяется MaxIdleSessionTimeout. Эти и другие конфигурационные настройки сервера Analysis Services можно видеть, если кликнуть по нему правой кнопкой в Object Explorere SSMS, выбрать в контекстном меню пункт Properties и отметить галку Show Advanced (All) Properties.

image

Рис.1

Указанные настройки хранятся в файле C:\Program Files\Microsoft SQL Server\MSAS10_50.MSSQLSERVER\OLAP\Config\msmdsrv.ini. Еще во времена 2005-го Энтони Манн и Эдвард Меломед написали статью «SQL Server 2005 Analysis Services (SSAS) Server Properties», в которой толково объясняется, что означает каждая настройка и на что она влияет. Замечательный документ. Я рекомендую всем его распечатать и пришпилить куда-нибудь на видное место. Более подробно прочитать про различия сессий и соединений можно в BOL – см. Установление соединений в ADOMD.NET и Работа с соединениями и сеансами в ADOMD.NET.

С точки зрения производительности бывает выгодно повторно использовать сессии, экономя на пересоздании контекста. В этом случае мы сохраняем в приложении идентификатор сессии (свойство соединения SessionID), a закрывая AdomdConnection, говорим серверу оставить сессию - сnn.Close(false). Перед открытием нового соединения ему присваивается сохраненный идентификатор прежней сессии и, если та еще не проэкспайрилась, оно будет использовать ее контекст. Все просто. Я попытался воспроизвести пример, описанный в книжке Тео Лачева «Applied Microsoft Analysis Services 2005» (п.17.4.3 Managing Connections and Sessions, стр.574), на PowerShell.

 

[reflection.assembly]::LoadWithPartialName("Microsoft.AnalysisServices.AdomdClient") | Out-Null

[string] $global:sessionId

[Microsoft.AnalysisServices.AdomdClient.AdomdConnection] $cnn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection("Data Source=localhost;Initial Catalog=Adventure Works DW 2008R2")

 

<# Предполагается, что в переменной $global:sessionId хранится идентификатор сессии AS, оставшийся от прошлого раза. #>

$cnn.SessionID = $global:sessionId

 

<# Если такого sessionId не значится, например, дело происходит в первый раз и он еще пуст или прошло больше IdleOrphanSessionTimeout

и старый проэкспайрился, при открытии соединения с таким sessionId выкинется ошибка. #>

 

while ($cnn.State -eq "Closed")

{

 try  

 {

  $cnn.Open()

 }

 catch [Microsoft.AnalysisServices.AdomdClient.AdomdConnectionException]

 {

  <# Ожидаем исключения, что такого SessionID нет. Если возникает другое - проталкиваем дальше. #>

  if ($_.Exception.InnerException.Message.IndexOf("Either the session does not exist or it has already expired") -le 0)

  {

   throw

  }

  else

  {

   <# Если выяснилось, что этот $cnn.SessionID истек, надо сбросить его обратно в Null и заново попытаться открыть соединение.

      Новому соединению будет назначена новая сессия с новым SessionID. #>

   $cnn.SessionID = $null

  }

 }

}

 

<# Сохраняем SessionID (на случай, если он сменился) для дальнейшего переиспользования. #>

$global:sessionId = $cnn.SessionID

 

<# Выполняем какие-то полезные действия на соединении #>

[Microsoft.AnalysisServices.AdomdClient.AdomdCommand] $cmd = $cnn.CreateCommand()

#...

$cnn.Close($false) #закрыть соединение, оставив сессию открытой

Скрипт 1

 

Смысл скрипта понятен: сначала пытаемся открыть соединение с прежним SessionID, оставшимся от каких-то предыдущих действий. Если таковой истек, сбрасываем $cnn.SessionID обратно в Null и снова пытаемся открыть соединение. Тогда при открытии для него будет автоматически создана новая сессия, и в $cnn.SessionID появится ее идентификатор.

К сожалению, этот скрипт не работает из-за одной неприятной особенности PowerShell. При присвоении $null в $cnn.SessionID он конвертируется в пустую строку, т.к. св-во SessionID имеет тип строки. ADOMD.NET обучено заводить новую сессию только в случае, когда свойство SessionID у соединения равно строго Null, т.е. нулевой указатель на область памяти, где хранится содержание строки. Если же там лежит какая-то строка, пусть даже пустая, т.е. указатель непустой, но указывает на пустую область памти, ADOMD.NET воспринимает это как сигнал того, что мы хотим открыть соединение в контексте некоторой сессии. Сессии с SessionID в виде пустой строки она не находит, поэтому соединение не открывает, и цикл будет длиться бесконечно .

Когда в переменную сохраняется некоторое значение, хотелось бы быть уверенным, что оно там в целости и неизменности сохранится, и в своем первозданном виде его можно оттуда достать по мере надобности. PowerShell является уникальным языком в этом плане. Можно положить одно, а достать совсем другое:

 

[string] $s = $null

Write-Host ($s -eq $null)

--------------------------------------------

False

Скрипт 2

 

Вопрос: не знает ли кто, как запретить Powershell’у творить такое непотребство? Мне не нужно, чтобы $null конвертировался в пустую строку. Нужно, чтобы $null оставался $null, как это принято во всех нормальных человеческих языцех.

В прошлом посте я полагал, что самый занятный язык – это диалект SQL, поддерживаемый Analysis Services. На самом деле, то были цветочки. Самый занятный язык – это PowerShell. Нарекания на такое поведение существуют с незапамятных времен - https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=307821&SiteID=99, http://www.vistax64.com/powershell/160947-string-null-eq-null-eq.html, http://www.techtalkz.com/microsoft-windows-powershell/150495-gotcha-null-string-not-null.html  . Возможно, его с тех пор уже поправили. Если вы в курсе таких исправлений, подскажите, и вам воздастся. Пока что воркэраунды, которые удалось найти, достаточно монструозны. Проще переписать Скрипт 1 как

 

cls

[reflection.assembly]::LoadWithPartialName("Microsoft.AnalysisServices.AdomdClient") | Out - Null

[string] $global:sessionId

[string] $connectionString = "Data Source=localhost;Initial Catalog=Adventure Works DW 2008R2"

[Microsoft.AnalysisServices.AdomdClient.AdomdConnection] $cnn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection($connectionString)

 

try  

{

 $cnn.Open()

}

catch [Microsoft.AnalysisServices.AdomdClient.AdomdConnectionException]

{

 if ($_.Exception.InnerException.Message.IndexOf("Either the session does not exist or it has already expired") -le 0)

 {

  throw

 }

 else

 {

  $cnn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection($connectionString)

  $cnn.Open()

  $global:sessionId = $cnn.SessionID

 }

}

 

<# Выполняем какие-то полезные действия на соединении #>

[Microsoft.AnalysisServices.AdomdClient.AdomdCommand] $cmd = $cnn.CreateCommand()

#...

$cnn.Close($false) #закрыть соединение, оставив сессию открытой

Скрипт 3

 

Алексей Шуленин

Comments

  • Anonymous
    July 22, 2010
    Согласен, что обходные пути совсем не просты: вот вариант [reflection.assembly]$analysisServicesAssembly =  [reflection.assembly]::LoadWithPartialName("Microsoft.AnalysisServices.AdomdClient") [Microsoft.CSharp.CSharpCodeProvider]$provider = New-Object Microsoft.CSharp.CSharpCodeProvider [System.CodeDom.Compiler.CompilerParameters]$params = New-Object System.CodeDom.Compiler.CompilerParameters $params.GenerateInMemory = $True $refs = "System.dll", "System.Data.dll", $analysisServicesAssembly.Location, "mscorlib.dll" $params.ReferencedAssemblies.AddRange($refs) [string]$txtCode = ' public class ConnectionSessionIDWrapper {  public void SetSessionID(Microsoft.AnalysisServices.AdomdClient.AdomdConnection connection, object sessionID)  {    if(connection != null) connection.SessionID = (string)sessionID;  }   } ' $results = $provider.CompileAssemblyFromSource($params, $txtCode) $mAssembly = $results.CompiledAssembly $wrapper = $mAssembly.CreateInstance('ConnectionSessionIDWrapper') [string] $global:sessionId [Microsoft.AnalysisServices.AdomdClient.AdomdConnection] $cnn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection("Data Source=localhost;Initial Catalog=Adventure Works DW 2008R2") <# Предполагается, что в переменной $global:sessionId хранится идентификатор сессии AS, оставшийся от прошлого раза. #> #$cnn.SessionID=[nullable]$global:sessionId $wrapper.SetSessionID($cnn, $global:sessionId) <# Если такого sessionId не значится, например, дело происходит в первый раз и он еще пуст или прошло больше IdleOrphanSessionTimeout и старый проэкспайрился, при открытии соединения с таким sessionId выкинется ошибка. #> while ($cnn.State -eq "Closed") { try   {  $cnn.Open() } catch [Microsoft.AnalysisServices.AdomdClient.AdomdConnectionException] {  <# Ожидаем исключения, что такого SessionID нет. Если возникает другое - проталкиваем дальше. #>  if ($_.Exception.InnerException.Message.IndexOf("Either the session does not exist or it has already expired") -le 0)  {   throw  }  else  {   <# Если выяснилось, что этот $cnn.SessionID истек, надо сбросить его обратно в Null и заново попытаться открыть соединение.      Новому соединению будет назначена новая сессия с новым SessionID. #>  $wrapper.SetSessionID($cnn, $null)  } } } <# Сохраняем SessionID (на случай, если он сменился) для дальнейшего переиспользования. #> $global:sessionId = $cnn.SessionID <# Выполняем какие-то полезные действия на соединении #> [Microsoft.AnalysisServices.AdomdClient.AdomdCommand] $cmd = $cnn.CreateCommand() #... $cnn.Close($false) #закрыть соединение, оставив сессию открытой