2018年9月28日金曜日

EntityFrameworkでSQLiteを使ってみた!

Entity FrameworkもSQLiteもそれぞれ使ったことはあるのですが、今回初めてEntity FrameworkからSQLiteを使ったので
その方法をメモ。

まず、System.Data.SQLite.EF6.Migrationsパッケージをインストールします。
この時、もれなく

・EntityFramework
・System.Data.SQLite.Core
・System.Data.SQLite.EF6
・System.Data.SQLite.Linq
・System.Data.SQLite

もインストールされます。

DbContextクラス、エンティティクラスを作成します。

public class SampleDbContext : DbContext
{
    public DbSet<user> Users { get; set; }

    public SampleDbContext() : base("name = Sample")
    {
    }
}

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }
}


App.configに接続文字列を追加します。


<connectionStrings>
  <add name="Sample" connectionString="Data Source=C:\xxxxx\sample.db;" providerName="System.Data.SQLite.EF6" />
</connectionStrings>

enable-migrations/add-migrationを実行すると、エラーが発生しました。

> enable-migrations
No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SQLite'. Make sure the provider is registered in the 'entityFramework' section of the application config file. 

> add-migration InitialCreate
No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SQLite'. Make sure the provider is registered in the 'entityFramework' section of the application config file. 


エラー内容に従って、App.configに設定を追加します。


<providers>
  <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
  <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
</providers>

再び、add-migrationを実行します。

