Dev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Data Model

Describes how to help developers acclimate to the new data layer in Optimizely Configured Commerce.

Optimizely has moved from NHibernate to Entity Framework in the 4.2 release of Configured Commerce. This topic covers a variety of topics to help developers acclimate to the new data layer.

Work with models/ORM (EF)

The data layer supporting Configured Commerce has undergone a major shift from 4.1 to 4.2. There are two major topics to discuss when addressing this:

  1. Changes to the technology stack, which swaps out NHibernate for Entity Framework.
  2. Changes in coding standards to encourage a "thin" data layer, or a data layer whose entities have little to no business logic.

Skinny data model

Whereas the previous versions of Configured Commerce had business logic encapsulated in methods and computed properties of data layer objects, 4.2 seeks to host all of that logic in the service layer, leaving the entities as simple as possible. This eliminates complexity in the data layer and allows for better control and extensibility throughout the platform. We encourage this paradigm be followed as the platform is extended.

Entity framework

Entity Framework is the default ORM for Configured Commerce. Although a great deal of the complexity of this switch should be abstracted from the application developer, there are a few differences in the development flows that will need to be considered. The bulk of this document will be focused on those.

Insite.Data

The Insite.Data namespace is the root namespace for entity related components in the Configured Commerce framework. Models have moved from Insite.Model to Insite.Data.Entities, and many other data related components have made similar moves to this namespace. Insite.Data is the main dependency for all data layer related logic.

Basic usage

Unit of work and repositories

If you are familiar with previous versions of Configured Commerce, then most of your data use cases will not need any altering. The recommended way to access data is still to obtain a UnitOfWork object, get the appropriate Repository<T> object, and use that repository to access the entities.

Retrieve Entities

Get an entity by Id

To get an object by its primary key, simply call the Get method on the corresponding repository.

Get a list of entities

To retrieve a list of entities, call the GetTable method, which returns an IQueryable<T> that can be filtered (where), sorted (orderby) or mapped (select).

Also, if you would rather write your own optimized sql for retrieving the entities, you can do so and call GetList.

📘

Note

This will return the values immediately. This method should be used with care and only when necessary.

Retrieve related data

Lazy loading

By default, lazy loading is enabled and can be used to retrieve entities and collections of entities related to your entity in the database. A separate sql call will be made once the collection is accessed in the code to retrieve these entities. In the example below, there are two sql calls made, one for the Get and one when Job.CustomProperties is accessed.

Eager loading

Although lazy loading is great for a number of use cases, it is sometimes advantageous to load collections ahead of time if you know that you are planning to access them. This optimizes the process by making only one SQL call.

In our last example, we always access the CustomProperties collection, so instead of lazy loading that collection, we can eagerly load it via the Include method provided by Insite.Data.Extensions.

📘

Note

You must return an IQueryable to use this method.

public ICollection<CustomProperty> GetJobCustomProperties(Guid jobId)
{
    var job = this.unitOfWork.GetRepository<Job>().GetTableAsNoTracking()
        .Where(j => j.Id == jobId)
        .Include(j => j.CustomProperties)
        .FirstOrDefault();
    return job.CustomProperties;
}

Update entities

Create an entity

Creating an entity consists of constructing a new instance of the desired entity type, and calling the Insert method on the corresponding repository.

📘

Note

To actually save the entity to the database, you will also need to call either the Save or SaveAsync method on the UnitOfWork. That will be covered in the next SubTopic.

Update an entity

Updating an attached entity requires an update to the instance, followed by a call to the Save method on the UnitOfWork instance.

You can also perform a Save asynchronously by calling SaveAsync on the UnitOfWork instance.

📘

Note

You do not have to call Save or SaveAsync immediately after updating the entity

Delete an entity

Deleting a job requires you invoke the Delete method on the Repository, followed by Save or SaveAsync on the UnitOfWork.

📘

Note

You do not have to call Save or SaveAsync immediately after updating the entity

Extend the data model

Alter the data model

Best practices

The officially supported method of changing the data model is to add entities to the model. Removing Configured Commerce tables or extending existing tables is not supported.

Create an entity class

To add to the data model, you should first create a POCO (Plain Old CLR Object) that extends from Insite.Data.Entities.EntityBase. For this example we have created the Job class.

Convention and annotations

In the previous image, you will notice that a Table attribute/annotation was added to this class. This is superfluous as convention assumes the name of the class. A list of common annotations for EF can be found here

https://msdn.microsoft.com/en-us/data/jj591583.aspx

Add properties

Once your class is created, you can begin to add properties. Properties can be of varying types, including other entities, but must have "virtual" as part of their signatures. Keeping in mind the principle of a "thin" data layer, it is best practice to leave out computed properties or methods. Our example adds the following:

Map the entity

Once you have created your entity class, you must create a mapping class. This class defines any custom relationships that are outside of standard entity framework conventions. Since our example follows convention, we don't require any logic. Still, we need to create the class, and it must extend from Insite.Data.Providers.EntityFramework.EntityMappings.EntityBaseTypeConfiguration<T> in order for your entity to be bootstrapped to the context correctly.

Custom mappings

Primarily, you will only need to add custom mapping logic to your mapping class if there is a many-to-many relationship between your entity and another. If this is the case, then you can follow standard entity framework guidance on creating this mapping.

Alter the database schema

Migrations

Migrations are not currently supported as of Configured Commerce 4.2. Any changes made to the database schema need to be done via SQL files embedded in your custom dll's.

Create a script

Creating a script consists of adding a new sql file to your Visual Studio Project. The file should have the format of <year>.<month>.<day>.<increment>.<description>.sql. These can be added anywhere in the project's folder structure.

Include a script

Including a script into the build consists of setting the Build Action property of the file to Embedded Resource and the Copy to Output Directory property to Do not copy.

Run a Sql script

Sql scripts that are embedded resources are run automatically during the bootstrap operation. If the sql script executes successfully, it is added to the DatabaseScript table in the database. This is done for auditing purposes, and to ensure that a script is not run twice. You can query this table to determine if your script has been run.

Performance considerations

Retrieve records

WhereContains

There is a known issue using Linq with Entity Framework in conjunction with ICollection<T>.Contains() from within a Where clause. An example of this would be as follows:

Using this approach can severely degrade the performance of your application. To alleviate this issue, Configured Commerce has provided an extension method, WhereContains, which gives the same result in a much more performant manner. The same method as above should be refactored to the following:

WithNoTracking

Another optimization that can be made is to work with a set of data that does not have change tracking enabled. This improves read performance and can also improve the performance for batch operations.

Entity Framework accomplishes this via the AsNoTracking extension method, found here.

To limit dependencies on Entity Framework, Configured Commerce exposes the same functionality on all of their repository instances. To get a collection without tracking, invoke the GetTableAsNoTracking method on the Repository. An example would look like the following:

public ICollection<CustomProperty> GetJobCustomProperties(Guid jobId)
{
    var job = this.unitOfWork.GetRepository<Job>().GetTableAsNoTracking()
        .Where(j => j.Id == jobId)
        .Include(j => j.CustomProperties)
        .FirstOrDefault();
    return job.CustomProperties;
}

Typical usage would be scenarios where no updates to the entity are expected, or optimized sections where ObjectState and attachment to the context will be actively managed.