连接管理
本页介绍 Entity Framework 关于将连接传递到上下文方面的行为和 Database.Connection.Open() API 的功能。
将连接传递到上下文
EF5 和早期版本的行为
有两个接受连接的构造函数:
public DbContext(DbConnection existingConnection, bool contextOwnsConnection)
public DbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
可使用这些构造函数,但必须解决一些限制:
- 如果将打开的连接传递给其中一个构造函数,则框架首次尝试使用该连接时会引发 InvalidOperationException,这表示它无法重新打开已打开的连接。
- 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
}
}
}