June 21st, 2010

Productivity Improvements for the Entity Framework

Background

We’ve been hearing a lot of good feedback on the recently released update to the Entity Framework in .NET 4. This release marks a significant advancement from the first release that shipped with .NET 3.5 SP1.  I’m not going to spend time here talking about what’s new, but you can check here to see for yourself. 


With all that said, there are still a number of things we can do to simplify the process of writing data access code with the Entity Framework. We’ve been paying attention to the most common patterns that we see developers using with the EF and have been brewing up a set of improvements to the Entity Framework designed to allow developers to accomplish the same tasks with less code and fewer concepts.


These improvements provide a cleaner and simpler API surface that focuses your attention on the most common scenarios but still allows you to drill down to more advanced functionality when it’s needed.  We hope you will enjoy this simpler experience, but we should be quick to assure you that this is NOT a new data access technology.  These improvements are built on the same technology for mapping, LINQ, providers and every other part of the Entity Framework.  Think of this as a fast path to writing data access code using conventions over configuration, better tuned APIs and other techniques intended to reduce development time when using the EF.


At this stage we have worked through what we think the core API and functionality should look like and would like your feedback. There are still some capabilities such as data binding and concurrency resolution which will cause the API to evolve as we continue the design process, but the main concepts are in place, so it is a good time for feedback.

Introducing DbContext & DbSet

At the heart of the Entity Framework Productivity Improvements are two new types, DbContext and DbSet(Of TEntity). DbContext is a simplified alternative to ObjectContext and is the primary object for interacting with a database using a specific model. DbSet(Of TEntity) is a simplified alternative to ObjectSet(Of TEntity) and is used to perform CRUD operations against a specific type from the model. These new types can be used regardless of whether you created your model using the Entity Designer or code.

The obvious question is ‘Why not just simplify ObjectContext and ObjectSet(Of T)?’  We are opting to introduce these new types in order to, on the one hand, preserve full backward compatibility with existing EF applications and continue to address all of the advanced scenarios that are possible given the EF’s existing flexibility, while on the other hand streamlining the experience of using the EF and tuning it for the most common cases.  We believe it is critical that the EF programming experience improve in some fundamental ways, and at the same time we are absolutely committed to our existing customers.  Establishing a collaborative relationship between the existing types and the new types allows us to achieve both requirements.  Also, there are easy ways to get to ObjectContext and ObjectSet(Of T) from DbContext and DbSet in case you want more control for a particular task.

 

One point we want to be very clear about is that these new types are not replacing any existing types; they are a simplified alternative that build on the existing types, and as we add features to the Entity Framework they will always be available in ObjectContext/ObjectSet, and they will also be available in DbContext/DbSet if appropriate.

 


We’ll drill into the API surface later, but first let’s take a look at the coding experience using these new types.

Code First Experience

DbContext provides a simplified Code First pattern that requires less code and takes care of some common concerns such as model caching, database provisioning, schema creation and connection management. This simplified pattern uses a number of conventions to take care of these tasks and allows tweaking or overriding of this behavior when required. Let’s start off by using these conventions to write the code needed to build a console application that performs data access using a model:

A C# version of these code samples is available here.

 

Imports System.Collections.Generic

Imports System.Data.Entity

 

Namespace MyDataAccessDemo

 

    Module Program

 

        Sub Main()

 

            Using context As New ProductContext()

 

                Dim food As New Category With {.CategoryId = “FOOD”}

                context.Categories.Add(food)

 

                Dim cheese As New Product With {.Name = “Cheese”}

                cheese.Category = context.Categories.Find(“FOOD”)

 

                context.Products.Add(cheese)

                context.SaveChanges()

            End Using

 

        End Sub

 

    End Module

 

    Public Class ProductContext : Inherits DbContext

 

        Public Property Products As DbSet(Of Product)

        Public Property Categories As DbSet(Of Category)

 

    End Class

 

 

    Public Class Product

 

        Public Property ProductId As Integer

        Public Property Name As String

        Public Property Category As Category

 

    End Class

 

    Public Class Category

 

        Public Property CategoryId As String

        Public Property Name As String

        Public Property Products As ICollection(Of Product)

 

    End Class

