---
title: Work with the data model and Entity Framework ORM
slug: configured-commerce/docs/working-with-the-data-model-and-the-entity-framework-orm
docTags: 
createdAt: 2026-08-13T10:46:17.000Z
---

Optimizely uses Entity Framework for Configured Commerce. The following topics help you acclimate to the new data layer:

- Goals and best practices
- Basic usage
- Extensibility
- Performance

## Goals and best practices

The goals of the Configured Commerce data layer are:

1. Use the Entity Framework.
2. Encourage a "thin" data layer, or a data layer whose entities have little to no business logic.

### Entity Framework

Entity Framework (EF) is open source and part of the .NET framework. EF is the default object relational mapper (ORM) for Configured Commerce. This type of tool simplifies mapping between objects in software to the tables and columns of a relational database.

### Skinny data model

We want to host all business 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 you to follow this paradigm as you extend the platform.

### Insite.Data

The `Insite.Data` namespace is the root namespace for entity-related components in the Configured Commerce framework. The models are `Insite.Data.Entities` and many other data-related components are included this namespace. Insite.Data is the main dependency for all data layer-related logic.

## Basic usage

### Unit of work and repositories

The recommended way to access data is to obtain a `UnitOfWork` object, get the appropriate `Repository<T>` object, and use that repository to access the entities.

### Retrieving entities

To get an entity by Id, get the object by its primary key by calling the **Get** method on the corresponding repository.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/9QYhVxw5gBQTXzUcjwNAH-20260722-174956.png "Working_with_Models_10.png")

### 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).

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/N43elEzvtBMmKbEdxdy5j-20260722-174956.png "Working_with_Models_1.png")

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

:::hint{type="info"}
**📘 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**.

:::hint{type="info"}
**📘 Note**
You must return an **IQueryable** to use this method.
:::

### 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.

:::hint{type="info"}
**📘 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.

:::hint{type="info"}
**📘 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**.

:::hint{type="info"}
**📘 Note**
You do not have to call **Save** or **SaveAsync** immediately after updating the entity.
:::

### Transactions and atomicity

A single **Save** or **SaveAsync** call is atomic on every framework. However, the way multiple saves are grouped into a transaction changed between .NET Framework 4.8 and .NET 8.0+. .NET 4.8 automatically wrapped each request's handler chain in one transaction, but .NET 8.0+ does not. If you need several saves to commit or roll back together, manage the transaction explicitly with **BeginTransaction**, **CommitTransaction**, and **RollbackTransaction**.

See [Transactions and the unit of work](docId\:p3YkdL3n05GsjwOJy-DoY) for the full explanation and the recommended pattern.

## Extend the data model

### Alter the data model

The officially supported best practice and method for changing the data model is to add entities to the model. Removing Optimizely tables is not supported. See [Creating Custom Tables with an Entity and WebApi](docId\:W2aUj3IY8aIJHyDLvtTZN) for details about table extension.

### 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.

In the previous image, 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 in [Code First Data Annotations](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:

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/0T4psFeC-SoVWDib9bMsy-20260722-174956.png "Working_with_Models_10.png")

### 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>` for your entity to be bootstrapped to the context correctly.

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. 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**.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/_zFEJ6PY543RMrecXUDWv-20260722-174956.png "Working_with_Models_13.png")

**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.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/NQwtDfKcOVRoLH27PRFCS_c9d8b6b-working-with-models-14.png)

## Performance considerations

### Retrieve records

`WithNoTracking`

Another optimization that can be made is to work with a set of data that doesn't 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 in [DbExtensions.AsNoTracking Method](https://msdn.microsoft.com/en-us/library/gg679352%28v=vs.103%29.aspx).

To limit dependencies on Entity Framework, Optimizely 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`. For example:

```csharp
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.
