Changes in Mapping Persistent Classes in Custom Modules with Sitefinity 4.1

Changes in Mapping Persistent Classes in Custom Modules with Sitefinity 4.1

Posted on April 07, 2011 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

UPDATE: After the initial version of this blog post, we introduced some changes in Sitefinity, which required somewhat different code. Some people noticed this in the comments below. The blog post has been updated with code working for Sitefinity 4.1. You can always find a working code sample illustrating the topic in the latest release of the Products module in Sitefinity SDK.

As you know, Sitefinity relies heavily on the OpenAccess ORM for its data layer. We’ve provided an extensible model, in which you can plug a custom implementation for your storage mechanism, but all our modules use OpenAccess by default. That’s why we are excited to announce that we’re shipping a new version of OpenAccess with the 4.1 release.

There will be tons of benefits in this new version that we’ll announce later. For now, we want to bring your attention to a particular change we’re introducing with this upgrade - Fluent mappings for persistent classes. We’ve updated all of our built-in modules to use the new mappings, and all custom modules that use OpenAccess will have to do the same. 

This means anyone who has created a custom Sitefinity 4.x module that relies on OpenAccess will need to make changes to their module before it is compatible with 4.1. 

In this blog post we’ll look at the following topics:

Let's get started...

Understanding the value of fluent mappings

To illustrate the value of fluent mapping support, let's look at how persistent classes were mapped until now. OpenAccess needs metadata to know how to generate tables from your persistent classes. In the current Sitefinity projects, there are two places where it gets the metadata from:

  • The app.config file in your model assembly
  • Attributes you put in the C# code for the persistent classes and their properties

This is cumbersome for two reasons. First, you need to maintain both the code of the persistent class and the app.config file. Second, the only way to provide different mappings for a single class is to duplicate the whole mapping.

Let’s take the sample Products module from the Sitefinity SDK as an example. Its current mappings look like this:

<mappings current="mssqlMapping">
  <mapping id="mssqlMapping">
      <namespace name="ProductCatalogSample.Model">
        <class name="ProductItem">
            <extension key="db-table-name" value="sfex_product_item" />      
                    <field name="permissions">
              <collection>              
                  <extension key="db-link-table" />
              </collection>
            </field>               
                    <field name="urls">
              <collection>
                  <extension key="inverse" value="parent" />
                  <extension key="managed" value="true" />
              </collection>
            </field>
                    <field name="permissionChildren">
              <collection>
                  <extension key="db-link-table" />
              </collection>
            </field>
        </class>
        <class name="ProductItemUrlData">
            <!--<extension key="db-table-name" value="sfex_product_urls" />-->
                    <field name="parent">
              <extension key="db-ref" value="Telerik.Sitefinity.GenericContent.Model.Content.contentId">
                  <extension key="db-column">
                    <extension key="db-column-name" value="content_id" />
                  </extension>
              </extension>
            </field>
        </class>
      </namespace>
  </mapping>
</mappings>

Although the above configuration doesn’t seem like a lot of work, it creates maintenance problems:
  • Every property or setting not explicitly specified in the XML is not present anywhere in your project. It gets the default persistence setting, but if the default changes, your data layer may break.
  • Apart from the XML, we need to put attributes for our properties in the model class itself. This means that there’s no single place where you can get a complete picture of how your class is transformed into a database table.
  • If you want to tweak a mapping for a particular database (say Oracle), you have to duplicate the whole mapping, doubling the XML configuration in size.
  • The mappings of all your persistent classes in the assembly are in the same configuration file. Having more persistent classes dramatically increases the size of this file, making it harder to maintain.

Fluent mappings address all of the above challenges. They provide a single place where all persistent classes are configured. Joshua Holt has created a series of videos introducing Fluent mappings in the OpenAccess ORM. I highly encourage you to watch those before you start changing your modules. Below, we’ll put the same concepts into the Context of a Sitefinity module. 

How to create new fluent mappings for your module

With the newly introduced fluent mappings, all of the above is replaced with code. Instead of relying on a particular XML file, the framework calls a method that you write. You can then supply the mapping and include your own logic depending on the database (e.g. map something differently for Oracle). The steps you need to follow are the following:

Implement the new Context property of the IOpenAccessProvider interface

This property provides runtime state information used by Sitefinity. You just need to have it – nothing fancy in the implementation, except an automatic getter and setter.

public OpenAccessProviderContext Context
{
    get;
    set;
}

