Pre-Generated Views With a Code First Model Entity Framework 6.0

Introduction

Entity Framework generates a set of mapping views to access the database before executing any queries or save changes to the data source. These mapping views are a set of E-SQL (Entity SQL) statements that represent the database in an abstract manner. Mapping views are cached per application domain. It means that if we create multiple instances of the same context in the same application domain, EF reuses the mapping views from the cached metadata. Entity Framework generates the mapping view per application domain when executing the first query and it will take a significant part of the time to execute. Entity Framework enables us to pre-generate mapping views and include them in a compiled project.

Generating Mapping views using the EF Power tool

Entity Framework Power Tool is one of the easiest ways to pre-generate a view. Once we have installed the EF Power Tool, we may be able to see the menu option to generate the view on a right-click of the DbContext class (Code First) / Edmx file (Database First).

Entity Framework Power Tools download link: Entity Framework Power Tools Beta 4.

Generate view

Once the process is finished, we will have the following class generated.

 Process

From now on when the application is run, Entity Framework will use this pre-generated whatever to load the views that are required. This class must be re-generated whenever any changes are made to the model otherwise Ef will throw an exception.

Generating Mapping views from Code (EF6 or later version)

Entity Framework version 6 has introduced an API to generate a view. When we use this method we have the freedom to serialize the view but we need to load the views by ourselves.

The System.Data.Entity.Core.Mapping.StorageMappingItemCollection class represents a collection of items in Storage Mapping. The ObjectContext has all the information about the storage mapping collection and we can retrieve it using the metadata workspace of ObjectContext. Generally, the developer uses the DbContext class to access the database and the DbContext class implements the IObjectContextAdapter interface to provide access to the underlying ObjectContext.

 Code

By using the following code we can get a storage mapping collection.

var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace
    .GetItemCollection(DataSpace.CSSpace);

Once we have the Storage Mapping Item collection, we can access the methods of GenerateViews and ComputeMappingHashValue of the StorageMappingItemCollection class.

The GenerateViews method creates a dictionary of entities for each view in the container mapping and the ComputeMappingHashValue method computes the hash value for a single container mapping and it is very useful at runtime to identify whether there is no change in the model after generating views.

The DbMappingViewCache class has one property, MappingHashValue, and one method, GetView. MappingHashValue must return as generated by the ComputeMappingHashValue method. When the Entity Framework loads the view, it will first compare the hash value of the model and the hash returned by this property. If they do not match then the Entity Framework throws an exception type of EntityCommandCompilationException.

The GetView method accepts an EntitySetBase and returns a DbMappingVIew containing the Entity SQL that was generated for that that was associated with the given EntitySetBase in the dictionary generated by the GenerateViews method. The GetView method returns null when we do not have the required view.

public class MyMappingViewCache : DbMappingViewCache
{
    public override string MappingHashValue
    {
        get
        {
            return "df5a56995eb89ee1d0f7cd1c0bd989a32f6f6c500f524f64aba8f87706ab51ab";
        }
    }

    public override DbMappingView GetView(EntitySetBase extent)
    {
        if (extent == null)
        {
            throw new ArgumentNullException("extent");
        }

        var extentName = extent.EntityContainer.Name + "." + extent.Name;

        if (extentName == "CodeFirstDatabase.Employee")
        {
            return GetView0();
        }

        if (extentName == "Model.Employees")
        {
            return GetView1();
        }

        return null;
    }

    private static DbMappingView GetView0()
    {
        return new DbMappingView(@"
            SELECT VALUE --Constructing Employee [CodeFirstDatabaseSchema.Employee]
                T1.Employee_Id, T1.Employee_Code, T1.Employee_Name
            FROM
            (
                SELECT
                    T.Id AS Employee_Id,
                    T.Code AS Employee_Code,
                    T.Name AS Employee_Name,
                    True AS _from0
                FROM Model.Employees AS T
            ) AS T1");
    }

    private static DbMappingView GetView1()
    {
        return new DbMappingView(@"
            SELECT VALUE -- Constructing Employees [PreGeneratedView.Model.Employee]
                T1.Employee_Id, T1.Employee_Code, T1.Employee_Name
            FROM
            (
                SELECT
                    T.Id AS Employee_Id,
                    T.Code AS Employee_Code,
                    T.Name AS Employee_Name,
                    True AS _from0
                FROM CodeFirstDatabase.Employee AS T
            ) AS T1");
    }
}

Summary

Using the method described previously we can create a pre-generated view and using the pre-generated view we can improve the performance of the Entity Framework (first-time query execution).