连接管理

本页介绍 Entity Framework 关于将连接传递到上下文方面的行为和 Database.Connection.Open() API 的功能

将连接传递到上下文

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 和更早版本中,存在一个 bug,即 ObjectContext.Connection.State 未更新,无法反映底层存储连接的真实状态。 例如,如果执行了以下代码,可能会返回“已关闭”状态,即使基础存储连接实际上是“打开”状态

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

另外,如果通过调用 Database.Connection.Open() 打开数据库连接,它将一直打开,直到下次执行查询或调用任何需要数据库连接的内容(例如 aveChanges())为止,但之后基础存储连接将关闭。 然后,当需要其他数据库操作时,上下文将重新打开和重新关闭连接:

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