Поделиться через


Управление подключениями

На этой странице описывается поведение Entity Framework в отношении передачи подключений к контексту и функциональных возможностей API Database.Connection.Open().

Передача соединений в контекст

Поведение в EF5 и более ранних версиях

Существует два конструктора, которые принимают подключения:

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

Их можно использовать, но вам придется обойти несколько ограничений:

  1. Если вы передаете открытое подключение к одному из них, то при первой попытке платформы его использовать, будет вызвано исключение InvalidOperationException с сообщением о том, что невозможно повторно открыть уже открытое соединение.
  2. Флаг contextOwnsConnection интерпретируется как указывающий, следует ли закрывать базовое соединение хранилища при удалении контекста. Но независимо от этого параметра соединение с хранилищем всегда закрывается при освобождении контекста. Таким образом, если у вас есть несколько DbContext с тем же самым подключением, то какой бы контекст ни был удалён первым, соединение будет закрыто. Аналогично, если вы объединили существующее соединение ADO.NET с DbContext, соединение всегда будет закрываться, когда DbContext будет удалён.

Можно обойти первое ограничение, приведенное выше, передав закрытое соединение и только выполнив код, который откроет его после создания всех контекстов:

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();
                }
            }
        }
    }
}

Второе ограничение просто означает, что необходимо воздержаться от удаления любого из объектов DbContext, пока вы не будете готовы к закрытию подключения.

Поведение в EF6 и будущих версиях

В EF6 и будущих версиях DbContext имеет два одинаковых конструктора, но больше не требуется закрывать соединение, переданное конструктору, при его получении. Таким образом, теперь это возможно:

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();
            }
        }
    }
}

Кроме того, флаг contextOwnsConnection теперь определяет, закрывается ли соединение и удаляется при удалении DbContext. Таким образом, в приведенном выше примере подключение не закрывается при удалении контекста (строка 32), как это было бы в предыдущих версиях EF, а закрывается при удалении самого подключения (строка 40).

Конечно, dbContext по-прежнему может контролировать соединение (просто задать contextOwnsConnection значение true или использовать один из других конструкторов), если вы хотите.

Замечание

При использовании транзакций с этой новой моделью существуют некоторые дополнительные рекомендации. Дополнительные сведения см. в разделе "Работа с транзакциями".

Database.Connection.Open()

Поведение для EF5 и более ранних версий

В EF5 и более ранних версиях возникает ошибка, в которой объект ObjectContext.Connection.State не был обновлен, чтобы отразить истинное состояние подключения к базовому хранилищу. Например, если вы выполнили следующий код, можно вернуть состояние "Закрыто", хотя на самом деле подключение к базовому хранилищу открыто.

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

Отдельно при открытии подключения к базе данных путем вызова Database.Connection.Open() он будет открыт до следующего момента выполнения запроса или вызова какого-либо подключения к базе данных (например, SaveChanges()), но после этого подключение к базовому хранилищу будет закрыто. Затем контекст повторно открывается и повторно закрывает подключение в любой момент, когда требуется другая операция базы данных:

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();
            }
        }
    }
}

Поведение в EF6 и будущих версиях

Для EF6 и будущих версий мы приняли следующий подход: если вызывающий код выбирает открыть подключение, вызвав context.Database.Connection.Open(), то у него есть на это веская причина, и платформа будет считать, что он хочет контролировать открытие и закрытие подключения, и больше автоматически не будет закрывать подключение.

Замечание

При неосторожном использовании это может привести к подключениям, которые остаются открытыми в течение длительного времени.

Мы также обновили код, чтобы ObjectContext.Connection.State теперь отслеживает состояние базового подключения.

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
        }
    }
}