In this part of the series I dive into details about Entities and what you, the module developer, must know when integrating taxonomy into your custom module. If you have not yet reviewed Part 1 and Part 2 of the series, please take the time to do so before reading this blog entry. Something to touch on prior to beginning is that for this blog entry I will be referring to an entity called Entry. My Entry entity is in a class named EntryInfo and I will likely refer to this throughout this blog entry and future blog entries in this series. As mentioned throughout the series, I am assuming you already have a working custom module of your own which contains your own “EntryInfo” equivalent.
Entities
Although most of you are probably familiar with entities, please allow me to take a moment to explain exactly what I mean here (because I could easily mean one of so many different things). When I reference entities, or its singular form entity, I am simply referring to what many DotNetNuke module developers would equate to an ‘Info’ object (or multiple Info objects when referencing the plural entities). Recently, in any modules I have been working on that are new projects I have been creating a separate folder within my components folder named “Entities”. In this folder, I keep my class files that represent my ‘Info’ objects (Ex. MyModule\Components\Entities\MyEntityInfo.cs). Simple modules may have a single entity (or possibly none), while a complex module could have a very high number of entities (Ex. A simple article module may have only a single entity: one called Article; A forum module should have at least four: Group, Forum, Thread, and Post; The Core Forum module, on the other hand, actually has at least twenty I could think of.). Please do keep in mind that an entity should be named in its singular form since it represents a single instance of the object and typically a single row of data from the data store (Ex. Article and NOT Articles). Now, let’s dive into details about our example entity, Entry.
Entry is a single class (as discussed earlier) that contains multiple properties, located in an EntryInfo named file (which resides in MyModule\Components\Entities\EntryInfo.cs. Also note, I named my class EntryInfo too). The majority of the properties in this class correspond to columns within a table (for the sake of this discussion, let’s say my table is named MyCompany_ModuleName_Entry).
Sample MyCompany_ModuleName_Entry Table:
1: EntryId [INT]
2: MyContent [NVARCHAR](MAX)
3: ModuleId [INT] -- FK to Modules table.
4: ContentItemID [INT] -- FK to ContentItems table.
5: CreatedByUserId [INT]
6: CreatedOnDate [DATETIME]
7: LastModifiedByUserId [INT]
8: LastModifiedOnDate [DATETIME]
It is possible that, perhaps, you are not referencing a table and are using a view instead in your module to retrieve your entity (this is completely acceptable but for simplicity I will stick to referencing the table). Also worth mentioning is that, as the snippet says, the ModuleId and ContentItemID columns are foreign keys to the Modules and ContentItems tables. Having said that, let’s take a moment to look at EntryInfo.cs in greater detail.
Sample EntryInfo Class:
1: public class EntryInfo
2: {
3:
4: #region Public Properties
5:
6: public int EntryId { get; set; }
7:
8: public string MyContent { get; set; }
9:
10: public int ModuleId { get; set; }
11:
12: public int CreatedByUserId { get; set; }
13:
14: public DateTime CreatedOnDate { get; set; }
15:
16: public int LastModifiedByUserId { get; set; }
17:
18: public DateTime LastModifiedOnDate { get; set; }
19:
20: //Not stored as columns
21: public int TotalRecords { get; set; }
22:
23: #endregion
24:
25: }
This sample shouldn’t contain any surprises, so I will skip going line for line here. One line worth mentioning, however, is line 21. Many of you are may already be doing this, or something similar, in your own module but in case some of the readers have not let’s spend a moment discussing it. As you can probably tell, based on the comment in line 20, this is a property which is not stored as a column. In my retrieval stored procedures that return multiple records (or instances of an entity), I return TotalRecords as a total count of items for use where paging is utilized (ie. datagrids, datalist, etc.). Although I don’t want to spend time on further details regarding paging, if you are using any of the core data grids you will definitely need this or an equivalent.
Implementation
Now that we have covered the basic EntityInfo class, we should get into the details of what you need to add to your own entity to utilize the core taxonomy. First, you need to have your class inherit from the core ContentItem class (under the DotNetNuke.Entities.Content namespace). The reason for this is that it will give us access to all of our ContenItem properties (which are stored in the ContentItems table of the Core). In doing this, there are a few things you should be made aware of. The first is that we are inheriting from a class that implements IHydratable. Because of this you must be careful with your column/property names. For example, if you have a property/column with the name of Content it could potentially conflict with the core Content Item’s Content property/column (we will get to why in just a minute). The second reason is that you will need to override a method in your EntryInfo class: Fill, which also calls FillInternal. To understand better, let’s take a look at the Fill method.
Sample EntryInfo’s Fill Method:
1: public override void Fill(System.Data.IDataReader dr) {
2: //Call the base classes fill method to populate base class properties
3: base.FillInternal(dr);
4:
5: EntryId = Null.SetNullInteger(dr["EntryId"]);
6: MyContent = Null.SetNullString(dr["MyContent"]);
7: ModuleId = Null.SetNullInteger(dr["ModuleId"]);
8: CreatedByUserId = Null.SetNullInteger(dr["CreatedByUserId"]);
9: CreatedOnDate = Null.SetNullDateTime(dr["CreatedOnDate"]);
10: LastModifiedByUserId = Null.SetNullInteger(dr["LastModifiedByUserId"]);
11: LastModifiedOnDate = Null.SetNullDateTime(dr["LastModifiedOnDate"]);
12: TotalRecords = Null.SetNullInteger(dr["TotalRecords"]);
13: }
Line 3 calls the base classes FillInternal method (the base class being ContentItem). When you call it, and in turn pass it a data reader, this is where the conflict I mentioned before can occur (as the FillInternal method is populating the ContentItem object). If we use our example and renamed “MyContent” to “Content”, there would be a conflict here (the FillInternal and the Fill methods would populate the two items named “Content” with the same data). While this may not be a problem in many cases, it is certainly something to be aware of (As you can see, I left my audit columns named the same because I was not worried about the conflict). This also means that when you return entities using your stored procedure, you need to make sure you use an INNER JOIN on the ContentItems table (via ContentItemID, which is why you need to add this column to your own table as noted in the table columns we previously outlined in the code snippet above). Please also keep in mind that you can only return a single instance of the same name regardless of table prefix naming (meaning, you cannot return our example’s Content.Content value as well as MyCompany_ModuleName_Entry.Content in the stored procedure) otherwise you will get an error. We will get into more details of the stored procedure, including an example, in Part 4. Also, in case you are not already aware of it, you will need to utilize the DotNetNuke.Common.Utilities namespace for using the Null.x methods. One final item worth mentioning about this snippet is that when using the Fill method only items that are properly filled (meaning, you set the property within the method and also return it in your stored procedure) will be available in other classes within your module (technically, the properties will be available but they will not be populated and you will see blank values in the user interface).
The last consideration with regards to your IHydratable implementation is that you should consider overriding the KeyID property too, as shown in the code snippet below where I set it’s value equal to the primary key value used in my example table, EntryId.
Sample EntryInfo’s KeyID Property:
1: public override int KeyID {
2: get { return EntryId; }
3: set { EntryId = value; }
4: }
As per Charles, the KeyID property was initially added to handle FillDictionary in the CBO. As the name implies, this KeyID property is used as the key in a Dictionary. As time went on, an overload method was added to the CBO to create any Dictionary type. Now, I have seen some questions in the past about cases where a Key is a non-integer value. In which case, you can either exclude it or utilize one of the other methods. However you implement is up to you and your situation but I will continue to use this for my example throughout the series (Not trying to spend too much time on IHydratable here).
So, to bring it all together, you simply need to inherit from the ContentItem class in your entity. Then, you need to override the Fill method (which also calls the base classes FillInternal) and the KeyID property (if applicable) in addition to updating your retrieval stored procedures (to make sure you are getting valid data for the Content Item) and adding the ContentItemID column to your own entity table. In the next section, Part 4, we will be focusing on the DAL which should finally start bringing everything together.