EF框架對資料庫的連接提供了一系列的預設行為,通常情況下不需要我們太多的關註。但是,這種封裝,降低了靈活性,有時我們需要對資料庫連接加以控制。 EF提供了兩種方案控制資料庫連接: 傳遞到Context的連接; Database.Connnection.Open(); 下麵詳解。 傳遞到Context ...
EF框架對資料庫的連接提供了一系列的預設行為,通常情況下不需要我們太多的關註。但是,這種封裝,降低了靈活性,有時我們需要對資料庫連接加以控制。
EF提供了兩種方案控制資料庫連接:
- 傳遞到Context的連接;
- Database.Connnection.Open();
下麵詳解。
傳遞到Context的連接
EF6之前版本
有兩個接受Connection的構造方法:
public DbContext(DbConnection existingConnection, bool contextOwnsConnection) 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 using System.Collections.Generic; 2 using System.Data.Common; 3 using System.Data.Entity; 4 using System.Data.Entity.Infrastructure; 5 using System.Data.EntityClient; 6 using System.Linq; 7 8 namespace ConnectionManagementExamples 9 { 10 class ConnectionManagementExampleEF5 11 { 12 public static void TwoDbContextsOneConnection() 13 { 14 using (var context1 = new BloggingContext()) 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 in query) 29 { 30 post.Title += "[Cool Blog]"; 31 } 32 context1.SaveChanges(); 33 } 34 } 35 } 36 } 37 }
第二個限制僅僅意味著你要確保確實要關閉連接後再調用DbContext.Dispose()。
EF6版本及更新版本
EF6和將來的版本的DbContext有兩個與以往版本相同重載的構造方法,但是不在要求傳遞一個關閉的連接了。所以下麵代碼在EF6及以後版本是正確的:
1 using System.Collections.Generic; 2 using System.Data.Entity; 3 using System.Data.SqlClient; 4 using System.Linq; 5 using System.Transactions; 6 7 namespace ConnectionManagementExamples 8 { 9 class ConnectionManagementExample 10 { 11 public static void PassingAnOpenConnection() 12 { 13 using (var conn = new SqlConnection("{connectionString}")) 14 { 15 conn.Open(); 16 17 var sqlCommand = new SqlCommand(); 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 in query) 28 { 29 post.Title += "[Cool Blog]"; 30 } 31 context.SaveChanges(); 32 } 33 34 var sqlCommand2 = new SqlCommand(); 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 using System; 2 using System.Data; 3 using System.Data.Entity; 4 using System.Data.Entity.Infrastructure; 5 using System.Data.EntityClient; 6 7 namespace ConnectionManagementExamples 8 { 9 public class DatabaseOpenConnectionBehaviorEF5 10 { 11 public static void DatabaseOpenConnectionBehavior() 12 { 13 using (var context = new BloggingContext()) 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 20 // (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 closed 30 // Each SaveChanges() / query etc now opens and immediately closes 31 // 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 using System; 2 using System.Data; 3 using System.Data.Entity; 4 using System.Data.Entity.Core.EntityClient; 5 using System.Data.Entity.Infrastructure; 6 7 namespace ConnectionManagementExamples 8 { 9 internal class DatabaseOpenConnectionBehaviorEF6 10 { 11 public static void DatabaseOpenConnectionBehavior() 12 { 13 using (var context = new BloggingContext()) 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 the 20 // 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 }