DbContext Class

Description Article
Table of Contents
Summary

The DbContext is the primary class that is responsible for interacting with data as objects (entity objects).

The context class manages the entity objects during run time, which includes populating objects with data from a database, change tracking, and persisting data to the database.

Description

Overview

A context class derives from DbContext and exposes DbSet properties that represent collections of the specified entities in the context:

If working with the Model First and Database First aproaches (i.e. using the EF Designer), the context will be automatically generated. If working with Code First, the context class is created manually.

How to Track Changes and Submit to Database

Entity Framework keeps track of the changes made to entity objects. For this it uses the change tracker. It can be accessed through the DbContext.ChangeTracker property.

Change tracking is used to:

  1. Update the state of every entity object so that changes can be persisted to the database; and
  2. Performs relationship fix-up when combination of reference navigation properties, collection navigation properties and foreign keys exists
Refer to Change Tracker: Overview for details of the change tracker.
The method DbContext.SaveChanges saves all changes made in this context to the underlying database.
Flush Mode

Nhibernate’s Session object, by default, will automatically persists all changes made to entities to the database before executing any “SELECT” query, ensuring consistency between the persisted entities and their in-memory state within the context of a session (Session.FlushMode is set to AutoFlush).

Entity Framework’s DbContext object has a contrary behavior: By default, the SaveChanges() method always initiate and completes an internal transaction, and changed data is never automatically flushed to the database before a new query.

Operations
Creating the DbContext class

In case of Code First model approach refer to this article for details about configuration of entities using Fluent API in the OnModelCreating() method.

In case of Code First model approach refer to this article for details about database initialization options on the constructor of the DbContext class.

How to do it
public class BreakAwayContext: DbContext
    {
        public BreakAwayContext(): base("name=BreakWayDatabase")
        {
           //Configure here Databse initialization if Code First model
           //...
        }

        public DbSet<Person> People { get; set; }
        public DbSet<Destination> Destinations { get; set; }
        public DbSet<Trip> Trips { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Configure here domain classes using modelBuilder and Fluent API if Code First:
            base.OnModelCreating(modelBuilder);
            //...
        }
    }
How to use it

A DbContext class is responsible for

  1. Managing and tracking changes to instances of the classes in the model; and
  2. Managing the connection to the database.

Therefore when used  it must be correctly disposed in order to release any used resources: the using pattern must be used.

using (var context = new BreakAwayContext())
{
    var query= from d in context.Destinations
                orderby d.Name
                select d;

    return query.ToList();
}
Combining several operations into one transaction within the same context

A new transaction is created through the DbContext.Database.BeginTransaction() method where can explicitly specified the IsolationLevel.

A DbContextTransaction object is returned which provides Commit() and Rollback() methods which perform commit and rollback on the underlying transaction.

Remarks

The DbContextTransaction is meant to be disposed once it has been committed or rolled back.

Therefore the using(…) {…} syntax, which will automatically call Dispose() when the using block completes, is advised to be used.

