EF框架对数据库的连接提供了一系列的默认行为,通常情况下不需要我们太多的关注。但是,这种封装,降低了灵活性,有时我们需要对数据库连接加以控制。
EF提供了两种方案控制数据库连接:
传递到Context的连接;
Database.Connnection.Open();
下面详解。
传递到Context的连接
EF6之前版本
有两个接受Connection的构造方法:
public DbContext(DbConnection existingConnection, boolcontextOwnsConnection)public DbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
使用上面两个方法的时候需要注意的限制:
1、如果使用上面任意一个方法,传递了一个已经打开的连接,则会在首次使用EF操作数据库时抛出一个InvalidOperationException异常,表示不能重复打开一个链接;
2、contextOwnsConnection参数指示在DbContext对象Dispose时候是否Dispose底层的数据库连接。无论参数是否设置,在DbContext.Dispose()时都会关闭底层的Connection。所以,如果你有超过一个DbContext使用同一连接,其中任何一个DbContext对象Dispose时都会关闭该连接(类似的,如果你将ADO.NET 和DbContext对象混合使用,在DbContect对象Dispose时也会关闭连接)。
可以通过传递一个关闭的连接,在创建的上下文中仅打开一次连接,且仅执行代码绕过上面第一条限制。如下:
1 usingSystem.Collections.Generic;2 usingSystem.Data.Common;3 usingSystem.Data.Entity;4 usingSystem.Data.Entity.Infrastructure;5 usingSystem.Data.EntityClient;6 usingSystem.Linq;7
8 namespaceConnectionManagementExamples9 {10 classConnectionManagementExampleEF511 {12 public static voidTwoDbContextsOneConnection()13 {14 using (var context1 = newBloggingContext())15 {16 var conn =
17 ((EntityConnection)18 ((IObjectContextAdapter)context1).ObjectContext.Connection)19 .StoreConnection;20
21 using (var context2 = new BloggingContext(conn, contextOwnsConnection: false))22 {23 context2.Database.ExecuteSqlCommand(24 @"UPDATE Blogs SET Rating = 5" +
25 "WHERE Name LIKE '%Entity Framework%'");26
27 var query = context1.Posts.Where(p => p.Blog.Rating > 5);28 foreach (var post inquery)29 {30 post.Title += "[Cool Blog]";31 }32 context1.SaveChanges();33 }34 }35 }36 }37 }
第二个限制仅仅意味着你要确保确实要关闭连接后再调用DbContext.Dispose()。
EF6版本及更新版本
EF6和将来的版本的DbContext有两个与以往版本相同重载的构造方法,但是不在要求传递一个关闭的连接了。所以下面代码在EF6及以后版本是正确的:
1 usingSystem.Collections.Generic;2 usingSystem.Data.Entity;3 usingSystem.Data.SqlClient;4 usingSystem.Linq;5 usingSystem.Transactions;6
7 namespaceConnectionManagementExamples8 {9 classConnectionManagementExample10 {11 public static voidPassingAnOpenConnection()12 {13 using (var conn = new SqlConnection("{connectionString}"))14 {15 conn.Open();16
17 var sqlCommand = newSqlCommand();18 sqlCommand.Connection =conn;19 sqlCommand.CommandText =
20 @"UPDATE Blogs SET Rating = 5" +
21 "WHERE Name LIKE '%Entity Framework%'";22 sqlCommand.ExecuteNonQuery();23
24 using (var context = new BloggingContext(conn, contextOwnsConnection: false))25 {26 var query = context.Posts.Where(p => p.Blog.Rating > 5);27 foreach (var post inquery)28 {29 post.Title += "[Cool Blog]";30 }31 context.SaveChanges();32 }33
34 var sqlCommand2 = newSqlCommand();35 sqlCommand2.Connection =conn;36 sqlCommand2.CommandText =
37 @"UPDATE Blogs SET Rating = 7" +
38 "WHERE Name LIKE '%Entity Framework Rocks%'";39 sqlCommand2.ExecuteNonQuery();40 }41 }42 }43 }
还有,现在contextOwnsConnection参数决定是否要在DbContext对象Dispose时关闭并Dispose连接对象。因此,上面代码,在DbContext对象Dispose时,数据库并未关闭(32行),但在以前的版本在此处会关闭连接。上面代码会在第40行关闭并释放。
当然,如果你仍然可以让DbContext对象控制连接,把contextOwnsConnection设置为true,或者使用另一个构造方法。
Database.Connnection.Open()
EF6以前的版本
EF5及之前版本ObjectionContext.Connection.State状态更新存在BUG,该值不能正确反映底层连接的状态。例如执行下面代码,获取的状态为Closed,即使其底层确实为Open:
((IObjectContextAdapter)context).ObjectContext.Connection.State
另外,如果你通过调用Database.Connection.Open()打开数据库连接,数据库连接将一直打开,直到执行下次执行一个查询或者任何请求连接的操作(例如SaveChanges)。但是在此之后底层连接将会被关闭。那么Context将会因任意的数据库操作而重新打开连接并在之后重新关闭:
1 usingSystem;2 usingSystem.Data;3 usingSystem.Data.Entity;4 usingSystem.Data.Entity.Infrastructure;5 usingSystem.Data.EntityClient;6
7 namespaceConnectionManagementExamples8 {9 public classDatabaseOpenConnectionBehaviorEF510 {11 public static voidDatabaseOpenConnectionBehavior()12 {13 using (var context = newBloggingContext())14 {15 //At this point the underlying store connection is closed
16
17 context.Database.Connection.Open();18
19 //Now the underlying store connection is open20 //(though ObjectContext.Connection.State will report closed)
21
22 var blog = new Blog { /*Blog’s properties*/};23 context.Blogs.Add(blog);24
25 //The underlying store connection is still open
26
27 context.SaveChanges();28
29 //After SaveChanges() the underlying store connection is closed30 //Each SaveChanges() / query etc now opens and immediately closes31 //the underlying store connection
32
33 blog = new Blog { /*Blog’s properties*/};34 context.Blogs.Add(blog);35 context.SaveChanges();36 }37 }38 }39 }
EF6及将来版本
在EF6和将来的版本中,框架采用的方案是如果代码通过context.Database.Connection.Open()打开一个数据库连接,有理由相信代码想要自己控制连接的打开和关闭,框架将不再自动关闭连接。
注意:这一特性可能造成数据库连接长时间打开,所以要特别留意,防止连接未及时关闭。
EF6中修复了ObjectContext.Connection.State状态更新的BUG。
1 usingSystem;2 usingSystem.Data;3 usingSystem.Data.Entity;4 usingSystem.Data.Entity.Core.EntityClient;5 usingSystem.Data.Entity.Infrastructure;6
7 namespaceConnectionManagementExamples8 {9 internal classDatabaseOpenConnectionBehaviorEF610 {11 public static voidDatabaseOpenConnectionBehavior()12 {13 using (var context = newBloggingContext())14 {15 //At this point the underlying store connection is closed
16
17 context.Database.Connection.Open();18
19 //Now the underlying store connection is open and the20 //ObjectContext.Connection.State correctly reports open too
21
22 var blog = new Blog { /*Blog’s properties*/};23 context.Blogs.Add(blog);24 context.SaveChanges();25
26 //The underlying store connection remains open for the next operation
27
28 blog = new Blog { /*Blog’s properties*/};29 context.Blogs.Add(blog);30 context.SaveChanges();31
32 //The underlying store connection is still open
33
34 } //The context is disposed – so now the underlying store connection is closed
35 }36 }37 }