End Namespace

 

 

That is 100% of the code you would write to get this program running. No separate model definition, XML metadata, config file or anything else is required. Conventions are used to fill in all of this information. Obviously there is quite a bit going on under the covers so let’s take a closer look at some of the things DbContext is doing automatically.

Model Discovery

During construction we scan the derived context for DbSet properties and include the types in the model. Model Discovery uses the existing Code First functionality so the new default conventions we recently blogged about are processed during discovery. You can opt out of set discovery by specifying an attribute on the set properties that should be ignored.


Of course there will be times when you want to further describe a model or change what was detected by convention. For example say you have a Book entity whose ISBN property is the primary key, this won’t be detected by convention. There are two options here; you can use data annotations to annotate the property in your class definition:

    Public Class Book

 

        <Key()>

        Public Property ISBN As String

        Public Property Title As String

 

    End Class

 

 

Alternatively DbContext includes a virtual method that can be overridden to use the Code First fluent API on ModelBuilder to further configure the model:

 

    Public Class ProductContext : Inherits DbContext

 

        Public Property Books As DbSet(Of Book)

 

        Protected Overrides Sub OnModelCreating(ByVal modelBuilder As Microsoft.Data.Objects.ModelBuilder)

            modelBuilder.Entity(Of Book)().HasKey(Function(b) b.ISBN)

        End Sub

 

    End Class

Model Caching

There is some cost involved in discovering the model, processing Data Annotations and applying fluent API configuration. To avoid incurring this cost every time a derived DbContext is instantiated the model is cached during the first initialization. The cached model is then re-used each time the same derived context is constructed in the same AppDomain. Model caching can be turned off by setting the CacheForContextType property on ModelBuilder to ‘false’ in the OnModelCreating method.

DbSet Initialization

You’ll notice in the sample that we didn’t assign a value to either of the DbSet properties on the derived context. During construction DbContext will scan the derived context for DbSet properties and, if they expose a public setter, will construct a new DbSet and assign it to the property. You can opt out of set initialization by specifying an attribute on the set properties that should not be initialized.

You can also create DbSet instances using the DbContext.Set(Of TEntity)() method if you don’t want to expose public setters for the DbSet properties.

Database Provisioning

By default the database is created and provisioned using SqlClient against localhostSQLEXPRESS and has the same name as the derived context. This convention is configurable and is controlled by an AppDomain setting that can be tweaked or replaced. You can tweak the default SqlClient convention to connect to a different database, replace it with a SqlCe convention that we include or define your own convention by implementing the IDbConnectionFactory interface.

 

 

    Public Interface IDbConnectionFactory

 

        Function CreateConnection(ByVal databaseName As String) As DbConnection

 

    End Interface

 

The active IDbConnectionFactory can be retrieved or set via the static property, Database.DefaultConnectionFactory.

 

DbContext also includes a constructor that accepts a string to control the value that is passed to the convention, the SqlClient and SqlCE factories allow you to specify either a database name or the entire connection string.


Before calling the convention, DbContext will check in the app/web.config file for a connection string with the same name as your context (or the string value if you used the constructor that specifies a string). If there is a matching entry, we will use that rather than calling the convention. Because connection string entries also include provider information this allows you to target multiple providers in one application.

Finally, if you want full control over your connections there is a constructor on DbContext that accepts a DbConnection.

Database First and Model First Experience

In the latest version of the Entity Framework that shipped with .Net Framework 4.0 and Visual Studio 2010 we introduced T4 based code generation. T4 allows you to customize the code that is generated based on a model you have defined using the designer in either the Database First or Model First approach. The default template generates a derived ObjectContext with an ObjectSet(Of T) for each entity set in your model.

The productivity improvements will also include a template that generates a derived DbContext with a DbSet(O f TEntity) for each entity set in your model. This allows Model First and Database First developers to make use of the simplified API surface described in the next section.

API Surface

Author

0 comments