私はEFが初めてで、データベースタイプUser
から情報クラスUserInfo
に変換する拡張メソッドを使用しようとしています。
違いがある場合は、最初にデータベースを使用しますか?
以下の私のコードはエラーを与えます
DbContextが破棄されたため、操作を完了できません。
try
{
IQueryable<User> users;
using (var dataContext = new dataContext())
{
users = dataContext.Users
.Where(x => x.AccountID == accountId && x.IsAdmin == false);
if(users.Any() == false)
{
return null;
}
}
return users.Select(x => x.ToInfo()).ToList(); // this line is the problem
}
catch (Exception ex)
{
//...
}
なぜそうなるのかはわかりますが、whereステートメントの結果がusers
オブジェクトに保存されない理由もわかりません。
私の主な質問はなぜ機能しないのか、次に拡張メソッドとEFを使用する正しい方法は何ですか?
これ question&answer は、IQueryableがその操作にアクティブなコンテキストを必要とすることを信じさせます。つまり、代わりにこれを試してください。
try
{
IQueryable<User> users;
using (var dataContext = new dataContext())
{
users = dataContext.Users.Where(x => x.AccountID == accountId && x.IsAdmin == false);
if(users.Any() == false)
{
return null;
}
else
{
return users.Select(x => x.ToInfo()).ToList(); // this line is the problem
}
}
}
catch (Exception ex)
{
...
}
_IQueryable<T>
_および_IEnumerable<T>
_として公開されたオブジェクトは、_List<T>
_に合成されるなど、繰り返し処理されるかアクセスされるまで実際には「実行」されません。 EFが_IQueryable<T>
_を返すとき、それは基本的にデータを取得できる何かを構成しているだけであり、実際に取得するまで取得を実行していません。
IQueryable
が定義されている場所にブレークポイントを置くと、.ToList()
が呼び出されたときにこれを感じることができます。 (Jofryが正しく指摘しているように、データコンテキストのスコープ内から。)データをプルする作業は、ToList()
呼び出し中に行われます。
そのため、データコンテキストのスコープ内で_IQueryable<T>
_を保持する必要があります。
IQueryableクエリは、列挙するまでデータストアに対して実際には実行されないことに注意する必要があります。
using (var dataContext = new dataContext())
{
このコード行は、実際にはSQLステートメントを作成する以外は何もしません
users = dataContext.Users.Where(x => x.AccountID == accountId && x.IsAdmin == false);
.Any()はIQueryableを列挙する操作であるため、SQLは(dataContextを介して)データソースに送信され、次に.Any()操作が実行されます
if(users.Any() == false)
{
return null;
}
}
「問題」の行は、上記で作成したsqlを再利用してから、クエリに追加する追加の操作(.Select())を実行しています。ここに置いた場合、問題のある行を除き、例外はありません
return users.Select(x => x.ToInfo()).ToList(); // this line is the problem
.ToList()を呼び出し、IQueryableを列挙します。これにより、元のLINQクエリで使用されたdataContextを介してSQLがデータソースに送信されます。このdataContextは破棄されたため無効になり、.ToList()は例外をスローします。
それが「なぜ機能しないのか」です。修正は、このコード行をdataContextのスコープ内に移動することです。
それを適切に使用する方法は、アプリケーションに依存するいくつかの間違いなく正しい答えを含む別の質問です(フォーム対ASP.net対MVCなど)。これが実装するパターンは、作業単位パターンです。新しいコンテキストオブジェクトを作成する費用はほとんどありません。そのため、一般的なルールは、作成してから作業を行い、それを破棄することです。 Webアプリでは、リクエストごとにコンテキストを作成する人もいます。
エラーがスローされる理由は、オブジェクトが破棄され、その後オブジェクトを介してテーブル値にアクセスしようとしているが、オブジェクトが破棄されているためです。
使用するまでは実際にデータを取得していないので(遅延ロード)、作業を実行しようとするときにdataContextは存在しません。範囲内でToList()を実行しても大丈夫だと思います。
try
{
IQueryable<User> users;
var ret = null;
using (var dataContext = new dataContext())
{
users = dataContext.Users.Where(x => x.AccountID == accountId && x.IsAdmin == false);
if(users.Any())
{
ret = users.Select(x => x.ToInfo()).ToList();
}
}
Return ret;
}
catch (Exception ex)
{
...
}
ここでは、非アクティブなDBContextでIQueryableオブジェクトを実行しようとしています。 DBcontextはすでに破棄されています。 DBContextが破棄される前にのみIQueryableオブジェクトを実行できます。スコープを使用してusers.Select(x => x.ToInfo()).ToList()
ステートメントを記述する必要があることを意味します
これは、リポジトリにToList()を追加するのと同じくらい簡単です。例えば:
public IEnumerable<MyObject> GetMyObjectsForId(string id)
{
using (var ctxt = new RcContext())
{
// causes an error
return ctxt.MyObjects.Where(x => x.MyObjects.Id == id);
}
}
呼び出し元クラスでDbコンテキスト破棄エラーを生成しますが、LINQ操作にToList()を追加して列挙を明示的に実行することで解決できます。
public IEnumerable<MyObject> GetMyObjectsForId(string id)
{
using (var ctxt = new RcContext())
{
return ctxt.MyObjects.Where(x => x.MyObjects.Id == id).ToList();
}
}
これを変更:
using (var dataContext = new dataContext())
{
users = dataContext.Users.Where(x => x.AccountID == accountId && x.IsAdmin == false);
if(users.Any())
{
ret = users.Select(x => x.ToInfo()).ToList();
}
}
これに:
using (var dataContext = new dataContext())
{
return = dataContext.Users.Where(x => x.AccountID == accountId && x.IsAdmin == false).Select(x => x.ToInfo()).ToList();
}
要点は、コンテキストデータセットの列挙を一度だけ強制することです。必要に応じて、呼び出し側に空のセットシナリオを処理させます。
using(var database=new DatabaseEntities()){}
Usingステートメントを使用しないでください。こう書いて
DatabaseEntities database=new DatabaseEntities ();{}
それが動作します。
using
ステートメントのドキュメントについては、 here を参照してください。