How to do it
using (var ctx = new BreakAwayContext())
{
  using (var trn = ctx.Database.BeginTransaction())
  {
    try
    {
      Post lvPost = new Post()
                      {
                        PostId = 101,
                        Content = "This is a test post",
                        Title = "This is a test post"
                      };

      ctx.Posts.Add(lvPost);

      ctx.SaveChanges();

      Post lvPost2 = new Post()
                      {
                        PostId = 10q,
                        Content = "This is a test post 2",
                        Title = "This is a test post 2"
                      };

      ctx.Posts.Add(lvPost2);

      ctx.SaveChanges();

      trn.Commit();
      Console.WriteLine("Data Saved Successfully. Transaction Commited");
    }
    catch
    {
      trn.Rollback();
      Console.WriteLine("Error Occured during data saved. Transaction Rolled Back");
    }
}
Re-using an existing transaction to the context

If a transaction already exists which includes operations in the same database but outside of Entity Framework, this must be passed to the context using the Database.Database.UseTransaction() method.

To make this çpossible a constructor must be defined on the context class which inherits from one of the DbContext constructors parameterized by:

  1. An existing connection parameter;
  2. The contextOwnsConnection as boolean.

Remarks

The contextOwnsConnection flag must be set to false when called in this scenario, in order to inform Entity Framework that it should not close the connection when terminating.

How to do it
using (var lvConnection = new SqlConnection("..."))
{
  lvConnection.Open(); 

  using (var lvTransation = lvConnection.BeginTransaction(System.Data.IsolationLevel.Snapshot))
  {
    try
    {
      var lvCommand = new SqlCommand();
      lvCommand.Connection = lvConnection;
      lvCommand.Transaction = lvTransation;
      lvCommand.CommandText =
                           @"UPDATE Blogs SET Rating = 5" + " WHERE Name LIKE '%Entity Framework%'";
      lvCommand.ExecuteNonQuery(); 

      using (var ctx = new BreakAwayContext(lvConnection, contextOwnsConnection: false))
      {
        ctx.Database.UseTransaction(lvTransaction); 

        var query =  ctx.Posts.Where(p => p.Blog.Rating >= 5); 

        foreach (var lvPost in query)
        {
          lvPost.Title += "[Cool Blog]";
        } 

        ctx.SaveChanges();
      } 

      lvTransaction.Commit();
    }
    catch (Exception)
    {
      lvTransaction.Rollback();
    }
  }
}
Using distributed transactions
Remarks

Limitations:

  • Requires .NET 4.5.1 or greater to work with asynchronous methods
  • It cannot be used in cloud scenarios unless one and only one connection exists (cloud scenarios do not support distributed transactions)
  • It cannot be combined with the DbContext.Database.UseTransaction() method
  • It will throw exceptions if any DDL is issued (e.g. execution of a Database Initializer) and distributed transactions have not been enabled through the MSDTC Service.

Advantages:

  • It will automatically upgrade a local transaction to a distributed transaction if more than one connection to a given database exists or is combined with a connection to a different database within the same transaction (note: you the MSDTC service configured to allow distributed transactions must be enabled)
  • Easear to codify
How to do it

Using synchronous transaction:

using (var lvScope = new TransactionScope(TransactionScopeOption.Required))
{
  using (var lvConnection = new SqlConnection("..."))
  {
    lvConnection.Open(); 

    var lvCommand = new SqlCommand();
    lvCommand.Connection = conn;
    lvCommand.CommandText =
                        @"UPDATE Blogs SET Rating = 5" + " WHERE Name LIKE '%Entity Framework%'";
    lvCommand.ExecuteNonQuery(); 

    using (var ctx = new BreakawayContext(conn, contextOwnsConnection: false))
    {
      var query = context.Posts.Where(p => p.Blog.Rating > 5); 

      foreach (var lvPost in query)
      {
        lvPost.Title += "[Cool Blog]";
      } 

      ctx.SaveChanges();
    }
  } 

  lvScope.Complete();
}

Using asynchronous transaction:

using (var lvscope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
  using (var lvConnection = new SqlConnection("..."))
  {
    await lvConnection.OpenAsync(); 

    var lvCommand = new SqlCommand();
    lvCommand.Connection = lvConnection;
    lvCommand.CommandText =
                        @"UPDATE Blogs SET Rating = 5" + " WHERE Name LIKE '%Entity Framework%'";
    await lvCommand.ExecuteNonQueryAsync(); 

    using (var ctx = new BreakAwayContext(conn, contextOwnsConnection: false))
    {
      var query = ctx.Posts.Where(p => p.Blog.Rating > 5); 

      foreach (var lvPost in query)
      {
        lvPost.Title += "[Cool Blog]";
      } 

      await ctx.SaveChangesAsync();
    }
  }
}

 

Create a DbSet<TEntity> at runtime

The method DbContext.Set<TEntity> can be used to create at runtime a set of a specific entity type.

How to do it
...
context.Set<TEntity>().Add(obj);
...
Implement custom actions on entities just retrieved from the database (i.e. after the materialize process)

This can be done by subscribing / listening to the ObjectMaterialized event on the underlying ObjectContext.

Remarks

The System.Data.Entity.Infrastructure is required.

How to use it
public BreakAwayContext()
{
  ((IObjectContextAdapter)this).ObjectContext
    .ObjectMaterialized += (sender, args) =>
      {
        // do someting
        //...
      };
}
How to log generated SQL

The DbContext.Database.Log property can be set to a delegate for any method that takes a string.

The SQL string can be sent to any TextWriter by setting it to the TextWriter.Write method.

Remarks

Refer to this article for details about logging generated SQL on Entity Framework.

How to do it

Option 1: Log SQL to output console:

using (var context = new BreakAwayContext())
{
    context.Database.Log = Console.Write;

    //...
}

Option 2: Log  SQL to a custom logger method:

public class LogManager
{
    public static void Log(string source, string message)
    {
        Console.WriteLine("source: {0} Message: {1} ", component, message);
    }
}
using (var context = new BreakAwayContext())
{
    context.Database.Log = s => LogManager.log("EF", s);

    //...
}

Sample SQL:

SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title]
FROM [dbo].[Blogs] AS [Extent1]
WHERE (N'One Unicorn' = [Extent1].[Title]) AND ([Extent1].[Title] IS NOT NULL)
-- Executing at 5/13/2013 10:19:04 AM
-- Completed in 4 ms with result: SqlDataReader

SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[BlogId] AS [BlogId]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[BlogId] = @EntityKeyValue1
-- EntityKeyValue1: '1' (Type = Int32)
-- Executing at 5/13/2013 10:19:04 AM
-- Completed in 2 ms with result: SqlDataReader

update [dbo].[Posts]
set [Title] = @0
where ([Id] = @1)
-- @0: 'Green Eggs and Ham' (Type = String, Size = -1)
-- @1: '1' (Type = Int32)
-- Executing asynchronously at 5/13/2013 10:19:04 AM
-- Completed in 11 ms with result: 1

insert [dbo].[Posts]([Title], [BlogId])
values (@0, @1)
select [Id]
from [dbo].[Posts]
where @@rowcount > 0 and [Id] = scope_identity()
-- @0: 'I do not like them!' (Type = String, Size = -1)
-- @1: '1' (Type = Int32)
-- Executing asynchronously at 5/13/2013 10:19:04 AM
-- Completed in 2 ms with result: SqlDataReader
References

[1] Refer to Execute Business Logic When Saving Changes article to samples about how to override DbContext.SaveChanges method.

[2] Refer to Using Entity Framework 4.1 DbContext Change Tracking for Audit Logging article to samples about how to override DbContext.SaveChanges method.