none
Entity Framework を使用したクラスライブラリの作成方法について RRS feed

  • 質問

  • いつもお世話になっております。

    SQL Server、Entity Frameworkについて学習をしているのですが、自分の力だけでは解決ができなかったので皆様のお力を頼らせて頂きたく、投稿させていただきました。

    目的:
    データベースごとにEntity Framework を使用したクラスライブラリを作成し、そのDLLをEntity Frameworkを参照していないプロジェクトから参照し、自作したクラスライブラリの機能からデータベースにアクセスしたい

    問題:
    データベースごとに作成したクラスライブラリを、ある一つのプログラムから参照し、それぞれのクラスライブラリのインスタンスを作成しようとすると、DbContextの初期化の段階で例外が発生する。

    質問:
    Entity Framework を使用したクラスライブラリを作成し、他のプログラムからどのように使用すればよいでしょうか。
    また、このようにデータベースごとにDLLを作成し、使いまわすのは設計的に問題ないでしょうか。
    もし他に、異なるデータベースにアクセスするより良い方法があればご教授ください。

    以上、よろしくお願い致します。

    Sample program
    http://fast-uploader.com/file/7037877449973/

    環境:
    OS:Windows 7 Professional
    開発環境:Visual Studio 2010 Professional
    サーバー:SQL Server 2012 Express
    .Net Framework バージョン:4.0
    Entity Framework バージョン:6.1.3


    2016年12月21日 12:14

回答

  • EF6, DbContext を利用した場合について、先の私のレスで「明日、もう少し調べてみます」と書きました。

    なので、自分でも EF6, DbContext を使ったサンプルを作ってどうなるか試してみました。環境は Visual Studio 2010 Professional, .NET 4, SQL Server 2008 Express でコンソールアプリ+クラスライブラリです。

    結果、質問者さんが経験された「2の結果」とほぼ同じことが再現できただけですが、話の辻褄は合うのが分かったので、自分的には、

    > 「使用するプログラム」で Entity Framework 用の SqlClient を使えるようにしなければならない

    というところに納得しています。

    具体的にどのようなことをしたかとその結果は以下の通りです。

    (1) Visual Stusio のテンプレートを利用してコンソールアプリを自動生成。同じソリューション内にクラスライブラリを追加。

    (2) クラスライブラリには NuGet パッケージ管理で EF6 をインストールし、DbContext クラスを使って質問者さんが作ったものと同様な形で SQL Server 2008 Express の DB にアクセスするものを作成。

    (3) クラスライブラリの App.config の中身(EF6 をインストールした際自動生成されたもの)はコメントアウト。

    (4) コンソールアプリは Visual Studio で自動生成されたままの状態(EF6 のインストールは行いません)。

    (5) コンソールアプリには、上記 (2) で作成したクラスライブラリへの参照を追加し、App.config を追加してその中に接続文字列を設定し、以下のコードを記述。

    namespace ConsoleAppDbContext
    {
        class Program
        {
            static void Main(string[] args)
            {
                string connString = ConfigurationManager.ConnectionStrings["BooksConnection"].ConnectionString;
                BooksAccessor books = new BooksAccessor(connString);
    
                connString = ConfigurationManager.ConnectionStrings["ParentConnection"].ConnectionString;
                ParentAccessor parent = new ParentAccessor(connString);
    
                var list1 = books.GetBookList();
                var list2 = parent.GetParentList();
    
                foreach (var item in list1)
                {
                    Console.WriteLine("Isbn: {0}, Title: {1}", item.Isbn, item.Title);
                }
    
                foreach (var item in list2)
                {
                    Console.WriteLine("Id: {0}, Name: {1}", item.Id, item.Name);
                }
            }
        }
    }

    (6) この状態で実行すると、books.GetBookList() メソッドの中で InvalidOperationException がスローされる。エラーメッセージは下記:

    No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SqlClient'. Make sure the provider is registered in the 'entityFramework' section of the application config file. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.

    new BooksAccessor(connString) および new ParentAccessor(connString) では接続文字列で指定される SqlClient を使うということで初期化には成功。ところが、books.GetBookList() で DB に接続に行くところで Entity Framework provider(Entity Framework 用の SqlClient)が見つからないということで例外がスローされています。

    (7) 上のエラーメッセージに従って、コンソールアプリの App.config に 'entityFramework' section(クラスライブラリの App.config に NuGet パッケージ管理で EF6 をインストールした際自動生成されたものと同じ)を追加。この状態で実行すると、new BooksAccessor(connString); で InvalidOperationException がスローされる。エラーメッセージは下記:

    The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application.

    App.config で指定されたプロバイダ(Entity Framework 用の SqlClient)がロードできないということで例外がスローされています。

    コンソールアプリの bin フォルダには EntityFramework.dll は自動的にコピーされているが、Entity Framework 用の SqlClient がある EntityFramework.SqlServer.dll は存在しないのでロードできないということらしい。(何故 EntityFramework.dll だけ自動的にコピーされるのかは分かりません)

    (8) コンソールアプリにも NuGet パッケージ管理で EF6 をインストール。自動的に EF6 の EntityFramework と EntityFramework.SqlServer がローカルコピー True で参照設定に追加される。その状態で実行すると問題なく実行され、Console.WriteLine で DB の内容が期待通りにコンソールに出力される。

    • 編集済み SurferOnWww 2016年12月26日 3:26 誤記訂正:(7) ・・・クラスライブラリの ⇒ (7) ・・・コンソールアプリの
    • 回答としてマーク fall1000 2016年12月26日 4:52
    2016年12月26日 2:51
  • 余計なお世話かもしれませんが・・・

    もし、質問者さんの作成しようとしているクラスライブラリが、DB からデータを取得してそれをカスタムクラスのコレクションとして渡すということだけの目的に使うのであれば、Entity Framework を使うのが適切かどうか考え直した方がいいかもしれません。

    ADO.NET のもっとプリミティブな機能(SqlConnection, SqlCommand, SqlDataReader など)を使うだけでその目的は果たせると思いますので。

    • 回答としてマーク fall1000 2016年12月26日 4:52
    2016年12月26日 3:03

