More with Type Projections

In my last post I introduced you to a new concept in the Service Manager infrastructure called Type Projections. Here, I want to expand on my previous example and discuss type projection structure in more depth. I've attached the management pack that I will be discussing (NFL.xml) and there is reference code at the bottom of this post for populating sample data.

Type projection definitions allow users to create virtual object collections through any relationships found in the system. In the previous post I showed how a projection could bring hosted and host objects together in one go. The following projection expands on that example by introducing System.Containment and System.Reference relationships, as well as introducing the concepts of type constraints and relationship direction:

       <TypeProjections>
        <TypeProjection ID="NFL.Conference.All" Accessibility="Public" Type="NFL.Conference">
          <Component Alias="Division" Path="$Target/Path[Relationship='NFL.ConferenceHostsDivision']$">
            <Component Alias="AFCTeam" Path="$Target/Path[Relationship='NFL.DivisionContainsTeam' SeedRole='Source' TypeConstraint='AFC.Team']$"/>
            <Component Alias="NFCTeam" Path="$Target/Path[Relationship='NFL.DivisionContainsTeam' SeedRole='Source' TypeConstraint='NFC.Team']$">
              <Component Alias="Coach" Path="$Target/Path[Relationship='NFL.CoachCoachesTeam' SeedRole='Target']$" />
            </Component>
          </Component>
        </TypeProjection>
      </TypeProjections>

Added to the Division component from the previous post, I've now introduced two components that bring in all the teams in the NFL. I've defined my NFL.Team class as abstract and created concrete classes, AFC.Team and NFC.Team. I've also created a relationship derived from System.Containment associating an NFL.Division to an NFL.Team. Of note is that this relationship ends with an abstract class as the target endpoint. While this is supported by type projection definitions, the projection I have defined actually demonstrates the ability to constraint a component to a particular class. The two components, AFCTeam and NFCTeam each share a relationship, however, they each add a different type constraint. Given something can't be an NFCTeam and AFCTeam at the same time, these components will introduce unique sets of data.

If I were to change the AFCTeam component to remove the type constraint, that component would return all NFL.Team instances while the NFCTeam component would return only those NFL.Team objects that are NFC.Teams as well.

Next, I added a System.Reference relationship between NFL.Coach and NFL.Team. Notice that the coach is the source of that relationship. If I want to include all coaches for NFC.Teams, I need to indicate in the type projection definition that I want to traverse the relationship from target to source. This is done via the SeedRole attribute. The "Seed" refers to the parent component of the current component; the attribute describes which role the seed should play relative to the relationship used in the current component. In the sample, the relationship is NFL.CoachCoachesTeam and the seed (NFCTeam) is the target of that relationship and hence SeedRole='Target'.

Traversing this structure is similar to the example in my previous post:

             // 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 types
            ManagementPackRelationship nflConferenceContainsDivision =
                nflManagementPack.GetRelationship("NFL.ConferenceHostsDivision");

            ManagementPackRelationship divisionContainTeam =
                nflManagementPack.GetRelationship("NFL.DivisionContainsTeam");

            ManagementPackRelationship coachCoachesTeam =
                nflManagementPack.GetRelationship("NFL.CoachCoachesTeam");

            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("\tDivision: {0}", division.Object.DisplayName);
                    foreach (IComposableProjection team in division[divisionContainTeam.Target])
                    {
                        Console.WriteLine("\t\tTeam: {0}", team.Object.DisplayName);
                        foreach (IComposableProjection coach in team[coachCoachesTeam.Source])
                        {
                            Console.WriteLine("\t\t\tCoach: {0}", coach.Object.DisplayName);
                        }
                    }
                }
            }

When we traverse from team to coach, we use the .Source property of the relationship which corresponds to the SeedRole description in the type projection definition. As part of traversing the projection, think of the role as where you are traversing to; in this example you are traversing from the team (the .Target property) to the coach (the .Source property).

