In my previous post discussing properties on custom content, I delved into some of the more complex types that can be properties, including classifications and related media. This post will get into the most complex type to work with: Related Data. This is taking existing custom content types and allowing other content types to have a property that relates one to the other. Relating data can happen with built-in content types (e.g. News), extend across other module builder modules, and even be self-referential! On top of all that, you can even create a custom content type that is the parent or child of another custom content type (within the same module). It is a very powerful feature that Sitefinity offers, and can make the translation from business logic to real Sitefinity code and content an easy step. Let’s get to it.
Querying Related Data
In actuality, if you’ve been following my posts, you already know how to fetch related data! If you recall, back in my other post we went over how to fetch Related Media (Images, Documents/Files, and Videos). The syntax and methods used there are pretty much what we use here. The only difference is the type we pass to GetRelatedItems(): DynamicContent.
In the following example, I created a new custom content type in the PressReleases module, called “Company.” The Company type has two properties: Title and PressReleases, the latter of which is of type Related Data, and the data type is PressRelease. GetRelatedItems() returns the Live version of the related data (the same is true when fetching Related Media). And since these objects are just more DynamicContent objects, you can utilize the same GetValue() methods to fetch specific properties of the related data.
Type type = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.Company"); DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(); IQueryable<DynamicContent> companies = dynamicModuleManager.GetDataItems(type) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible); foreach (DynamicContent company in companies) { // Fetch related data just like related media, only passing DynamicContent to GetRelatedItems. IQueryable<DynamicContent> pressReleases = company.GetRelatedItems<DynamicContent>("PressReleases"); // We can now iterate over the company's collection of Press Releases // and use them like any other DynamicContent object. foreach (DynamicContent pressRelease in pressReleases) { string pressReleaseTitle = pressRelease.GetString("Title"); } }
Filtering by Related Data
Let’s say you wanted to find all the Companies that have a specified Press Release related to them. One might first try starting with getting companies from GetDataItems(companyType), then using Where() to select and filter by the Press Releases returned from GetRelatedItems. That doesn’t work, unfortunately, due to the nature of how GetRelatedItems works, and due to how the LINQ translates back into SQL (or whatever your backing data store might be). Worry not, however, for there is a solution! You just have to work from the other direction. Rather than starting with a collection of Company objects and filtering them, instead start with the Press Release you want to filter by, and fetch all Companies that are related to it.
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(); // Can get an ID by first querying Press Releases based on some search criteria (e.g. a matching Title). Guid livePressReleaseId = new Guid("c60d2d01-53ee-6d8d-a049-ff0000513041"); Type pressReleaseType = TypeResolutionService .ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.PressRelease"); DynamicContent pressRelease = dynamicModuleManager.GetDataItem(pressReleaseType, livePressReleaseId); // From the Press Release, get any Companies that have it selected as a Related Data item. Type companyType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.Company"); const string relatedDataFieldName = "PressReleases"; // The property name storing Related Data on the Company type. const string providerName = null; // Unless you have custom providers defined, null can be passed in. IQueryable<DynamicContent> companies = pressRelease .GetRelatedParentItems(companyType.FullName, providerName, relatedDataFieldName) .OfType<DynamicContent>() .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible);
First we acquire the Press Release DynamicContent object that we want to filter by. Above I just did a query ahead of time and hardcoded a GUID representing a Live, Visible Press Release in my database. In your code you can get it dynamically using the same fetching/filtering techniques I’ve demonstrated in previous posts. From there, we call the key method on the DynamicContent object: GetRelatedParentItems. In this case, the method is taking in the type of the parent (Company), the provider name (you can always pass null here if you haven’t made any custom providers), and the related data field name on Company (“PressReleases”). We then strongly type it using OfType, and proceed to use the same Where() clause we’ve been using to only get Live, Visible Companies.
We have to use OfType<>() because the generic GetRelatedParentItems<>() method does not work for this scenario! But with we’ve written above, we can fetch any collection of Companies filtered by a particular Press Release. It is not the most intuitive, but it is at least possible to do using the built-in Sitefinity methods. And if you bookmark this blog post, you will never forget.
All Kinds of Data to Relate
In the above examples we’ve been using the simplest Related Data case: Two custom content types within the same module builder module. Related Data, however, can expand beyond that simple scenario. When setting up your custom content type, Sitefinity provides a drop down list of all the possible options for data to related to. In addition to other types within the same module, you can select:
- Other custom content types in other installed/activated modules within the Module Builder.
- Built-in content types within Sitefinity, such as News and Blog Posts
- The custom content type you are currently editing. Note, however, that this won’t be an option when initially creating the type, because the type has to exist before it appears in this list.
When working with related data concerning other custom content types, the code is exactly the same as above. When working with built-in types, though, you don’t use DynamicContent as the object to work with. Sitefinity has defined model classes (think domain objects) for their built-in types that you use instead. These have advantages over DynamicContent since Sitefinity can specify strongly-typed properties specific to a given type. This can’t be done, naturally, with dynamically-created DynamicContent types.
I have added a “MyNews” Related Data property to the Company content type. Below is a simple example of accessing this property, and each related news item’s properties:
Type companyType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.Company"); IQueryable<DynamicContent> companies = dynamicModuleManager.GetDataItems(companyType) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible); foreach (DynamicContent company in companies) { // Telerik.Sitefinity.News.Model.NewsItem (same namespace as Image, etc). IQueryable<NewsItem> news = company.GetRelatedItems<NewsItem>("MyNews"); }
Parent/Child Custom Content Types
The final subject we’re touching on is that of parent and children custom content types. When relating data between two types, it often makes more sense to make one the parent of another, instead of simply having a related data property on the type. In this case, I created a “Division” custom content type, that is a child of Company. Unlike Related Data, you can only make a new content type a child of a content type that exists within the same module. You can’t make child content types for built-in types, cross-module types, and (of course) itself. In this case, Division is a child of Company, and Division has just one field for simplicity: Title.
Pitfalls to Avoid
Despite the name of the method we used in the previous section, we will not be using “GetRelatedParentItems” here. That method only functions with Related Data, not when relating data in a parent/child scenario. In addition, there are other “gotcha” properties on DynamicContent that look to be entirely relevant, but are in fact never set by Sitefinity. “SystemHasChildItems” is a boolean, and “SystemChildItems” is a collection of objects. They will never be set and are always false and null, respectively. According to documentation, these properties exist to “improve performance”, for you (the developer) to assign manually so that you don’t call the real data-access methods multiple times. When trying to initially hydrate your data or check to see if your item has children, avoid these!
The Real Methods, With One More Caveat
In actuality, you have to use the DynamicModuleManager to gain access to a parent’s children. While the above pitfalls can snag you and make you fall into a trap, the fact that Sitefinity does not load children by default makes sense: If you’re working with 1,000 Companies and you only care about Divisions when drilling down into a Company’s details, there’s no reason to go and fetch all 1,000 Companies’ Divisions! Instead, Sitefinity lets you make the call there. The DynamicModuleManager has two methods that are very similar to the named properties above: HasChildItems() and GetChildItems(). Both take in a DynamicContent and will return whether or not it has children (the former) and the children itself (the latter).
One last thing to watch out for: Unlike all of the previous data-fetching methods we’ve worked with, this one (GetChildItems()) does not give you the Live version of the data. It returns the Master version of the data instead. In earlier versions of Sitefinity, GetRelatedItems() did the same thing, but Sitefinity changed the behavior. For GetChildItems(), however, we still have to make an additional step in order to retrieve the Live versions of our Divisions.
Time for the Code
Below is a fully-functional example of iterating over a Company’s Divisions, first checking if any exist, and then accessing a Division’s Title property.
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(); Type companyType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.Company"); IQueryable<DynamicContent> companies = dynamicModuleManager.GetDataItems(companyType) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible); Type divisionType = TypeResolutionService.ResolveType("Telerik.Sitefinity.DynamicTypes.Model.PressReleases.Division"); foreach (DynamicContent company in companies) { bool hasDivisions = dynamicModuleManager.HasChildItems(company); if (!hasDivisions) { continue; } IQueryable<Guid> masterDivisionIds = dynamicModuleManager.GetChildItems(company).Select(c => c.Id); IQueryable<DynamicContent> liveDivisions = dynamicModuleManager.GetDataItems(divisionType) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible) .Where(d => masterDivisionIds.Contains(d.OriginalContentId)); foreach (DynamicContent division in liveDivisions) { // division.SystemParentItem contains the master version of the parent Company DynamicContent object. string divisionTitle = division.GetString("Title"); } }
Here we are applying the described steps in order to fetch a Company’s Divisions. For each Company, we check to see if it has Children. If so, we get them via the provided GetChildItems() method on dynamicModuleManager, selecting their IDs. With those IDs, we then query Sitefinity via the dynamicModuleManager again, this time passing in the Division type, and filtering down to Live, Visible items whose OriginalContentId is in our collection of Master IDs. From there we have access to the Live Divisions of the given Company.
Sitefinity bridges the parent/child gap the other direction as well. As you can see in the inner foreach comment, the Division itself exposes the parent, via the SystemParentItem property (and unlike the other two “System” properties that are never set by Sitefinity, this one actually is set by Sitefinity). But just like with GetChildItems(), it exposes details about the Master version, not the Live version. If you have a child and want its Live parent, you have to do the same extra step of getting the Live version by OriginalContentId.
End of the Road
These posts going over the properties for custom content types are certainly not an exhaustive coverage of every possible type of property you can work with, but it does hit on all the most-used ones, simple and complex. I covered Related Data properties and Parent/Children types in this post, Classifications and Related Media in my previous post, and simple types (strings and numbers) in an older post. We’ve covered a lot of ground here! With these teachings at your disposal, you will walk away a stronger Sitefinity developer when it comes to developing with the Module Builder.
The post Relating Data in Sitefinity Content appeared first on Falafel Software Blog.