Implement the GetMetadataSource method of the IOpenAccessMetadataProvider interface

This method is called automatically and returns a MetadataSource object, containing all information about the mappings in the specific provider. The object will be of the type you implement in the last step. Here just create an instance by passing the context parameter.

public MetadataSource GetMetaDataSource(IDatabaseMappingContext context)
{
    return new ProductsFluentMetadataSource(context);
}

Implement your fluent mapping class and inherit from OpenAccessFluentMappingBase

public class ProductsFluentMapping : OpenAccessFluentMappingBase
{
    public ProductsFluentMapping(IDatabaseMappingContext context)
        : base(context)
    { }
 
    public override IList<MappingConfiguration> GetMapping()
    {
        var mappings = new List<MappingConfiguration>();
        MapItem(mappings);
        MapUrlData(mappings);
        return mappings;
    }
 
    private void MapItem(IList<MappingConfiguration> mappings)
    {
        var itemMapping = new MappingConfiguration<ProductItem>();
        itemMapping.HasProperty(p => p.Id).IsIdentity();
        itemMapping.MapType(p => new { }).ToTable("custom_products");
        itemMapping.HasProperty(p => p.Price);
        itemMapping.HasProperty(p => p.QuantityInStock);
        itemMapping.HasAssociation<Telerik.Sitefinity.Security.Model.Permission>(p => p.Permissions);
        itemMapping.HasProperty(p => p.InheritsPermissions);
        itemMapping.HasAssociation<Telerik.Sitefinity.Security.Model.PermissionsInheritanceMap>(p => p.PermissionChildren);
        itemMapping.HasProperty(p => p.CanInheritPermissions);
        itemMapping.HasAssociation(p => p.Urls).WithOppositeMember("parent","Parent").ToColumn("content_id").IsDependent().IsManaged();
        itemMapping.HasAssociation<Telerik.Sitefinity.Workflow.Model.Tracking.ApprovalTrackingRecordMap>(p => p.ApprovalTrackingRecordMap);
        mappings.Add(itemMapping);
    }
 
    private void MapUrlData(IList<MappingConfiguration> mappings)
    {
        var urlDataMapping = new MappingConfiguration<ProductItemUrlData>();
        urlDataMapping.MapType(p => new { }).Inheritance(InheritanceStrategy.Flat).ToTable("sf_url_data");
        mappings.Add(urlDataMapping);
    }
}

This is the implementation of the mapping itself. All mappings inherit OpenAccessFluentMappingBase and override the GetMapping() method. In the sample Products module, we have separated its work in two calls, one for each persistent class. Everything you need to specify in a mapping has a corresponding method in the MappingConfiguration class. You can specify the name of the table, properties, any associations to other classes, inheritance configurations, etc. When developing a content-based module, they are going to be very similar to the mappings for Products in the above sample.

Implement your specific metadata source and return it in the provider

public class ProductsFluentMetadataSource : ContentBaseMetadataSource
{
    public ProductsFluentMetadataSource():base(null)
    { }
 
    public ProductsFluentMetadataSource(IDatabaseMappingContext context)
        : base(context)
    { }
 
    protected override IList<IOpenAccessFluentMapping> BuildCustomMappings()
    {
        var sitefinityMappings = base.BuildCustomMappings();
        sitefinityMappings.Add(new ProductsFluentMapping(this.Context));
        return sitefinityMappings;
    }
}

The metadata source is an object that you return in the GetMetadataSource method you implemented in the second step above. If you are building a custom module from scratch, you can directly create an instance of any class that derives from MetadataSource. However, when building content-based modules, most of the work has been done for you anyway. We’ve provided a way for you to reuse it – just create your own metadata source and inherit from ContentBaseMetadataSource. Then your only job is to override BuildCustomMappings() and add an instance of the mapping class you created in the previous step to the ones created in the base implementation (Generic Content).

Samples and additional help

Please note that all modules working with OpenAccess need to edit their project files and supply the correct path to the OpenAccess assemblies. For more information on this, refer to our documentation.

The source code for the new Products module mappings is available in the 4.1 SDK. Once you follow the steps above, your module should be ready for Sitefinity 4.1. Have these changes in mind before upgrading your projects to the release coming up next week. We’ll be available to help you if you have questions about the new OpenAccess version. We’re looking forward to seeing how you extend Sitefinity. Happy coding.

progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation