Administración de conexiones

En esta página se describe el comportamiento de Entity Framework con respecto a pasar conexiones al contexto y a la funcionalidad de la API Database.Connection.Open().

Pasar conexiones al contexto

Comportamiento de EF5 y versiones anteriores

Hay dos constructores que aceptan conexiones:

public DbContext(DbConnection existingConnection, bool contextOwnsConnection)
public DbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)

Es posible usar estos, pero tiene que solucionar un par de limitaciones:

  1. Si pasa una conexión abierta a cualquiera de estas dos, la primera vez que el marco intenta usarlo, se produce una excepción InvalidOperationException diciendo que no puede volver a abrir una conexión abierta.
  2. La marca contextOwnsConnection se interpreta para indicar si se debe eliminar o no la conexión del almacén subyacente cuando se elimina el contexto. Pero, independientemente de esa configuración, la conexión de almacén siempre se cierra cuando se elimina el contexto. Por lo tanto, si tiene más de un DbContext con la misma conexión que el contexto eliminado primero cerrará la conexión (de forma similar si ha mezclado una conexión de ADO.NET existente con dbContext, DbContext siempre cerrará la conexión cuando se elimine).

Es posible solucionar la primera limitación anterior pasando una conexión cerrada y ejecutando solo el código que lo abriría una vez creados todos los contextos:

using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.EntityClient;
using System.Linq;

namespace ConnectionManagementExamples
{
    class ConnectionManagementExampleEF5
    {         
        public static void TwoDbContextsOneConnection()
        {
            using (var context1 = new BloggingContext())
            {
                var conn =
                    ((EntityConnection)  
                        ((IObjectContextAdapter)context1).ObjectContext.Connection)  
                            .StoreConnection;

                using (var context2 = new BloggingContext(conn, contextOwnsConnection: false))
                {
                    context2.Database.ExecuteSqlCommand(
                        @"UPDATE Blogs SET Rating = 5" +
                        " WHERE Name LIKE '%Entity Framework%'");

                    var query = context1.Posts.Where(p => p.Blog.Rating > 5);
                    foreach (var post in query)
                    {
                        post.Title += "[Cool Blog]";
                    }
                    context1.SaveChanges();
                }
            }
        }
    }
}

La segunda limitación solo significa que debe evitar la eliminación de cualquiera de los objetos DbContext hasta que esté listo para que se cierre la conexión.

Comportamiento en EF6 y versiones futuras

En EF6 y versiones futuras, DbContext tiene los mismos dos constructores, pero ya no requiere que la conexión pasada al constructor se cierre cuando se reciba. Por lo tanto, esto ahora es posible:

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;

namespace ConnectionManagementExamples
{
    class ConnectionManagementExample
    {
        public static void PassingAnOpenConnection()
        {
            using (var conn = new SqlConnection("{connectionString}"))
            {
                conn.Open();

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

                using (var context = new BloggingContext(conn, contextOwnsConnection: false))
                {
                    var query = context.Posts.Where(p => p.Blog.Rating > 5);
                    foreach (var post in query)
                    {
                        post.Title += "[Cool Blog]";
                    }
                    context.SaveChanges();
                }

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

Además, la marca contextOwnsConnection controla ahora si la conexión está cerrada y desechada cuando se elimina DbContext. Por lo tanto, en el ejemplo anterior, la conexión no se cierra cuando se elimina el contexto (línea 32), como lo habría hecho en versiones anteriores de EF, sino cuando se elimina la propia conexión (línea 40).

Por supuesto, todavía es posible que DbContext tome el control de la conexión (simplemente establezca contextOwnsConnection en true o use uno de los otros constructores) si así lo desea.

Nota:

Hay algunas consideraciones adicionales al usar transacciones con este nuevo modelo. Para obtener más información, consulte Trabajar con transacciones.

Database.Connection.Open()

Comportamiento de EF5 y versiones anteriores

En EF5 y versiones anteriores hay un error de modo que ObjectContext.Connection.State no se actualizó para reflejar el estado verdadero de la conexión del almacén subyacente. Por ejemplo, si ejecutó el código siguiente, puede devolver el estado Cerrado aunque, de hecho, la conexión del almacén subyacente sea Abierta.

((IObjectContextAdapter)context).ObjectContext.Connection.State

Por separado, si abre la conexión de base de datos llamando a Database.Connection.Open(), se abrirá hasta la próxima vez que ejecute una consulta o llame a cualquier cosa que requiera una conexión de base de datos (por ejemplo, SaveChanges()), pero después de que se cierre la conexión del almacén subyacente. A continuación, el contexto volverá a abrir y cerrará la conexión cada vez que se requiera otra operación de base de datos:

using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.EntityClient;

namespace ConnectionManagementExamples
{
    public class DatabaseOpenConnectionBehaviorEF5
    {
        public static void DatabaseOpenConnectionBehavior()
        {
            using (var context = new BloggingContext())
            {
                // At this point the underlying store connection is closed

                context.Database.Connection.Open();

                // Now the underlying store connection is open  
                // (though ObjectContext.Connection.State will report closed)

                var blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);

                // The underlying store connection is still open  

                context.SaveChanges();

                // After SaveChanges() the underlying store connection is closed  
                // Each SaveChanges() / query etc now opens and immediately closes
                // the underlying store connection

                blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);
                context.SaveChanges();
            }
        }
    }
}

Comportamiento en EF6 y versiones futuras

Para EF6 y versiones futuras, hemos adoptado el enfoque que si el código de llamada elige abrir la conexión llamando al contexto. Database.Connection.Open() tiene una buena razón para hacerlo y el marco asume que quiere controlar la apertura y cierre de la conexión y ya no cerrará la conexión automáticamente.

Nota:

Esto puede provocar conexiones que están abiertas durante mucho tiempo, por lo que se usan con cuidado.

También hemos actualizado el código para que ObjectContext.Connection.State realice un seguimiento del estado de la conexión subyacente correctamente.

using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Infrastructure;

namespace ConnectionManagementExamples
{
    internal class DatabaseOpenConnectionBehaviorEF6
    {
        public static void DatabaseOpenConnectionBehavior()
        {
            using (var context = new BloggingContext())
            {
                // At this point the underlying store connection is closed

                context.Database.Connection.Open();

                // Now the underlying store connection is open and the
                // ObjectContext.Connection.State correctly reports open too

                var blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);
                context.SaveChanges();

                // The underlying store connection remains open for the next operation  

                blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);
                context.SaveChanges();

                // The underlying store connection is still open

           } // The context is disposed – so now the underlying store connection is closed
        }
    }
}