> add-migration InitialCreate
System.ObjectDisposedException: 破棄されたオブジェクトにアクセスできません。
オブジェクト名 'SQLiteConnection' です。
   場所 System.Data.SQLite.SQLiteConnection.CheckDisposed()
   場所 System.Data.SQLite.SQLiteConnection.get_State()
   場所 System.Data.Entity.Internal.RepositoryBase.CreateConnection()
   場所 System.Data.Entity.Migrations.History.HistoryRepository.QueryExists(String contextKey)
   場所 System.Data.Entity.Migrations.History.HistoryRepository.Exists(String contextKey)
   場所 System.Data.Entity.Migrations.History.HistoryRepository.GetPendingMigrations(IEnumerable`1 localMigrations)
   場所 System.Data.Entity.Migrations.DbMigrator.GetPendingMigrations()
   場所 System.Data.Entity.Migrations.DbMigrator.Scaffold(String migrationName, String namespace, Boolean ignoreChanges)
   場所 System.Data.Entity.Migrations.Design.MigrationScaffolder.Scaffold(String migrationName, Boolean ignoreChanges)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.Scaffold(MigrationScaffolder scaffolder)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.RunCore()
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
破棄されたオブジェクトにアクセスできません。
オブジェクト名 'SQLiteConnection' です。


いろいろ調べてみましたが、原因がよく分かりません。
(SQLServer用接続文字列の場合、エラーは発生しません。)
あきらめて、DbContextクラスのデフォルトコンストラクタで、接続文字列を指定することにしました。

public SampleDbContext() : base(new SQLiteConnection($"Data Source = {Properties.Settings.Default.DatabaseFilePath};"),
false)
{
}


三度、add-migrationを実行します。

> add-migration InitialCreate
No MigrationSqlGenerator found for provider 'System.Data.SQLite'. Use the SetSqlGenerator method in the target migrations configuration class to register additional SQL generators.


Configurationクラスのコンストラクタにメソッド呼び出しを追加します。

public Configuration()
{
    AutomaticMigrationsEnabled = false;
    SetSqlGenerator("System.Data.SQLite", new SQLiteMigrationSqlGenerator());
}


add-migration/update-databaseを実行するとデータベースファイルが作成されました。
余談ですが、データベースファイルパスにフルパスではなくファイル名のみ指定すると、
下記のようなエラーが発生するのでご注意を。

> update-database
System.Data.Entity.Core.ProviderIncompatibleException: CreateDatabase is not supported by the provider.
   場所 System.Data.Entity.Core.Common.DbProviderServices.DbCreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   場所 System.Data.Entity.Core.Common.DbProviderServices.CreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   場所 System.Data.Entity.Core.Objects.ObjectContext.CreateDatabase()
   場所 System.Data.Entity.Migrations.Utilities.DatabaseCreator.Create(DbConnection connection)
   場所 System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   場所 System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   場所 System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   場所 System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
CreateDatabase is not supported by the provider.


またいつか、どこかで。

2018年9月21日金曜日

IBM Cloud上でCloudantを使ってみた!(後編)

前回、ローカルからCloudantデータベースにアクセスしました。
今回は、WebAPIでCloudantを使ってみます。


@app.route('/api/adduser', methods = ['POST'])
def AddUser():

    if request.is_json:
        if 'user' not in request.json:
            user = ''
        else:
            user = str(request.json['user'])

        client = Cloudant('d.....', 'a.....', url='https://d.....')
        client.connect()

        my_database = client['users']

        if user not in my_database:
            my_database.create_document({'_id' : user})

        client.disconnect()

        return jsonify(status = 'success')
    else:
        return jsonify(status = 'error')


{"user":"Taro"}というデータを2回送信したところ、1回目は登録され、2回目にエラーが発生しました。


requests.exceptions.HTTPError: 409 Client Error: Conflict conflict Document update conflict. for url:https://.....


デバッグ用コードを入れ、いろいろ試してみたところ、どうも存在チェックが正常に動作していないようです。
存在チェック前に全データを出力する処理を入れるとなぜだかうまくいきました。


@app.route('/api/adduser', methods = ['POST'])
def AddUser():

    if request.is_json:
        if 'user' not in request.json:
            user = ''
        else:
            user = str(request.json['user'])

        client = Cloudant('d.....', 'a.....', url='https://d.....')
        client.connect()

        my_database = client['users']

        #########################################
        # usersの全データを出力
        for doc in my_database:
            print(doc)
        #########################################

        if user not in my_database:
            my_database.create_document({'_id' : user})

        client.disconnect()

        return jsonify(status = 'success')
    else:
        return jsonify(status = 'error')


使用したpython-cloudantのバージョンは2.3.1でした。
どうやらpython-cloudantのバグのようで、少なくとも2.9.0ではこの問題は修正されていました。

またいつか、どこかで。

2018年9月14日金曜日

IBM Cloud上でCloudantを使ってみた!(中編)

前回はCloudantサービスとデータベースを作成しました。
今回は、ローカルからデータを登録・取得します。

Cloudantデータベースにアクセスするには、ライブラリをインストールする必要があります。
私の環境はWindowsですので、下記のコマンドを実行してインストールしました。


> py -m pip install cloudant



接続するには、ユーザー名、パスワード、URLが必要です。
「資格情報の表示」から確認しておきましょう。

















python-cloudantのドキュメントページには、データベースオープン、ドキュメント作成などのソースが載っています。
それらを参考に、「引数で指定されたユーザーが存在しなければ登録する」というコードを作成しました。


from cloudant.client import Cloudant
import sys

user = sys.argv[1]

client = Cloudant('d.....', 'a.....', url = 'https://d.....')
client.connect()

database = client['users']
if user not in database:
    database.create_document({'_id' : user})

for document in database:
    print(document)

client.disconnect()


実行してみます。


> py cloudantsample.py Taro
{'_id': 'Taro', '_rev': '1-.....'}

> py cloudantsample.py Hanako
{'_id': 'Hanako', '_rev': '1-.....'}
{'_id': 'Taro', '_rev': '1-.....'}

> py cloudantsample.py Taro
{'_id': 'Hanako', '_rev': '1-.....'}
{'_id': 'Taro', '_rev': '1-.....'}



ダッシュボードで確認すると、データが追加されています。











次回はクラウド上でデータ登録・取得してみます。

またいつか、どこかで。

2018年9月7日金曜日

IBM Cloud上でCloudantを使ってみた!(前編)

IBM Cloud上でCloudantを使用して、データを登録・取得してみました。
その方法をメモ。

まず、Cloudantサービスを作成します。
「データベース」の中から「Cloudant」を選択し、サービスを作成します。
サービス作成後、アプリケーションに接続しておきます。

次に、データベースを作成します。
Cloudantのダッシュボードを開きます。

データベース名を入力します。
今回は「users」というデータベースを作成します。














データベースが作成されました。














データの登録・取得は次回。

またいつか、どこかで。