Если вы захотите создавать объекты в SCSM с помощью кода или скриптов, рано или поздно (скорее всего рано) вы столкнетесь с таким понятием как Type Projection. В данной статье я попытаюсь разъяcнить, что же это такое, для чего нужно и как его можно использовать, но я не буду рассматривать создание собственных Type Projection.
В эпоху, “когда небо было голубее, а деревья больше” даже трехзвенные системы (клиент-сервер-БД) редко использовали свою собственную модель данных. Чаще всего классы и методы подстраивались под структуру и особенности базы данных. Это и понятно – БД уже имеет все средства для работы с объектами, и изобретать что-то новое смысла не было. Если кто-то не знаком с реляционными базами данных, в частности с MSSQL, то давайте немного истории:
- Все данные в MSSQL хранятся в виде таблиц.
- Связи между таблицами можно устанавливать по определенному столбцу, поддерживается отношение один-к-одному, один-к-многим и многие-ко-многим.
- Для получения данных сразу и нескольких связанных таблиц можно с помощью оператора SELECT JOIN.
Отличительная особенность таких систем – возможность оперировать данными напрямую в базе данных. Поэтому часто даже для коммерческих систем появлялись альтернативные интерфейсы, переписывалась логика и т.д.
С ростом объемов данных и сложности приложений, разработчики пришли к тому, что необходимо было создавать свою модель данных, и приспосабливать базу данных под эту модель. Для меня первой такой системой стал Sharepoint, где модель данных была абстрагирована от базы данных. В свою очередь это приводило к тому, что работать напрямую с базой данной стало практически невозможно, поэтому на плечи разработчика, кроме всего прочего, ложилась ответственность за создание интерфейсов доступа к данным. Одним из ключевых моментов таких интерфейсов является обслуживание связей. К примеру, у нас есть Инцидент. У него есть ряд полей, таких как название, описание, дата создания. Всё они являются простыми типами данных – строка, дата, число. Кроме этого, в инциденте есть поля Пользователь, Связанные инциденты и так далее. Логично, что эти поля должны являться ссылкой на другие объекты в модели данных. В Sharepoint это и была именно ссылка (reference) – вы получали значение, являющиеся просто указанием на другой объект в системе с указанием его идентификатора. Такой подход имеет ряд минусов:
- в связи с тем, что в поле хранится только идентификатор, разработчик должен точно знать тип данных, используемый в данном поле, чтобы использовать затем нужные методы.
- сложно получить связанный набор данных в одном запросе.
В SCSM разработчики Microsoft предложили радикально другой подход. Для получения объектов используется два вида методов: получение самих объектов и получение связанных данных. В первом случае мы получаем объект(ы) только с простыми полями, без ссылок на связанные объекты. Во втором случае обязательным параметров метода для получения объектов является описание отношений. Это описание определяет, какие связанные поля будут получены в запросе. Это описание получило название Type Projection (далее по тексту – TP). К сожалению, я не смог подобрать адекватного перевода этого термина, поэтому буду пользоваться английским вариантом.
Type Projection описывает, какие связи между различными классами будут использованы для запроса. К примеру, класс Инцидент имеет связь со многими классами в системе. Но при этом получать все эти поля в каждом запросе нет необходимости, чаще всего используется запрос, в котором необходимо получить сам инцидент и связанного с ним пользователя. Type Projection может быть вложенным, т.е. в одном запросе мы можем получать данные сразу о цепочке связанных объектов.
При описании Type Projection указывается целевой класс, для которого создается TP, а также компоненты этого TP. Каждый компонент определяет, какая из связей будет использоваться в данном TP.
Рассмотрим Type Projection на примере класса Инцидент. Как я уже говорил, он имеет массу связей, но чаще всего (по мнению разработчиков MS) используется связь инцидента с классом Пользователь в полях “Affected User” и “Assigned User”. Определение этого Type Projection в пакете управления выглядит следующим образом (да, это XML, а вы на что надеялись ;) ):
<typeprojection id="System.WorkItem.Incident.View.ProjectionType" type="CoreIncident!System.WorkItem.Incident" accessibility="Public"> <component alias="AffectedUser" path="$Target/Path[Relationship='WorkItem!System.WorkItemAffectedUser']$"></component> <component alias="AssignedUser" path="$Target/Path[Relationship='WorkItem!System.WorkItemAssignedToUser']$"></component> </typeprojection>
В строке 1 находится определение. ID – это внутреннее имя TP. Здесь стоит сделать оговорку. В документации и самом SCSM вы можете встретить еще один ID, обычно он имеет префикс InernalID и тип GUID. Этот InternalID хранится только в базе данных и генерируется в момент появления объекта в базе данных. Но т.к. этот GUID формируется по тем же принципам, что и в OpsMgr, для каждого уникального объекта он будет всегда одним и тем же на любой инсталляции.
Кроме этого в определении указывается целевой класс, для которого мы создаем TP, в данном случае это класс Инцидента.
В строках 2 и 3 мы определяем компоненты, т.е. какие связи класса мы будет использовать в этом ТР. Path является ссылкой на отношение, Alias – название связи.
ТР используются повсеместно и в консоли SCSM. Когда вы создаете то или иное представление, вы всегда используете одно из ТР. Вот так выглядит настройка представления, использующего показанный выше ТР:
Вы видите, что доступны только два связанных поля. Но достаточно выбрать другой ТР, и мы получим полный набор связанных полей:
Такой подход имеет огромное количество плюсов, это и простота получения данных, и получение только необходимого объема данных т.д. Но несколько минусов могут перечеркнуть все плюсы. Как вы уже заметили, каждый класс имеет несколько ТР и количество полей сильно зависит от того, какой из ТР мы используем. В случае, если мы не верно выбрали ТР в момент проектирования, затем мы можем не получить необходимые нам данные. Скажем, мы хотим получить связанные с инцидентом запросы на изменение. Если мы будем использовать для этого ТР “System.WorkItem.Incident.View.ProjectionType”, то получим пустые поля RelatedWorkItem. Еще один огромный минус – это довольно большая сложность при использовании ТР в Powershell. Хотя тут скорее сама модель ТР не причем, а дело в реализации. Для унификации работы с объектами разработчики воспользовались generic-методами и классами. Такие методы могут работать с данными разных типов, собственно определение типа происходит в момент использования метода или определения класса. В C# такая конструкция экономит очень и очень много строчек кода, а вот в Powershell мы имеем ровно противоположную картину – для использования generic-методов надо написать довольно много дополнительного кода. Еще один минус, и честно говоря мне абсолютно не понятный – данные из ТР нельзя использовать как столбцы в представлениях. На картинке выше вы видите все поля, и можете их использовать, но это лишь в критерии. В отображаемых столбцах мы можем выбрать только связанные поля класса Пользователь (Affected User и Assigned User). По этой же причине мы не можем вставлять в шаблоны писем данные о связанных элементах.
Итак, мы разобрались (надеюсь :) ) что такое ТР, самое время показать как их использовать. Т.к. вряд ли тут наберется много программистов, все примеры будут на Powershell (к моему сожалению, т.к. на C# это выглядит гораздо проще и изящнее). Для начала получим все ТР для конкретного класса:
# Подгружаем библиотеку SCSM [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core") | Out-Null $managementGroup = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup("localhost"); $mpCR = $managementGroup.ManagementPacks.GetManagementPack("System.WorkItem.ChangeRequest.Library", "31bf3856ad364e35", "7.0.5826.0") # Этот guid - это класс инцидента, посмотреть все можно с помощью команды #$managementGroup.EntityTypes.GetClasses() | select ID, Name, DisplayName #$classCR = $managementGroup.EntityTypes.GetClass("e6c9cf6e-d7fe-1b5d-216c-c3f5d2c7670c") $classCR = $managementGroup.EntityTypes.GetClass("A604B942-4C7B-2FB2-28DC-61DC6F465C68") # Получаем все ТР для класса Инцидент. # Мы могли вместо получения экземпляра класса использовать фильтр $_.TargetType.Name -eq "System.WorkItem.Incident" # но экземпляр нам всё равно понадобиться потом, так что я предпочитаю делать именно так: $managementGroup.EntityTypes.GetTypeProjections() | ? {$_.TargetType -eq $classCR} | % { # выводим ключевые свойства write-host "ID:"$_.ID write-host "Name:"$_.Name write-host "Display Name:"$_.DisplayName write-host "TargetType:"$_.TargetType.Name write-host "Endpoint:"$_.TargetEndpoint.Name write-host "Components: Endpoint (Class)" $_.ComponentCollection | % {write-host "`t"$_.TargetEndpoint.Name"("$_.TargetType.Name")"} write-host }
Часть вывода:
ID: 1862825e-21bc-3ab2-223e-2a7f2439ba75 Name: System.WorkItem.Incident.View.ProjectionType Display Name: Инцидент (типовой) TargetType: System.WorkItem.Incident Endpoint: Components: Endpoint (Class) RequestedWorkItem ( System.User ) AssignedWorkItem ( System.User )
Теперь мы можем получать данные из объектов. Для получения всех объектов (без фильтрации) необходимо:
- Получить экземпляр ТР
- Создать экземпляр класса ObjectProjectionCriteria, передав в качестве параметра ТР
- Выполнить метод GetObjectProjectionReader
- Получить данные
Для примера давайте получим все запросы на изменение:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core") | Out-Null $managementGroup = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup("localhost"); # В данном случае мы получаем ТР по его имени с помощью возможностей Powershell $projCR = $managementGroup.EntityTypes.GetTypeProjections() | ? {$_.Name -eq "System.WorkItem.ChangeRequestProjection"} # Создаем критерий для отбора. Конструктор имеет несколько вариантов, # один из них позволяет также сказать фильтр для отбора определенных объектов. # см. http://msdn.microsoft.com/en-us/library/microsoft.enterprisemanagement.common.objectprojectioncriteria.ctor.aspx # Мы получаем все объекты без всякого фильтра: $criteria = new-object Microsoft.EnterpriseManagement.Common.ObjectProjectionCriteria($ProjCR) # Далее мы создаем метод из generic-метода # Первое, что необходимо сделать, найти определение нужной нам функции. В нашем случае это GetObjectProjectionReader: # http://msdn.microsoft.com/en-us/library/ee455921.aspx # Как видно, он должна принимать два параметра типа ObjectProjectionCriteria и ObjectQueryOptions # Создает массив из этих типов [Type[]]$MethodType = ([Microsoft.EnterpriseManagement.Common.ObjectProjectionCriteria],[Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]) # Получаем нужный нам метод с соответствующими параметрами $GetObjectProjectionReader = $managementGroup.EntityObjects.GetType().GetMethod("GetObjectProjectionReader",$MethodType) # Т.к. метод GetObjectProjectionReader еще и возвращает данные в виде generic-типа, # нам необходимо создать его с нужными выходным типом, # в данном случае это EnterpriseManagementObject $GetObjectProjectionReaderMethod = $GetObjectProjectionReader.MakeGenericMethod([Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject]) # Теперь мы можем выполнять этот метод. Для этого надо вызвать Invoke, # первым параметром мы передаем объект, для которого мы хотим вызвать метод, вторым параметром - аргументы этого метода $allCRs = $GetObjectProjectionReaderMethod.Invoke($managementGroup.EntityObjects, ($criteria, [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]::Default)) # Если бы это был обычный метод, а не generic, вызов бы выглядел более привычно: # $managementGroup.EntityObjects.GetObjectProjectionReader($criteria, [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]::Default) # Как видите, в Powershell мы вынуждены выполнять три операции вместо одной. В C# всё выглядит гораздо более проще: # allCRs = managementGroup.EntityObjects.GetObjectProjectionReader<EnterpriseManagementObject>(criteria, ObjectQueryOptions.Default); #Теперь остается извлечь все CR-ы и вывести их свойства foreach($cr in $allCRs) { # Имя объекта - это его заголовок $cr.Object.Name # Перебираем все компоненты в ТР foreach($comp in $projCR.ComponentCollection) { # В данном случае Endpoint - это название поля в объекте write-host "`t"$comp.TargetEndpoint.Name # Перебираем все связанные объекты для каждого компонента, ведь их может быть несколько $cr[$comp.TargetEndpoint.Name] | % { write-host "`t`t"$_.Object.DisplayName } } # Данный код делает тоже самое, что и выше, но выводит только НЕ пустые связанные элементы #$keys = $cr | %{ $_.key.name } | sort -uniq #foreach($key in $keys) #{ # write-host "`t"$key # $cr[$key] | % { write-host "`t`t"$_.Object.DisplayName} #} # Теперь получаем все простые свойства объекта $cr.Object.Values | % {write-host "`t"$_.Type":"$_.Value} write-host }
Часть вывода скрипта:
CR241 Activity MA243: Apply Change MA275: RA242 MA277: CreatedWorkItem Антон М. Гриценко AssignedWorkItem RequestedWorkItem RelatesToWorkItem_ IR164 - This is a test RelatesToWorkItem RelatesToConfigItem HasRelatedWorkItems FileAttachment KnowledgeDocument Reason : Notes : ImplementationPlan : RiskAssessmentPlan : BackoutPlan : TestPlan : PostImplementationReview : TemplateId : StandardChangeRequest RequiredByDate : Status : ChangeStatusEnum.InProgress Category : ChangeCategoryEnum.Standard Priority : Impact : Risk : ImplementationResults : Area : Id : CR241 Title : Standard Change Request from incident Description : ContactMethod : CreatedDate : 15.10.2010 8:12:43 ScheduledStartDate : ScheduledEndDate : ActualStartDate : 15.10.2010 8:13:27 ActualEndDate : DisplayName : CR241: Standard Change Request from incident
[…] уже писал ранее о Type Projection, но то была больше теоритическая статья, сегодня я […]
> Скажем, мы хотим получить связанные с инцидентом запросы на обслуживание
вроде же текущая версия SCSM не поддерживает Request Fulfilment?
Да, тут конечно опечатка.
Requst fo Service не то, чтобы не поддерживается. Его просто нет в поставке, сделать его не сложно, на CodePlex есть даже проект на эту тему.
Конечно не сложно, если знаешь как делать :)
с чего стоит начать изучать внутренности SCSM, если нет опыта даже в SCOM? (есть немного в SCCM)
С чтения документации, в том числе блога разработчиков, а также с изучения структуры пакеты управления, она вся в XML и очень похожа на OpsMgr.