接続管理
このページでは、コンテキストへの接続の引き渡しに関する Entity Framework の動作と、Database.Connection.Open() API の機能について説明します。
コンテキストへの接続の引き渡し
EF5 以前のバージョンの動作
接続を受け入れるコンストラクターは 2 つあります。
public DbContext(DbConnection existingConnection, bool contextOwnsConnection)
public DbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
これらも使用することはできますが、いくつかの制限を回避する必要があります。
- 開いている接続をこれらのいずれかに渡した後で、初めてフレームワークがこの接続を使用しようとすると、InvalidOperationException がスローされます。この例外は、既に開いている接続を再び開くことはできないということを伝えています。
- contextOwnsConnection フラグは、コンテキストが破棄された場合に、基になるストア接続を破棄する必要があるかどうかを示すものと解釈されます。 ただし、その設定とは無関係に、コンテキストが破棄された場合、ストア接続は常に閉じられます。 そのため、同じ接続を備える複数の DbContext がある場合は、最初に破棄されたコンテキストによって接続が閉じられます (同様に、既存の ADO.NET 接続と DbContext が混在している場合も、DbContext が破棄されると常に接続が閉じられます)。
閉じた接続を渡し、すべてのコンテキストが作成された後に接続を開くコードを実行するだけで、上記の 1 つ目の制限を回避することができます。
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();
}
}
}
}
}
2 つ目の制限は、接続を閉じる準備が整うまで、DbContext オブジェクトの破棄を控える必要があるというものです。
EF6 および今後のバージョンでの動作
EF6 および今後のバージョンでも、DbContext には同じく 2 つのコンストラクターがありますが、コンストラクターに渡した接続が受け取られたら、接続を閉じる必要はもうなくなりました。 そのため、以下が可能になります。
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 が破棄された場合に、接続を閉じるかどうか、また接続を破棄するかどうかの両方を制御できるようになりました。 したがって、以前のバージョンの EF の場合とは違って、上の例ではコンテキストが破棄されたとき (32 行目) には接続を閉じず、接続自体が破棄されたとき (40 行目) に閉じます。
もちろん、必要であれば、DbContext で接続を制御することもできます (contextOwnsConnection を true に設定するか、他のコンストラクターのいずれかを使用します)。
Note
この新しいモデルでトランザクションを使用する場合は、追加の考慮事項がいくつかあります。 詳細については、「トランザクションの操作」を参照してください。
Database.Connection.Open()
EF5 以前のバージョンの動作
EF5 以前のバージョンでは、基になるストア接続の実際の状態を反映する形で ObjectContext.Connection.State が更新されないといったバグがあります。 たとえば、次のコードを実行すると、基になるストア接続が実際には Open である場合でも、状態 Closed が返されます。
((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() を呼び出すことによって接続を開くことを選択した場合は接続を開くだけの正当な理由があるので、フレームワークはコードによって接続の開閉を制御する必要があると想定し、自動的に接続を閉じることはしなくなります。
Note
これにより、接続が長時間開いたままになる可能性があるため、慎重に使用する必要があります。
また、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
}
}
}
.NET