読者です 読者をやめる 読者になる 読者になる

RingoTabetterやRingoTabetterAPIを作るときに調べたり悩んだことのメモ

C# Dapper Heroku

以前、C#で食べたリンゴの割合をグラフ化しましたが、その後いろいろと手を出していたら振り返りをするのを忘れていました。

ここらへんで振り返っておかないと内容を忘れそうな気がしたので、その時に実装した内容と悩んでいることをメモしておきます。

 

Twitterまわり

Monoで使えるC#Twitterライブラリについて

当初は、使ったことのあるLinqToTwitterを使おうと思いましたが、Herokuでビルドした時に、

remote: Errors:
remote:
remote: /tmp/build_5de09a93de4910a3a1fb6652998ce0a4/NancyOwinHeroku-sample.sln (default targets) ->
remote: (Build target) ->
remote: /tmp/build_5de09a93de4910a3a1fb6652998ce0a4/DoubleApp/DoubleApp.csproj (default targets) ->
remote: /tmp/build_5de09a93de4910a3a1fb6652998ce0a4/mono/lib/mono/4.5/Microsoft.CSharp.targets (CoreCompile target) ->
remote:
remote:         Program.cs(7,7): error CS0246: The type or namespace name `LinqToTwitter' could not be found. Are you missing an assembly referenc
remote:
remote:          2 Warning(s)
remote:          1 Error(s)
remote:
remote: Time Elapsed 00:00:03.4269890
remote:
remote:  !     Push rejected, failed to compile .NET app
remote:
remote: Verifying deploy...
remote:
remote: !       Push rejected to afternoon-reef-5008.

というエラーが出てきてビルドできませんでした。

 
仕方ないので、他のライブラリを探してみたところ、Monoをサポートしているとの記載があったCoreTweetが良さそうでした。
CoreTweet/CoreTweet · GitHub

公式Wikiにも日本語バージョンの記載があり、扱いやすいライブラリでした。

 

max_idsince_idについて

勘違いをしていた部分があったので、以下が参考になりました。
Twitter API Timeline解説 - のんびりしているエンジニアの日記

 

PostgreSQLまわり

MERGEがない

last_searchesという最後に収集したツイートの情報を入れるテーブルに対して、INSERT or UPDATE をしようと思いましたが、PostgreSQL9.3系にはMERGEがないとのことでした。

 

日付/時刻データ型から月だけを抜き出す

月別品種別数量を取得する際、tweet_atという日付/時刻データ型から月だけを抽出する必要がありました。

調べてみたところ、date_part関数を使えば、こんな感じのSQLで取得できました。
日付/時刻関数と演算子 - PostgreSQL 9.3.2文書

 

YAMLの読込みについて

りんごの品種はYAMLで管理することにして、NuGetパッケージのYamlDotNetを使いました。
aaubry/YamlDotNet · GitHub

 
使い方は以下の記事が参考になりました。なお、version3.0.0にて修正が入り、現在のYamlDotNetは1つのNuGetパッケージに統合されています。
C#でYAMLを使えるか試してみた | OPC Diary

ここらへんで使っていますが、使い方については特に変更はありませんでした。

 

これでいいのか悩んでいる部分

Herokuでの正規表現について

先ほど試してみたら再現しなかったので勘違いかもしれませんが、公開しているソースコードに関係するので一応残しておきます。

RingoTabetterAPIでは[リンゴ]で始まるツイートに含まれるリンゴの品種を数え上げています。そのため、CoreTweetで取得したツイートに対し、^[リンゴ]という正規表現にて対象のものを絞り込んでいます。

ただ試してみたところ、

  • 手元のWindows7環境ではツイートを絞り込めた
  • Heroku環境では絞り込めない

ということが発生しました。

文字コードの違いだろうかと思いいろいろとやってみましたが、最終的にはこのあたりのように、

  • ^[リンゴ]UTF-8バイト列を指定
  • 上記のUTF-8バイト列をUTF-8で文字列化

として、

private readonly byte[] filterBytes = new byte[]
{
    // フィルターである`^[リンゴ]`のUTF-8バイト列
    0x5e, 0x5c, 0x5b, 0xe3, 0x83, 0xaa, 0xe3, 0x83, 0xb3, 0xe3, 0x82, 0xb4, 0x5c, 0x5d
};
var filterUtf8 = System.Text.Encoding.UTF8.GetString(filterBytes);

 
というようなことをやり、最後にそれをRegexに渡して絞り込むようにしました。

 
ただ、別途検証用コードを書いたところ再現しないこともあったので、また調べてみます。

 

Highchartsのarea chartsについて

月別品種別数量をarea chartを使って表現しましたが、月別の数量をdataとして与える場合、12個の要素を持つ配列にする必要があります。

データベースでは月別品種別数量を縦持ちしているため、どこで横持ちである配列に入れ替えようかと考えましたが、今回は以下のような感じにしました。

RingoTabetterApi/RingoTabetterApi/Models/Highcharts.cs

// Highchartsで使うため、月別数量を縦持ちしているDBデータを、配列Quantitiesで横持ちにする
var result = new Dictionary<string, AppleByMonthsPoco>();
foreach (var s in sum)
{
    if (result.ContainsKey(s.Name))
    {
        result[s.Name].Quantities[s.Month - 1] = s.Quantity;
    }
    else
    {
        var color = apple.Cultivar.Items.Where(c => c.Name == s.Name).FirstOrDefault().Color ?? "Black";
        var row = new AppleByMonthsPoco();
        row.Name = s.Name;
        row.Quantities[s.Month - 1] = s.Quantity;
        row.Color = color;
        result.Add(s.Name, row);
    }
}
_totalByMonth = result.Values;

 

Dapperでのデータベースアクセスについて

データベースへアクセスする各メソッドにて、connectionのOpenやCloseを実装するのが手間だったので、

  • ModelBaseクラス
    • connectionのOpen/Closeを実行
    • 引数のラムダ式に書いてあるSQLを実行
  • データベースアクセスが発生するクラス

みたいな感じで作りました。

 
実際のコードは以下の通りです。

ModelBase

protected IEnumerable<T> Query<T>(Func<IEnumerable<T>> func)
{
    IEnumerable<T> result;
    // トランザクションを使っている場合
    if (connection.State == ConnectionState.Open)
    {
        result = func();
        return result;
    }
    // 単独の場合
    using (connection)
    {
        try
        {
            connection.Open();
            result = func();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            throw;
        }
        finally
        {
            connection.Close();
        }
    }
    return result;
}

 
使用例(Appleクラス)

public IEnumerable<TotalApplePoco> AddUp()
{
    Func<IEnumerable<TotalApplePoco>> func = () =>
    {
        var sql = @"SELECT COUNT(name) as quantity, name FROM apples GROUP BY name ORDER BY quantity DESC";

        var result = transaction == null ?
            connection.Query<TotalApplePoco>(sql) :
            connection.Query<TotalApplePoco>(sql, transaction: transaction);
        return result;
    };

    var apples = Query(func);
    return apples;
}

 
ただ、作り方として良かったのかどうかは分かりません...