以前、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にも日本語バージョンの記載があり、扱いやすいライブラリでした。
- Home(日本語) · CoreTweet/CoreTweet Wiki · GitHub)
- C#でTwitterアプリを作る 第0回 - らこらこブログ
- .NET Twitter ライブラリ「CoreTweet」 - Qiita
max_id
とsince_id
について
勘違いをしていた部分があったので、以下が参考になりました。
Twitter API Timeline解説 - のんびりしているエンジニアの日記
PostgreSQLまわり
MERGEがない
last_searches
という最後に収集したツイートの情報を入れるテーブルに対して、INSERT or UPDATE をしようと思いましたが、PostgreSQL9.3系にはMERGEがないとのことでした。
- サポートされていない機能 - PostgreSQL 9.3.2文書
- How do I do an UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL? - Stack Overflow
日付/時刻データ型から月だけを抜き出す
月別品種別数量を取得する際、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環境では絞り込めない
ということが発生しました。
文字コードの違いだろうかと思いいろいろとやってみましたが、最終的にはこのあたりのように、
として、
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
クラス- データベースアクセスが発生するクラス
みたいな感じで作りました。
実際のコードは以下の通りです。
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; }
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; }
ただ、作り方として良かったのかどうかは分かりません...