When traversing from divisions to teams, I am using the abstract .Target property of the NFL.DivisionContainsTeam relationship. Type constraints are not expressed in the traversal mechanism for these objects; the key from going to one component to another is the relationship endpoint, save any type constraints.

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
            Dictionary<string, List<string>> divisions = new Dictionary<string,List<string>>();
            divisions.Add("AFC North", new List<string>());
            divisions["AFC North"].Add("Baltimore Ravens");
            divisions["AFC North"].Add("Cincinnati Bengals");
            divisions["AFC North"].Add("Cleveland Browns");
            divisions["AFC North"].Add("Pittsburgh Steelers");
            divisions.Add("NFC North", new List<string>());
            divisions["NFC North"].Add("Chicago Bears");
            divisions["NFC North"].Add("Detroit Lions");
            divisions["NFC North"].Add("Green Bay Packers");
            divisions["NFC North"].Add("Minnesota Vikings");
            
            divisions.Add("AFC South", new List<string>());
            divisions["AFC South"].Add("Houston Texans");
            divisions["AFC South"].Add("Indianapolis Colts");
            divisions["AFC South"].Add("Jacksonville Jaguars");
            divisions["AFC South"].Add("Tennessee Titans");
            divisions.Add("NFC South", new List<string>());
            divisions["NFC South"].Add("Atlanta Falcons");
            divisions["NFC South"].Add("Carolina Panthers");
            divisions["NFC South"].Add("New Orleans Saints");
            divisions["NFC South"].Add("Tampa Bay Buccaneers");

            divisions.Add("AFC East", new List<string>());
            divisions["AFC East"].Add("Buffalo Bills");
            divisions["AFC East"].Add("Miami Dolphins");
            divisions["AFC East"].Add("New England Patriots");
            divisions["AFC East"].Add("New York Jets");
            divisions.Add("NFC East", new List<string>());
            divisions["NFC East"].Add("Dallas Cowboys");
            divisions["NFC East"].Add("New York Giants");
            divisions["NFC East"].Add("Philadelphia Eagles");
            divisions["NFC East"].Add("Washington Redskins");

            divisions.Add("AFC West", new List<string>());
            divisions["AFC West"].Add("Denver Broncos");
            divisions["AFC West"].Add("Kansas City Chiefs");
            divisions["AFC West"].Add("Oakland Raiders");
            divisions["AFC West"].Add("San Diego Chargers");
            divisions.Add("NFC West", new List<string>());
            divisions["NFC West"].Add("Arizona Cardinals");
            divisions["NFC West"].Add("San Francisco 49ers");
            divisions["NFC West"].Add("Seattle Seahawks");
            divisions["NFC West"].Add("St. Louis Rams");

            Dictionary<string, string> teamToCoach = new Dictionary<string, string>();
            teamToCoach.Add("Chicago Bears", "Lovie Smith");

            ManagementPackClass divisionClass = managementPack.GetClass("NFL.Division");
            ManagementPackClass nfcTeamClass = managementPack.GetClass("NFC.Team");
            ManagementPackClass afcTeamClass = managementPack.GetClass("AFC.Team");
            ManagementPackClass coachClass = managementPack.GetClass("NFL.Coach");

            ManagementPackRelationship divisionContainsTeamClass = managementPack.GetRelationship("NFL.DivisionContainsTeam");
            ManagementPackRelationship coachCoachesTeamClass = managementPack.GetRelationship("NFL.CoachCoachesTeam");
            foreach (string divisionName in divisions.Keys)
            {
                CreatableEnterpriseManagementObject division = new CreatableEnterpriseManagementObject(managementGroup, divisionClass);

                string conferenceName;
                ManagementPackClass teamClass;

                if (divisionName.Contains("AFC") == true)
                {
                    conferenceName = "AFC";
                    teamClass = afcTeamClass;
                }
                else
                {
                    conferenceName = "NFC";
                    teamClass = nfcTeamClass;
                }

                // Need to make sure to set key values of the host
                division[conference, "Name"].Value = conferenceName;
                division[divisionClass, "Name"].Value = divisionName;
                division[systemEntity, "DisplayName"].Value = divisionName;
                dataTransaction.Add(division);
                foreach (string teamName in divisions[divisionName])
                {
                    CreatableEnterpriseManagementObject team = new CreatableEnterpriseManagementObject(managementGroup, teamClass);
                    team[teamClass, "Name"].Value = teamName;
                    team[systemEntity, "DisplayName"].Value = teamName;
                    dataTransaction.Add(team);

                    CreatableEnterpriseManagementRelationshipObject divisionContainsTeam = 
                               new CreatableEnterpriseManagementRelationshipObject(managementGroup, divisionContainsTeamClass);
                    divisionContainsTeam.SetSource(division);
                    divisionContainsTeam.SetTarget(team);
                    dataTransaction.Add(divisionContainsTeam);

                    if (teamToCoach.ContainsKey(teamName) == true)
                    {
                        CreatableEnterpriseManagementObject coach = new CreatableEnterpriseManagementObject(managementGroup, coachClass);
                        coach[coachClass, "Name"].Value = teamToCoach[teamName];
                        coach[systemEntity, "DisplayName"].Value = teamToCoach[teamName];
                        dataTransaction.Add(coach);

                        CreatableEnterpriseManagementRelationshipObject coachCoachesTeam = 
                                      new CreatableEnterpriseManagementRelationshipObject(managementGroup, coachCoachesTeamClass);
                        coachCoachesTeam.SetSource(coach);
                        coachCoachesTeam.SetTarget(team);
                        dataTransaction.Add(coachCoachesTeam);
                    }
                }
            }

            // 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