質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

2回答

1795閲覧

Entity frameworkの更新処理について

elvis

総合スコア29

Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

1クリップ

投稿2019/02/25 14:04

質問

現在、フォームでDBの値を更新できるアプリを開発しています。

そこで質問ですが、下記のようにDbContextをメンバーにおいて、
共用することは、正しい書き方なのでしょうか。

それとも、各イベントやメソッドの中で、using ステートメント等を用いて、
その度に使い捨てたほうが良いのでしょうか。

接続を専有してそうで不安になり、質問させていただきました。
どうぞ宜しくお願いします。

###環境
◎.net Frame Work 4.61
◎C#
◎Entity framework 6.2
◎SQLite3
◎visual studio Community 2017
◎windows7 64bit

現在のソースコード(共用するパターン)

C#

1 2namespace Test 3{ 4 public partial class MainWindow : Window 5 { 6 EntityDbContext Context = new EntityDbContext(); 7 8 public MainWindow() 9 { 10 InitializeComponent(); 11 12 //全体にバインド 13 this.DataContext = Context.MT_Users.Where(x => x.UserID == 235).Single(); 14 } 15 16 /// <summary> 17 /// 更新ボタンクリック 18 /// </summary> 19 /// <param name="sender"></param> 20 /// <param name="e"></param> 21 private void ButtonUpdate_Click(object sender, RoutedEventArgs e) 22 { 23 //実行 24 Context.SaveChanges(); 25 } 26 27 /// <summary> 28 /// 削除ボタンクリック 29 /// </summary> 30 /// <param name="sender"></param> 31 /// <param name="e"></param> 32 private void ButtonDelete_Click(object sender, RoutedEventArgs e) 33 { 34 var deleteTarget = new MT_Users { UserID = 235 }; 35 Context.MT_Users.Attach(deleteTarget); 36 Context.MT_Users.Remove(deleteTarget); 37 38 //実行 39 Context.SaveChanges(); 40 } 41 } 42} 43

###仕掛中のコード(毎回接続するパターン)

C#

1namespace Test 2{ 3 public partial class MainWindow : Window 4 { 5 public MainWindow() 6 { 7 InitializeComponent(); 8 9 using (var context = new EntityDbContext()) 10 { 11 //バインド 12 this.DataContext = context.User.Where(x => x.UserID == 235).Single(); 13 } 14 } 15 16 /// <summary> 17 /// 更新ボタンクリック 18 /// </summary> 19 /// <param name="sender"></param> 20 /// <param name="e"></param> 21 private void ButtonUpdate_Click(object sender, RoutedEventArgs e) 22 { 23       //フォームで変更された値をSaveChanges()で更新したいのですが、 24       //どう書けば良いか、わかりません。 25 } 26 27 /// <summary> 28 /// 削除ボタンクリック 29 /// </summary> 30 /// <param name="sender"></param> 31 /// <param name="e"></param> 32 private void ButtonDelete_Click(object sender, RoutedEventArgs e) 33 { 34 using (var context = new EntityDbContext()) 35 { 36    var deleteTarget = new MT_Users { UserID = 235 }; 37    context.MT_Users.Attach(deleteTarget); 38    context.MT_Users.Remove(deleteTarget); 39 40  //実行 41    context.SaveChanges(); 42 } 43 } 44 } 45}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

こんにちは。

実はEntityFrameworkはクエリを実行してデータの実体を取得するか、SaveChangesなどを実行するタイミングで接続を開くため、「接続を占有」という点ではどちらの記述でも問題ないかと思います。
個人的には、DBという「リソース」の有効区間を明示化できている後者の書き方の方が好みです。が、DbContextをDIで放り込む場合などではサービスクラス単位で1つのDbContextを共有するのが一般的なため、前者のような書き方も広く利用されています。

以下は主観ですが、前者のように、クラス内に「リソース」(一般的に、IDisposableを実装するオブジェクトのこと)を配置する場合、そのクラス自身も「リソース」であると看做すことができるため、クラス自身にもIDisposableを実装し、Disposeメソッド内で自分が持つリソースの破棄するように記述するという設計が理想的です。そのため、作成->破棄のサイクルを回すことができないMainWindowのようなオブジェクト内にリソースを配置するのはあまり好ましくないと考えています。なので、今回のケースでは後者の「毎回接続するパターン」をベースに考えることを個人的にオススメします。

あとは、「フォームで変更された値をSaveChanges()で更新したい」というコメントがありますが、それは単純にButtonUpdate_Clickメソッド内で必要なフォームの値を全てかき集めて、それを書き込めばいいだけではないでしょうか?

投稿2019/02/25 15:31

tamoto

総合スコア4103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

elvis

2019/02/25 15:51

tamotoさん こんばんは。ご回答ありがとうございます。 無知な私にも、わかり易く説明してくださり感謝しております。 毎回接続するかたちで実装したいと思います。 >単純にButtonUpdate_Clickメソッド内で必要なフォームの値を全てかき集めて、それを書き込めばいいだけではないでしょうか? 前者の場合はフォームにModelをバインドしているので、テキストボックスなどで変更した値は、Context.SaveChanges()を実行するだけで、保存されます。 それに対し後者は、コンストラクタでModelをバインドしていますが、usingでDbContext破棄しているため、ButtonUpdate_Clickにおいてcontext.SaveChanges()で保存することは不可能と考えた次第です。この場合は、かき集めて書き込むカタチになるのでしょうか。
tamoto

2019/02/26 00:22

そうですね、個人的には、フォームにDBのModelを直接配置することはせず、サービスを提供するのに必要なデータだけを保持するようにしておき、保存するときに書き込むオブジェクトを作成するようにします。その方が、書き込まれるデータの整合性を担保する責任が一箇所にまとまるためです。今後不用意な変更を加えた際に意図しないデータがDBに書き込まれる危険を抑えることに繋がります。
Zuishin

2019/02/26 20:56

コンテキストを残しておかないとどのデータが更新されているかわからず全て読み込んだり書き込んだりすることになりはしませんか? それで問題ない時はないんでしょうけど、大量のデータを扱うときにパフォーマンスが悪くなると思います。 それならいっそデータベースを使わず Json や XML でシリアライズした方が速いのではないかと思いますが。
Zuishin

2019/02/26 21:12

EntityFramework はデータを 10 万件書き込む際に 10 万回の UPDATE 文を発行します。そのそれぞれにプロトコルが必要なので、無茶苦茶遅いです。 10 万件のデータを扱う際には、コンテキストを破棄した場合、書き込みに必ず 10 万回の UPDATE 文が必要になりますが、コンテキストを残しておいた場合、書き換えられたデータが 100 件であれば書き込みに使う UPDATE 文は 100 回で済みます。
mikupedia

2019/02/27 00:48 編集

tamotoさんの回答を読む限り >DbContextをDIで放り込む場合などではサービスクラス単位で1つのDbContextを共有する とも書かれていますのでそのようなトラッキングを行いたいパターンについては そのサービス内でContextを共有するのがベストなんだと思います
tamoto

2019/02/27 00:37

なるほど、更新対象のデータが巨大である場合というのは全く想定していませんでした。しかし、そんな巨大なデータを扱うなら、それこそMainWindowになんて配置しないで、専用のサービスクラスを起こしてライフサイクルをマネジメントするべきだと考えてしまいます。 それと、EntityFrameworkCoreでの話ですけど、値が変化しないようなUpdateの記述はDBへのSQL発行が省略されることを確認しているので、書き方次第でしょうけど大規模データであっても効率的な更新は可能なはずだと思っています。
Zuishin

2019/02/27 02:25

サービスクラスについても言及されていますが、それを使うより接続の度に作成・破棄を繰り返すことを勧める回答のように見えたので、うーんと思いました。 多分質問者さんもデータベースは接続の度にコンテキストを作るものだと思われたんじゃないかと思います。
tamoto

2019/02/27 03:25

えっと、そうですね、データがDBと生きて直結していなければならない場合でもない限り、その通り接続の度に生成と破棄を繰り返すことをむしろ推奨しています。コンテキストの生成にかかるコストを抑えることよりも、リソースのライフサイクルの扱いを間違えることに起因するエラーを回避することをほとんどのケースで優先しています。 今回は質問の対象がWindowだったため、DBのテーブルから1件から数件程度のレコードを取り出して使っているようなものをイメージしていました。質問のコードからも、UserIDに紐つく1件のレコードを取り扱う程度のものと読み取れます。このような場合にWindowにDBリソースの管理を任せるのは、得られるリターンに対してリソースマネジメントの難易度が割に合いません。DBのテーブル1枚をまるごとDataTableにマッピングして編集するなど、Windowが実質DBであるとでも言わない限り、コンテキストを保持することは回避するべきだと考えています。もちろん、コーディングで完璧に管理できる自信があって、後々のメンテナンスにも対応可能であるなら、限界までパフォーマンスを引き出せる手段を選べば良いですが、自分にはそれをやりきれる自信はありません。
Zuishin

2019/02/27 04:03 編集

私は MainWindow をライフサイクルの観点から Window ではなくアプリととらえています。 実際にはアクセシビリティの観点から MainWindow ではなくシングルトンで実装することが多いのですが、アプリのライフサイクルでトラッキングして特に問題が生じるとは思いません。 リソースマネージメントをするために Entity Framework があるので、それをさらにマネージメントする必要があるのか疑問です。 昔の、また他の言語の DB の扱いは接続型なのでこまめに切断してリソースをマネージメントする必要がありますが、Entity Framework はそのあたりを管理してくれる非接続型です。すなわち、アクセスする必要がある時だけ DB に接続し、データの取得や書き換えが終わると速やかに切断します。 つまり、わざわざ Dispose を呼び出さなくてもそれ相当の仕事を自動的にしてくれるということです。 DB におけるリソースとは、接続文字列ではなく接続です。接続することを、リソースの占有、切断することをリソースの解放と称します。 コンテキストが保持しているのは接続ではなく、接続文字列やデータのトラッキングにすぎないので、これを保持しても DB のリソースは一切占有しません。 Git に例えるとわかりやすいかもしれません。コンテキストはローカルリポジトリにあたります。push する前にリポジトリを作り、した後に削除するようなことをせずともローカルリポジトリはずっと存在していていいはずです。 もしローカルリポジトリをいちいち削除するとすると、push や pull にあたって必ず全データのやり取りが必要になってきます。 切断している間のデータの変化を監視するのがローカルリポジトリの役目であり、DbContext の役目です。
tamoto

2019/02/27 05:10

主張はよく理解できます。接続こそがリソースであり、非接続型のDbContextはリソースではないというのは物理資源的な視点では絶対的に正しいです。 しかし、DbContextをアプリのライフサイクルで扱うことには個人的に反対です。アプリが常にDBに対して開かれた環境であるという想定であれば間違いではないのですが、一般的にアプリはそうではないと思っています。DBを使えるのは、DBのコンテキスト、すなわち「文脈」内に立ち入っているその間だけ有効であるものとして扱いたいのです。それは、アプリが起動中の常ではなく、最初にデータを受け取るとき、書き換えたデータを渡すとき、などに限られます。この「文脈への立ち入り」を指してリソースの利用と表現していました。コード上の概念としての、DBの「文脈」のusing、あるいはCreate/Disposeを徹底するべきだという主張なのです。 あえて極端な言い方をすると、publicでstaticなグローバル変数をアプリ全体で共有するのは避けたいので、特定の文脈内に立ち入った者だけが中にいる間だけIOを扱えるように厳格寄りにルール付けしたい、というような話です。扱うのに手間が増えるのがわかっていて区画を切ったり入場を制限することに意味がないとは思いません。
Zuishin

2019/02/27 05:56

了解しました。後はポリシーの問題になるのでここでは差し控えます。
tamoto

2019/02/27 06:09

こちらこそどうもです。また何か気がかりなことがありましたらそのときはよろしくお願いします。
elvis

2019/03/10 14:19

皆さま、様々なご意見をありがとうございます。 私は未熟者ゆえ半分くらいしか内容を理解できていませんが、利用するケースによって適切な使い分けが必要だということはわかった気がします。 本当はこんなに奥が深いのに、かいつまんで解りやすく教えてくださったtamotoさんに心より感謝申し上げます。ありがとうございました。
guest

0

こんにちは!
ほぼほぼtamoto様がご説明されているので蛇足感がありますが。
私の場合も後者の毎回接続/切断の記述を好みます。
理由は発行されたクエリおよび取得データがEntityFramework側でキャッシュされ
同じクエリが発行されたときにキャッシュのデータを返すため都合が悪かったためです。
AsNoTracking句で回避することもできますが、1つのContextを使いまわすよりも
DBへアクセスする処理をテーブル単位などの粒度でサービスクラスを作ってレイヤーを分けて
それぞれでその都度Contextの生成、破棄を行うようにしたほうが機能もまとまってわかりやすいです。

投稿2019/02/26 01:33

mikupedia

総合スコア159

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

elvis

2019/03/10 13:58

ご返信が遅くなり申し訳ございません。 キャッシュデータを返すことや、AsNoTracking句など、初心者の私には未知の領域ですが、ひとつひとつ調べて勉強していきたいと思います。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問