すべての返信

  • どのような例外が発生するのでしょうか?

    # ぐらいは質問に含めていただきたいです。参考: フォーラムのご利用方法(質問の投稿)について

    2016年12月21日 13:02
  • こんにちは。

    事象から憶測すると、クラスライブラリを参照するプログラムにEntityFrameworkの参照設定が入っていないのではないでしょうか。

    2016年12月22日 1:10
  • 「Sample program」というのが質問者さんが作ったもので、今問題が発生しているソースそのものなんですよね?

    以下のソースがあったのですが、「ここで例外発生」というのが Database2Accessor.Accessor の初期化の時だけで Database1Accessor.Accessor の初期化は問題ないのですか?

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    // このプログラムから Database1Accessor.Accessor、Database2Accessor.Accessor を使用し、
    // 異なるデータベースにアクセスする
    
    namespace EntityFrameworkTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                var connectionString1 = System.Configuration.ConfigurationManager.ConnectionStrings["Database1"].ConnectionString;
                var database1Accessor = new Database1Accessor.Accessor(connectionString1);
    
                var connectionString2 = System.Configuration.ConfigurationManager.ConnectionStrings["Database2"].ConnectionString;
                var database2Accessor = new Database2Accessor.Accessor(connectionString2); // ここで例外発生
    
                try
                {
                    var table1 = database1Accessor.GetTable1s();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
    
                Console.ReadKey();
            }
        }
    }

    であれば、両者の違いをご自分で調べてみるというのがまず質問者さんのやるべきことで、それを行えばそこに原因や問題解決のためのヒントが見つかって、自己解決できるのではないですか?

    それをやってどうしても見つからなければ、問題を再現できる必要最低限にコードを削ってみてください。その過程で原因が見つかることが多いです。原因が見つかれば自己解決できると思いますし、できなくても問題再現に必要最低限まで削ったコードをアップしてもらえると回答がつきやすいと思います。

    そして一番重要なことですが、他の方からもお願いされているように、どのような例外が発生するのかとそのエラーメッセージは質問の最初に必ず書いてください。

    2016年12月22日 2:22
  • ご返信頂きありがとうございます。

    失礼致しました。例外は以下のようなものが発生しています。

    「InvalidOperationExceptionはハンドルされませんでした。」

    「'DbContextConfiguration1' のインスタンスが設定されましたが、この型は 'Database2DbContext' コンテキストと同じアセンブリに見つかりませんでした。DbContext 型と同じアセンブリに DbConfiguration 型を配置するか、DbContext 型で DbConfigurationTypeAttribute を使用して DbConfiguration 型を指定するか、config ファイルで DbConfiguration 型を設定してください。詳細については、http://go.microsoft.com/fwlink/?LinkId=260883 を参照してください。」


    この例外が発生する状況は以下のようになっています。
    まず前提条件として、
    Database1Accessor.dll という Entity Framework を参照しているクラスライブラリの中で
    Database1DbContext という DbContext を継承したクラスと
    DbContextConfiguration1 という DbConfiguration を継承したクラス、
    Accessor という外部からデータベースを操作するためのクラスを定義しており、
    同じように Database2Accessor.dll という Entity Framework を参照しているクラスライブラリの中で
    Database2DbContext という DbContext を継承したクラスと
    DbContextConfiguration2 という DbConfiguration を継承したクラス、
    Accessor という外部からデータベースを操作するためのクラスを定義しています。

    この二つのライブラリを参照する、 Entity Framework を参照していないテスト用のプロジェクトがあり、このプロジェクトからまず、Database1Accessor.dllの外部から操作するために用意した Accessor クラス経由で Database1DbContext を初期化します。
    ここまでは正常に動作するのですが、この後に Database2Accessor.dllの外部から操作するために用意した Accessor クラス経由で Database2DbContext を初期化しようとすると上記の例外が発生してしまいます。

    何か原因に心当たりはございませんでしょうか。

    よろしくお願い致します。

    2016年12月22日 2:23
  • こんにちは。

    はい、クラスライブラリを参照するプログラムに Entity Framework の参照設定は入れていません。

    データベースにアクセスする部分をクラスライブラリに切り離し、クラスライブラリ内に用意した操作用のクラス経由でクラスライブラリを参照するプログラムからデータベースにアクセスしたいと考え、このような構成にしました。

    このような設計はやはりだめなのでしょうか。

    2016年12月22日 2:32
  • ご指摘頂きありがとうございます。

    どのような例外が発生するか、どのようなエラーメッセージが表示されるかは、今後ご質問させていただく際に忘れず記載するように気を付けたいと思います。

    説明が不足してしまい申し訳ありません。

    Sample Program というものは私が作成したプログラムで、問題が発生していたプログラムから検証をするために機能を抜き出して作成したプログラムになります。

    私がこのプログラムで検証した内容は以下の通りです。

    検証1.
    Database1Accessor.Accessor のコンストラクタに Database1DbContext 用の接続文字を渡し、インスタンスを作成する。
    その後、作成したインスタンスの public なメソッドからデータベースにアクセスする。

    検証2.
    Database2Accessor.Accessor のコンストラクタに Database2DbContext 用の接続文字を渡し、インスタンスを作成する。
    その後、作成したインスタンスの public なメソッドからデータベースにアクセスする。

    検証3.
    始めにDatabase1Accessor.Accessor のコンストラクタに Database1DbContext 用の接続文字を渡し、インスタンスを作成する。
    次にDatabase2Accessor.Accessor のコンストラクタに Database2DbContext 用の接続文字を渡し、インスタンスを作成する。
    その後、作成したインスタンスの public なメソッドからデータベースにアクセスする。

    検証4.
    始めにDatabase2Accessor.Accessor のコンストラクタに Database2DbContext 用の接続文字を渡し、インスタンスを作成する。
    次にDatabase1Accessor.Accessor のコンストラクタに Database1DbContext 用の接続文字を渡し、インスタンスを作成する。
    その後、作成したインスタンスの public なメソッドからデータベースにアクセスする。

    検証結果は次の通りです。

    検証1 結果.
    正常に動作することを確認。

    検証2 結果.
    正常に動作することを確認。

    検証3 結果.
    Database2Accessor.Accessor のインスタンスを作成する際に Database2DbContext の初期化で「InvalidOperationExceptionはハンドルされませんでした。」という例外が発生。
    例外のメッセージ内容は次のようになります。
    「'DbContextConfiguration1' のインスタンスが設定されましたが、この型は 'Database2DbContext' コンテキストと同じアセンブリに見つかりませんでした。DbContext 型と同じアセンブリに DbConfiguration 型を配置するか、DbContext 型で DbConfigurationTypeAttribute を使用して DbConfiguration 型を指定するか、config ファイルで DbConfiguration 型を設定してください。詳細については、http://go.microsoft.com/fwlink/?LinkId=260883 を参照してください。」

    検証4 結果.
    Database1Accessor.Accessor のインスタンスを作成する際に Database1DbContext の初期化で「InvalidOperationExceptionはハンドルされませんでした。」という例外が発生。
    例外のメッセージ内容は次のようになります。
    「'DbContextConfiguration2' のインスタンスが設定されましたが、この型は 'Database1DbContext' コンテキストと同じアセンブリに見つかりませんでした。DbContext 型と同じアセンブリに DbConfiguration 型を配置するか、DbContext 型で DbConfigurationTypeAttribute を使用して DbConfiguration 型を指定するか、config ファイルで DbConfiguration 型を設定してください。詳細については、http://go.microsoft.com/fwlink/?LinkId=260883 を参照してください。」

    以上より、このサンプルプログラムで作成したような、アセンブリの分かれているクラスライブラリを一つのプログラムから参照した場合、両方のクラスライブラリの機能を同時に使用することができないことがわかりました。
    ですがこれ以上のことが自分の実力不足で解決することができず、お力をお貸し頂きたいと考えフォーラムに投稿させて頂きました。

    私としてはできればデータベースごとに操作用のクラスライブラリを作成し、いろいろなプログラムからその作成したクラスライブラリを組み合わせて使用したいという思いがあります。
    何か良い解決策はありませんでしょうか。
    2016年12月22日 3:51
  • 以下のコードのどちらか一方しか初期化のコードが存在しない場合は例外はスローされない。両方あると後の方の初期化のコードで InvalidOperationException 例外がスローされる。順番を入れ替えてもやはり後の方の初期化のコードで InvalidOperationException 例外がスローされる・・・ということと理解しました。

    var connectionString1 = System.Configuration.ConfigurationManager.ConnectionStrings["Database1"].ConnectionString;
    var database1Accessor = new Database1Accessor.Accessor(connectionString1);
    
    var connectionString2 = System.Configuration.ConfigurationManager.ConnectionStrings["Database2"].ConnectionString;
    var database2Accessor = new Database2Accessor.Accessor(connectionString2); // ここで例外

    自分は DbConfiguration クラスというのは使ったことがなく初めて知ったのですが、以下の記事(エラーメッセージのリンク先)によると、"Configuration for an Entity Framework application can be specified in a config file (app.config/web.config) or through code." ということで、後者のコードによる構成設定を行う場合に使用するものだそうです。

    https://msdn.microsoft.com/ja-jp/data/jj680699
    Entity Framework Code-Based Configuration (EF6 onwards)

    その記事の「Using DbConfiguration」セクションに書いてある "Create only one DbConfiguration class for your application. This class specifies app-domain wide settings." としなければならないところ、2 つのアセンブリにそれぞれ別の DbConfiguration クラスが定義されているところが問題で、2 つの DbContext クラスを初期化しようとすると後の方で例外がスローされているような気がします。(気がするだけで検証したわけではありません)

    質問者さんのケースのように 2 つの DbContext クラスが異なるアセンブリにあるような場合は、「Using DbConfiguration」セクションに書いてある "Place your DbConfiguration class in the same assembly as your DbContext class" という条件を満たせないのですが、そういう場合は、その記事の「Moving DbConfiguration」セクションに書いてある手段を取ることで解決できるそうです。

    お試しください。

    #そもそも、DbConfiguration クラスの定義が必要なんでしょうか? 「設定より規約」の原則に従う EF で、既定の動作では要件を満たさない場合のみに使うということらしいですが・・・

    2016年12月22日 7:39
  • 今回の問題とは直接関係ない余計な話かもしれませんが。

    (1) 今回の話は EF Code First の機能を使って DB の生成も行うということでしょうか? もしそうではなくて、既存の SQL Server データーベースかあるのなら、それから Visual Studio のウィザードを使って EDM を生成して利用してはいかがでしょう。

    10 行でズバリ !! 概念モデルの作成 (EDM) (C#)
    https://code.msdn.microsoft.com/10-EDM-C-5a940782/

    以下の画像のようなクラスファイルが自動生成されますので、それを利用した方が簡単かつ間違いがないと思います。

    (2) Exception をキャッチするのは止めた方がいいです。理由は以下の記事を見てください。

    NETの例外処理 Part.1
    https://blogs.msdn.microsoft.com/nakama/2008/12/29/net-part-1/

    .NETの例外処理 Part.2
    https://blogs.msdn.microsoft.com/nakama/2009/01/02/net-part-2/

    .NET 4 からは破損状態例外は catch できなくなっているそうですが、「それでも Catch (Exception e) を使用するのはよくない」ということについては以下の記事を見てください。

    破損状態例外を処理する
    https://msdn.microsoft.com/ja-jp/magazine/dd419661.aspx



    • 編集済み SurferOnWww 2016年12月22日 9:57 画像差し替え&誤字訂正
    2016年12月22日 9:53
  • >SurferOnWww 様

    お調べ頂きありがとうございます。
    お教え頂いたことを参考に、以下のことを試してみました。

    1.後に初期化する DbContext の派生クラスの DbConfigurationTypeAttribute に、
    先に初期化する DbContext で指定した DbConfiguration の派生クラスを文字列で指定する。
    Sample Program「EntityFrameworkTest_1.zip」
    http://firestorage.jp/download/22fcc2b2213657881b6149df091a6681a5d700ea

    2.DbConfigurationを使わないようにクラスライブラリの内容を変更する
    Sample Program「EntityFrameworkTest_2.zip」
    http://firestorage.jp/download/e3e8ef5be311c705658cf2dff4d1d011c7a254ad

    3.それぞれのクラスライブラリから参照する新しいクラスライブラリを作成し、その中で DbConfiguration の派生クラスを一つだけ定義し、
    DbContext の DbConfigurationTypeAttribute 属性設定時に、新しく作成したクラスライブラリの DbConfiguration を指定する
    Sample Program「EntityFrameworkTest_3.zip」
    http://firestorage.jp/download/f7c9fa550e0ca517514029c3f49dcf646cb2d0ac

    試してみた作業内容と結果は以下のようになりました。

    ・1の結果
    Main で先に初期化する Database1Accessor.Accessor の中で初期化を行う Database1DbContext に付加した DbConfigurationTypeAttribute で指定する Database1Accessor.DbContextConfiguration1 を、二番目に初期化する Database2Accessor.Accessor の中で初期化を行う Database2DbContext に次のように文字列で設定しました。

    [DbConfigurationType("Database1Accessor.DbContextConfiguration1, Database1Accessor")]
    class Database2DbContext:DbContext{
    .....
    }

    その結果 Main の中でどちらのクラスライブラリも正常に初期化することができ、データベースにも正常にアクセスすることができました。


    ・2の結果
    それぞれのクラスライブラリで DbContextConfiguration1 と DbContextConfiguration2 を削除し、DbContext を継承したクラスに DbConfigurationTypeAttribute 属性を付加しないように変更しました。
    この段階でコンパイルし、実行すると Database1Accessor.Accessor と Database2Accessor.Accessor のインスタンスは問題なく作成することができました。
    しかし、そのあとで Database1Accessor.Accessor のインスタンスからデータベースにアクセスしようとすると、以下の例外が発生してしまいました。
    「InvalidOperationExceptionがキャッチされました」
    「ADO.NET プロバイダーに、不変名が 'System.Data.SqlClient' の Entity Framework プロバイダーがありません。アプリケーションの構成ファイルの "entityFramework" セクションにプロバイダーが登録されていることを確認してください。詳細については、http://go.microsoft.com/fwlink/?LinkId=260882 を参照してください。」

    そこで、App.config に以下の要素を追加し、再度コンパイルと実行を行いました。
    <configSections>
        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
      </configSections>
      <entityFramework>
        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
        <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
        </providers>
      </entityFramework>

    その結果、Main が始まってすぐに初期化する Database1Accessor.Accessor の中で行っている Database1DbContext の初期化で次の例外が発生してしまいました。
    「InvalidOperationException はハンドルされませんでした。」
    「不変名が 'System.Data.SqlClient' の ADO.NET プロバイダーのアプリケーション構成ファイルに登録された Entity Framework プロバイダー型 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' を読み込めませんでした。assembly-qualified 名が使用されていること、およびアセンブリを実行中のアプリケーションで使用できることを確認してください。詳細については、http://go.microsoft.com/fwlink/?LinkId=260882 を参照してください。」

    そのため、二つのクラスライブラリを参照しているテストプログラムのプロジェクトにも Entity Framework の参照を追加し、実行を行いました。
    その結果、例外は発生せず、正常にデータベースにアクセスすることができました。

    ・3の結果
    新たに MyDbContextConfiguration というプロジェクトを作成し、このプロジェクトに Entity Framework の参照を追加し、 DbConfiguration を継承した MyDbContextConfiguration というクラスを作成しました。
    このプロジェクトの参照を Database1DbContext と Database2DbContext が定義されているクラスライブラリのプロジェクトに追加し、
    テスト用のプログラムの参照にも追加しました。
    そして Database1DbContext と Database2DbContext の定義で以下のように DbConfigurationTypeAttribute 属性を付加しました。

    [DbConfigurationType(typeof(MyDbContextConfigurations.MyDbContextConfiguration))]
    class Database1DbContext:DbContext{
    .....
    }

    [DbConfigurationType(typeof(MyDbContextConfigurations.MyDbContextConfiguration))]
    class Database2DbContext:DbContext{
    .....
    }

    その結果、テストプログラム実行で例外は発生せず、正常にデータベースにアクセスすることができました。

    以上が試したプログラムの結果になります。
    いずれも正常にデータベースにアクセスすることができることを確認しましたが、
    以下の点に不満の残る結果となりました。

    ・1について
    Database2DbContext を定義しているクラスライブラリが Database1DbContext を定義しているクラスライブラリに依存してしまう点が、
    私の目的とずれてしまいました。
    できればそれぞれのクラスライブラリは互いに非依存でありたいと考えています。

    ・2について
    検証用のテストプログラムに Entity Framework の参照を追加しなければならず、
    App.config にも Entity Framework に関連する要素を追加しなければならない点が私の目的と合致しませんでした。
    できればクラスライブラリを使う側は Entity Framework を使用していることを意識しなくても済むように作りたいと考えています。

    ・3について
    DbConfigurationTypeAttribute 属性を付加する際に使う MyDbContextConfiguration のためだけに
    新しいクラスライブラリを作成するのが少し大げさではないかと感じてしまいました。
    また、今後別のデータベースを操作するためのライブラリを追加作成する際に、
    できればライブラリが依存するのは Entity Framework のみに抑え、
    他に新しく自分で定義したライブラリ(MyDbContextConfiguration)を必ず参照しなければならないという
    制約ができてしまうのは避けたいと感じました。


    以上になります。


    自分自身で自分の目的が少し曖昧でしたが、以上のことを試してみて何を達成すれば自分の目的を達成することができるか整理し直してみました。
    1.クラスライブラリを参照し、使用するプログラムには Entity Framework の参照を追加せず、
    App.config にも Entity Framework に関連する要素を記述しない

    2.クラスライブラリが依存するのは Entity Framework のみで、データベースにアクセスするために自分で定義した他のクラスライブラリを参照しない

    3.作成したクラスライブラリはどのような組み合わせでも、同じプログラムから実行することができ、正常にデータベースの操作を行うことができる

    以上を満たすことができれば私の目的を達成することができると思います。

    何かよい知恵がありましたらご教授下さい。
    また、そもそもの設計思想に何か問題があればご指摘いただけると幸いです。


    以上、よろしくお願い致します。
    2016年12月22日 9:57
  • >SurferOnWww 様

    ご指摘ありがとうございます。

    >(1) 今回の話は EF Code First の機能を使って DB の生成も行うということでしょうか? もしそうではなくて、既存の SQL Server データーベースかあるのなら、それから Visual Studio のウィザードを使って EDM を生成して利用してはいかがでしょう。

    はい。今回はEF Code First の機能を使用してデータベースの操作を行おうと考えています。

    もしうまくいきそうになければ EDM の利用も考えてみたいと思います。

    >(2) Exception をキャッチするのは止めた方がいいです。理由は以下の記事を見てください。

    お教え頂きありがとうございます。例外の機構に関しては入門書レベルの知識しかなく、何となくで使っていたところがありました。

    示していただいたサイトで勉強させて頂きます。

    2016年12月22日 10:15
  • > はい。今回はEF Code First の機能を使用してデータベースの操作を行おうと考えています。

    EF Code First というのは、既存の DB がなくスキーマ等も一切決まってない状態で、ゼロから DB を C# 等のコード作成のトライ&エラーに合わせて何度も作り直していく・・・というようなシナリオには有用だと思いますが、そうなんでしょうか?

    質問者さんが作ったアプリを配布して、実環境でユーザーに EF Code First の機能で DB の作成までしてもらうのはほとんど無理ということは理解されているでしょうか?

    上の質問者さんのレスからはそのあたりに大きな誤解があるような気がしますが、そんなことは言われるまでもなく承知の上と言うことでしたら失礼しました。

    2016年12月22日 10:34
  • 理不尽さを感じますよね。
    原因は、
    「デザイナを使って DbContext を作る場合は、(クラスライブラリであっても)作成したプロジェクトの App.config に設定が記述される。」からです。
    これは、DB First でも Code First でも同じです。
    ですから fall1000 さんの達成条件を満たすには、
    クラスライブラリのApp.configをマージする仕組みを作るか、デザイナを使わないなど、それなりにゴリゴリ書く必要があります。
    私はあきらめてます。
    2016年12月23日 4:33
  • >SurferOnWww 様

    >EF Code First というのは、既存の DB がなくスキーマ等も一切決まってない状態で、ゼロから DB を C# 等のコード作成のトライ&エラーに合わせて何度も作り直していく・・・というようなシナリオには有用だと思いますが、そうなんでしょうか?

    はい、今回はデータベースが何もない状況で、一からデータベースを構築し、そのデータベースを利用したデスクトップアプリケーションを作成する、ということをしています。

    今回データベースを構築する前に何か便利なライブラリ等がないか検索を行い、その際に Entity Framework というものがあるということを知り、学習も兼ね、Entity Framework の Code First 機能を用いてデータベース構築を行っています。
    そしてデータベース構築の際に作成した DbContext をクラスライブラリにまとめ、アプリケーションからそのクラスライブラリを使用し、データベースの操作を行いたいと考え、現在制作作業を行っています。
    2016年12月23日 10:37
  • >hihijiji

    ご返信いただきありがとうございます。

    なかなか Entity Frame Work のデータベースアクセス部分をクラスライブラリに切り出し、いろいろなアプリケーションで使いまわすというのは、難しいのですね。

    私自身、データベースやアプリケーションからのデータベース操作に関する知識が浅いまま Entity Framework というものに手を出してしまったため、もう少しデータベースに関しての知識を深めてから手を出すべきであったと少し後悔しています。

    ですが、もう少しなんとか自分の目的を達成できないか頑張ってみようと思います。

    2016年12月23日 10:47
  • 「・1について」と「・3について」は確かにあまり好ましくなさそうですね。でも、

    > ・2について
    > 検証用のテストプログラムに Entity Framework の参照を追加しなければならず、
    > App.config にも Entity Framework に関連する要素を追加しなければならない点が
    > 私の目的と合致しませんでした。
    > できればクラスライブラリを使う側は Entity Framework を使用していることを意識
    > しなくても済むように作りたいと考えています。
     
    は、親の App.config に接続文字列を設定するのはどうしても避けられないものの、それ以外はクリアできると思ったのですがダメでしたか?

    以前、Visual Studio のウィザードを利用して DB First で EDM を作ったサンプルがあったのでそれにちょっと手を加えて調べてみましたが、親のプロジェクトの App.config に接続文字列を設定し、クラスライブラリを参照設定するだけで OK でした。

    上の画像で EdmAdventureWorks と EdmNorthwind が EDM を持つクラスライブラリで、それを利用する親のコンソールアプリが ConsolAppWithEdmLibrary です。親で行ったのは App.config を追加して接続文字列を設定したこととクラスライブラリの参照設定のみです。

    調べてませんけど、DbContext クラスを使った場合とは何か違うのかもしれませんね。 (ウィザードで作る EDM は ObjectContext クラスを使うところは確かに違うのですが)


    • 編集済み SurferOnWww 2016年12月24日 9:03 脱字追記&画像張替え
    2016年12月24日 7:27
  • ちょっと調べてみましたが、EF6, DbContext を利用した場合、System.Data (System.Data.dll 内) の SqlClient ではなくて、EntityFramework.SqlServer.dll の Entity Framework 用の SqlClient を使わなければならないので、クラスライブラリを使用する側にも NuGet で EF6 をインストールしなければならないということのように思われます。

    その理解が合っているとすると、

    > 1.クラスライブラリを参照し、使用するプログラムには Entity Framework の参照を追加せず、
    > App.config にも Entity Framework に関連する要素を記述しない

    というのは無理っぽいですね。少なくとも「使用するプログラム」で Entity Framework 用の SqlClient を使えるようにしなければならないわけですから。

    明日、もう少し調べてみます。

    2016年12月25日 15:17
  • > SurferOnWww 様

    返信が遅くなってしまい申し訳ありません。

    >ちょっと調べてみましたが、EF6, DbContext を利用した場合、System.Data (System.Data.dll 内) の SqlClient ではなくて、EntityFramework.SqlServer.dll の Entity Framework 用の SqlClient を使わなければならないので、クラスライブラリを使用する側にも NuGet で EF6 をインストールしなければならないということのように思われます。

    そのような仕様になっていたのですか…
    では今回のような目的を達成させるには DbContext を使用せずに、ObjectContext を使用してクラスライブラリを作成しなければならないということなのでしょうか。

    ObjectContext は EF4.0 、.Net 4.0 の EF5.0 までで使用されていたものという記事を見たのですが、EF6.0 で使用しても問題ないのでしょうか。

    質問してばかりで申し訳ないのですが、よろしくお願い致します。
    2016年12月26日 0:34
  • > ObjectContext は EF4.0 、.Net 4.0 の EF5.0 までで使用されていたものという記事を見たのですが、
    > EF6.0 で使用しても問題ないのでしょうか。

    上の私のレスで書いたサンプルは Visual Studio 2010 Professional で作ったものですが、EdmAdventureWorks, EdmNorthwind(EDM を持つクラスライブラリ)にも、ConsolAppWithEdmLibrary(クラスライブラリ利用する親のコンソールアプリ)にも EF6 は使ってないです。

    クラスライブラリの方には、デザイナで EDM を追加すると自動的に System.Data.Entity が参照設定に追加されますが、それは EF4 のものです。

    NuGet パッケージ管理で EF6 をインストールした後 EDM を追加するとどうなるかは試してませんので分かりませんが、そもそも EF6 をインストールする必要はないので、試す必要もないかと。

    2016年12月26日 2:15
  • EF6, DbContext を利用した場合について、先の私のレスで「明日、もう少し調べてみます」と書きました。

    なので、自分でも EF6, DbContext を使ったサンプルを作ってどうなるか試してみました。環境は Visual Studio 2010 Professional, .NET 4, SQL Server 2008 Express でコンソールアプリ+クラスライブラリです。

    結果、質問者さんが経験された「2の結果」とほぼ同じことが再現できただけですが、話の辻褄は合うのが分かったので、自分的には、

    > 「使用するプログラム」で Entity Framework 用の SqlClient を使えるようにしなければならない

    というところに納得しています。

    具体的にどのようなことをしたかとその結果は以下の通りです。

    (1) Visual Stusio のテンプレートを利用してコンソールアプリを自動生成。同じソリューション内にクラスライブラリを追加。

    (2) クラスライブラリには NuGet パッケージ管理で EF6 をインストールし、DbContext クラスを使って質問者さんが作ったものと同様な形で SQL Server 2008 Express の DB にアクセスするものを作成。

    (3) クラスライブラリの App.config の中身(EF6 をインストールした際自動生成されたもの)はコメントアウト。

    (4) コンソールアプリは Visual Studio で自動生成されたままの状態(EF6 のインストールは行いません)。

    (5) コンソールアプリには、上記 (2) で作成したクラスライブラリへの参照を追加し、App.config を追加してその中に接続文字列を設定し、以下のコードを記述。

    namespace ConsoleAppDbContext
    {
        class Program
        {
            static void Main(string[] args)
            {
                string connString = ConfigurationManager.ConnectionStrings["BooksConnection"].ConnectionString;
                BooksAccessor books = new BooksAccessor(connString);
    
                connString = ConfigurationManager.ConnectionStrings["ParentConnection"].ConnectionString;
                ParentAccessor parent = new ParentAccessor(connString);
    
                var list1 = books.GetBookList();
                var list2 = parent.GetParentList();
    
                foreach (var item in list1)
                {
                    Console.WriteLine("Isbn: {0}, Title: {1}", item.Isbn, item.Title);
                }
    
                foreach (var item in list2)
                {
                    Console.WriteLine("Id: {0}, Name: {1}", item.Id, item.Name);
                }
            }
        }
    }

    (6) この状態で実行すると、books.GetBookList() メソッドの中で InvalidOperationException がスローされる。エラーメッセージは下記:

    No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SqlClient'. Make sure the provider is registered in the 'entityFramework' section of the application config file. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.

    new BooksAccessor(connString) および new ParentAccessor(connString) では接続文字列で指定される SqlClient を使うということで初期化には成功。ところが、books.GetBookList() で DB に接続に行くところで Entity Framework provider(Entity Framework 用の SqlClient)が見つからないということで例外がスローされています。

    (7) 上のエラーメッセージに従って、コンソールアプリの App.config に 'entityFramework' section(クラスライブラリの App.config に NuGet パッケージ管理で EF6 をインストールした際自動生成されたものと同じ)を追加。この状態で実行すると、new BooksAccessor(connString); で InvalidOperationException がスローされる。エラーメッセージは下記:

    The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application.

    App.config で指定されたプロバイダ(Entity Framework 用の SqlClient)がロードできないということで例外がスローされています。

    コンソールアプリの bin フォルダには EntityFramework.dll は自動的にコピーされているが、Entity Framework 用の SqlClient がある EntityFramework.SqlServer.dll は存在しないのでロードできないということらしい。(何故 EntityFramework.dll だけ自動的にコピーされるのかは分かりません)

    (8) コンソールアプリにも NuGet パッケージ管理で EF6 をインストール。自動的に EF6 の EntityFramework と EntityFramework.SqlServer がローカルコピー True で参照設定に追加される。その状態で実行すると問題なく実行され、Console.WriteLine で DB の内容が期待通りにコンソールに出力される。

    • 編集済み SurferOnWww 2016年12月26日 3:26 誤記訂正:(7) ・・・クラスライブラリの ⇒ (7) ・・・コンソールアプリの
    • 回答としてマーク fall1000 2016年12月26日 4:52
    2016年12月26日 2:51
  • 余計なお世話かもしれませんが・・・

    もし、質問者さんの作成しようとしているクラスライブラリが、DB からデータを取得してそれをカスタムクラスのコレクションとして渡すということだけの目的に使うのであれば、Entity Framework を使うのが適切かどうか考え直した方がいいかもしれません。

    ADO.NET のもっとプリミティブな機能(SqlConnection, SqlCommand, SqlDataReader など)を使うだけでその目的は果たせると思いますので。

    • 回答としてマーク fall1000 2016年12月26日 4:52
    2016年12月26日 3:03
  • > SurferOnWww 様

    お忙しい中、ご検証頂きありがとうございます。

    >もし、質問者さんの作成しようとしているクラスライブラリが、DB からデータを取得してそれをカスタムクラスのコレクションとして渡すということだけの目的に使うのであれば、Entity Framework を使うのが適切かどうか考え直した方がいいかもしれません。

    >ADO.NET のもっとプリミティブな機能(SqlConnection, SqlCommand, SqlDataReader など)を使うだけでその目的は果たせると思いますので。

    たしかに仰る通りだと思います。Entity Framework 6.0 を使用することに固執しすぎていたかもしれません。
    今回の目的には Entity Framework 6.0 の使用は適さないということで、他の方法(SurferOnWww 様に上げていただいた SqlConnection, SqlCommand, SqlDataReader などを利用する、もしくはEF4.0 の EDM を利用した DB First で開発を行う)でAccessor クラスを作り直そうと思います。

    SurferOnWww 様を始め、長い時間、私の質問にお付き合い頂きありがとうございました。
    2016年12月26日 4:51