Getting Started with Type Projections

A very powerful new concept in the SDK is the notion of Type Projections. I always describe type projections as a view over our type system, much like a SQL view over tables, with the added capability that our type projections are hierarchal. What projections allow you to do is query over and retrieve collections of objects related somehow in our model in one go. In this post I want to provide a very simply example of how to define and retrieve a type projection, and in future posts I will dive much deeper into working with them.

For this post, I've attached a management pack (NFL.xml) that defines the classes and type projection used in the sample code. Also, I have the code I used to populate my objects in the Reference Code section below.

In the management pack you will notice two classes defined, NFL.Conference and NFL.Division, and a hosting relationship type, NFL.ConferenceHostsDivision, between them. My scenario is that I want to display all the conferences and divisions in the UI, so I define a type projection that puts the two classes together:

 <TypeProjection ID="NFL.Conference.All" Accessibility="Public" Type="NFL.Conference">
     <Component Alias="Division" Path="$Target/Path[Relationship='NFL.ConferenceHostsDivision']$" />
</TypeProjection>

The first thing you should notice in the above definition is the Type property on the root element; this defines the type of instance that should exist at the root or "seed" of the projection. Next, you'll notice the projection has one component, Division, that relates to the root. The Path attribute is what defines how to find the desired component, relative to the seed. I will describe the path notation in more depth in future posts, but here Target refers to the seed element of the projection (an instance of NFL.Conference), followed by a Path construct that tells the system to follow the NFL.ConferenceHostsDivision relationship type.  This will result in a structure that looks like this:

* NFL.Conference

* NFL.Division

Now, in order to work with this, the SDK has introduced a new object type, EnterpriseManagementObjectProjection, along with an interface, IComposableProjection, to allow for retrieval of and then navigation of the returned structure. The following code shows a basic example of retrieving and iterating through the objects:

             // Connect to the management group
            EnterpriseManagementGroup managementGroup =
                new EnterpriseManagementGroup("localhost");

            // Get the management pack
            ManagementPack nflManagementPack = 
                managementGroup.ManagementPacks.GetManagementPack("NFL", null, new Version("1.0.0.0"));

            // Get the type projection
            ManagementPackTypeProjection nflProjection = 
                nflManagementPack.GetTypeProjection("NFL.Conference.All");

            // Get the relationship type
            ManagementPackRelationship nflConferenceContainsDivision = 
                nflManagementPack.GetRelationship("NFL.ConferenceHostsDivision");

            IObjectProjectionReader<EnterpriseManagementObject> objectProjections =
                managementGroup.Instances.GetObjectProjectionReader<EnterpriseManagementObject>(
                    new ObjectProjectionCriteria(nflProjection), ObjectQueryOptions.Default);

            foreach (EnterpriseManagementObjectProjection projection in objectProjections)
            {
                Console.WriteLine("Conference {0}", projection.Object.DisplayName);
                foreach (IComposableProjection division in projection[nflConferenceContainsDivision.Target])
                {
                    Console.WriteLine("Division {0}", division.Object.DisplayName);
                }
            }

The sample first connects to the server and retrieves the type projection definition which is necessary for interacting with the SDK and the relationship type definition which is necessary for navigating the resulting instance. Next, we get an IObjectProjectionReader<EnterpriseManagementObject> that will contain all instances of our type projection. Finally, the example shows how to iterate through the object and get data.

Reference Code

             // Connect to the management group
            EnterpriseManagementGroup managementGroup =
                new EnterpriseManagementGroup("localhost");

            // Import the management pack
            ManagementPack managementPack = new ManagementPack("NFL.xml");
            managementGroup.ManagementPacks.ImportManagementPack(managementPack);
            managementPack = managementGroup.ManagementPacks.GetManagementPack(managementPack.Id);

            // Populate Data
            IncrementalDiscoveryData dataTransaction = new IncrementalDiscoveryData();

            // Get System.Entity class
            ManagementPackClass systemEntity = managementGroup.EntityTypes.GetClass(SystemClass.Entity);

            // Conferences
            ManagementPackClass conference = managementPack.GetClass("NFL.Conference");
            CreatableEnterpriseManagementObject nfc = new CreatableEnterpriseManagementObject(managementGroup, conference);
            nfc[conference, "Name"].Value = "NFC";
            nfc[systemEntity, "DisplayName"].Value = "National Football Conference";
            dataTransaction.Add(nfc);

            CreatableEnterpriseManagementObject afc = new CreatableEnterpriseManagementObject(managementGroup, conference);
            afc[conference, "Name"].Value = "AFC";
            afc[systemEntity, "DisplayName"].Value = "American Football Conference";
            dataTransaction.Add(afc);

            // Divisions
            ManagementPackClass division = managementPack.GetClass("NFL.Division");
            string[] nfcDivisionNames = new string[] {
                "NFC East",
                "NFC North",
                "NFC South",
                "NFC West"
            };
            
            foreach (string nfcDivisionName in nfcDivisionNames)
            {
                CreatableEnterpriseManagementObject nfcDivision = new CreatableEnterpriseManagementObject(managementGroup, division);
                
                // Need to make sure to set key values of the host
                nfcDivision[conference, "Name"].Value = "NFC";
                
                nfcDivision[division, "Name"].Value = nfcDivisionName;
                nfcDivision[systemEntity, "DisplayName"].Value = nfcDivisionName;
                dataTransaction.Add(nfcDivision);
            }

            string[] afcDivisionNames = new string[] {
                "AFC East",
                "AFC North",
                "AFC South",
                "AFC West"
            };

            foreach (string afcDivisionName in afcDivisionNames)
            {
                CreatableEnterpriseManagementObject afcDivision = new CreatableEnterpriseManagementObject(managementGroup, division);

                // Need to make sure to set key values of the host
                afcDivision[conference, "Name"].Value = "AFC";

                afcDivision[division, "Name"].Value = afcDivisionName;
                afcDivision[systemEntity, "DisplayName"].Value = afcDivisionName;
                dataTransaction.Add(afcDivision);
            }

            // Use Overwrite instead of Commit here because I want to bypass optimistic concurrency checks which will not allow the 
            // insertion of an existing instances and I want this method to be re-runnable
            dataTransaction.Overwrite(managementGroup);

NFL.xml