くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2016年04月

C#でZipファイルをフォルダ階層つけて作る

一括ダウンロードみたいなのをしたくなったので、Webアプリ側でZip圧縮したくなった。割と最近の.Netには標準でCompression名前空間にZipまわりのものがあるので使ってみる。以下ソース。

using System.IO.Compression;

using (var z = ZipFile.Open(@"C:\Test\hoge.zip", ZipArchiveMode.Update))
{
    z.CreateEntryFromFile(@"C:\Source\fuga.txt", @"fuga.txt",CompressionLevel.Optimal);
    z.CreateEntryFromFile(@"C:\Source\moe.txt", @"moe.txt", CompressionLevel.Optimal);
}

解凍後にフォルダ階層もつけておきたい場合は以下の感じ

using System.IO.Compression;

using (var z = ZipFile.Open(@"C:\Test\hoge.zip", ZipArchiveMode.Update))
{
    z.CreateEntryFromFile(@"C:\Source\fuga.txt", @"folder1\fuga.txt",CompressionLevel.Optimal);
    z.CreateEntryFromFile(@"C:\Source\moe.txt", @"folder2\moe.txt", CompressionLevel.Optimal);
}

で、これだと日本語のファイル名が文字化けする。なので文字コード指定する。

using System.IO.Compression;
Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
using (var z = ZipFile.Open(@"C:\Test\hoge.zip", ZipArchiveMode.Update, sjisEnc))
{
    z.CreateEntryFromFile(@"C:\Source\fuga.txt", @"folder1\fuga.txt",CompressionLevel.Optimal);
    z.CreateEntryFromFile(@"C:\Source\moe.txt", @"folder2\moe.txt", CompressionLevel.Optimal);
}

あーあと、ZipFileとかのクラスを使う場合は、「System.IO.Compression」と「System.IO.Compression.FileSystem」を参照に追加しておかないとダメみたいよ。

VisualStudioのGitでプロキシエラー

この前VisualStudioのソース管理で同期したら、リモートリポジトリにつなぎに行くところで、急にプロキシエラーになった。エラーは⇒の感じ「リモート サーバーがエラーを返しました: (407) プロキシ認証・・・」

Nuget更新した影響かしら。プロキシエラーと言われましても、どこをいじればいいやら。。。と色々試した結果、「コマンドラインからGitを使って一回リモートリポジトリにアクセスする」とうまくいった。 適当にCloneとかでいいのでコマンドでやると、プロキシじゃなくてリモートリポジトリのパスワード聞かれるから、パスワード入れて普通にCloneしてやる。そうすと、VisualStudio側の同期がうまくいくようになる。

なかなか不思議な感じですね。VisualStudio自体、プロキシの設定はIEの設定を見るらしいし、Gitもそうかなと思ってやってみたらうまくいった。一回通しておくと、キャッシか何かに残ってそれを見るのかしら。。。

MongoDBをC#から使う その4

その4。大量インサートとかインデックス。↓ソース

//ためし用のクラス
public class TestDoc
{
    public ObjectId Id { get; set; }
    public int 連番 { get; set; }
    public string ボディ { get; set; }
}
//1000万件オブジェクト作って、普通にInsertMany
List documents = new List();
for (int i = 0; i < 10000000; i++)
{
    documents.Add(
            new TestDoc { 連番 = i, ボディ ="あいうえおかきくけこ" }
        );
}
collection.InsertMany(documents);//2m57s789
//1000万件オブジェクト作って、BulkInsert
var models = new List>();
for (int i = 0; i < 10000000; i++)
{
    models.Add(
            new InsertOneModel(new TestDoc { 連番 = i, ボディ = "あいうえおかきくけこ" })
        );
}
collection = database.GetCollection("TestDocBulk");
collection.BulkWrite(models);//2s41m349
//1000万件を10万件毎にBulkInsert
var models = new List>();
for (int i = 0; i < 10000000; i++)
{
    models.Add(
            new InsertOneModel(new TestDoc { 連番 = i, ボディ = "あいうえおかきくけこ" })
        );
    if(i % 100000 == 0 && i != 0)
    {
        collection.BulkWrite(models);
        models.Clear();
    }
}
//2m37s157

InsertManyとBulkWriteに大差がないとゆー。Bulkは何かで使えるのだろーか。 もうちょっと複雑なドキュメントとかだと意味あるのかしら。。。 ある程度のまとまりに分けてインサートすると、メモリがアホみたいに増えないので、時間的な差があまりなさそーだし、やっぱこっちのほうがいいかな。次はFind。Bulkで作ったコレクションで試す(1000万レコード)。

//インデックスなし
var finddocument = collection.Find(new BsonDocument { { "連番", 12345 } });
long count = finddocument.Count();// ⇒ 4s045
List listTmp = finddocument.ToList();// ⇒ 3s686
//インデックスつける
collection.Indexes.CreateOne(new BsonDocument("連番", 1));
//同じのもう一回
var finddocument = collection.Find(new BsonDocument { { "連番", 12345 } });
long count = finddocument.Count();// ⇒ 0s088
List listTmp = finddocument.ToList();// ⇒ 0s013

インデックス無いとやっぱ使いものにならないす。インデックスつけてよーやくいい感じ。

なんとなくつかめてきたので、後は実地でいろいろ試してみよう。

MongoDBをC#から使う その3

MongoDBをC#から使う その3。今回はLinq。なんだけど、Linq使うのは色々準備しないとだめですね。 とりあえずソース。

//ためしドキュメント用のクラス
public class LinqTest
{
    public ObjectId Id { get; set; }
    public int 連番 { get; set; }
    public string ボディ { get; set; }
}

public void LinqTest()
{
    string connectionString = "mongodb://localhost";
    MongoClient client = new MongoClient(connectionString);
    var database = client.GetDatabase("foo");
    var collection = database.GetCollection("LinqTest");

    //ためし用コレクションの作成
    List documents = new List();
    for (int i = 0; i < 100; i++)
    {
        documents.Add(
                new LinqTest{連番 = i, ボディ = i.ToString()}
            );
    }
    collection.InsertMany(documents);

    //Linqでfind
    var query =
            from x in collection.AsQueryable()
            where x.連番 == 10
            select x;

    foreach (var tmp in query)
    {
        Console.WriteLine("{0} : {1}",tmp.連番,tmp.ボディ);
    }
}

Linqで検索はもちろん便利なんだけど、動的なWhere句作ったりが割と大変。 あと、本当に出来ないとなると、割と致命的なのがBsonDocumentの検索がLinqだとできないっぽい? あんま調べてない。海外のサイトでもちらほら質問が出てるけど、どれも解決してないっぽい。 知ってる人いたら教えてください。要調査と実験。

で、Linqは便利だけど、そのままだとどうしても動的な条件がやりづらい。なので、いろいろ準備してあげる必要あり。それはまた今度。次は性能面を見る。

MongoDBをC#から使う その2

前回に続き、 MongoDBをC#から試す。今回はスキーマレス的なCRUD。とりあえずソースは↓

public void TestCRUD()
{
    string connectionString = "mongodb://localhost";
    MongoClient client = new MongoClient(connectionString);
    var database = client.GetDatabase("foo");
    var collection = database.GetCollection("freedom");

    ///////////////////////////////////////////////////////////////////////////////////////
    //追加系
    ///////////////////////////////////////////////////////////////////////////////////////
    Console.WriteLine("\n■追加系\n");
    var document = new BsonDocument
    {
        { "free01", 1       },
        { "free02", "ABCDE" },
        { "free03", "自由"  },
        { "自由04", "FREE"  }
    };
    //1件追加
    collection.InsertOne(document);

    //↑で追加したのとは違う構成
    List documents = new List();
    documents.Add(
            new BsonDocument
            {
                {"HOGE",10 },
                {"FUGA","MOE" }
            }
        );
    //同じオブジェの中でも違う定義にする
    documents.Add(
            new BsonDocument
            {
                { "なにか" , "DOC" },
                { "もっと何か", new BsonDocument { { "BSON","ビーソン"}, { "JSON",1} } }
            }
        );
    //複数件追加
    collection.InsertMany(documents);

    ///////////////////////////////////////////////////////////////////////////////////////
    //参照系
    ///////////////////////////////////////////////////////////////////////////////////////
    Console.WriteLine("\n■参照系\n");
    //追加した中身の確認
    var finddocument = collection.Find(new BsonDocument());
    foreach (var tmp in collection.Find(new BsonDocument()).ToList())
    {
        Console.WriteLine(tmp);
    }
    //フィルターつけてFind
    finddocument = collection.Find(new BsonDocument { { "HOGE",10} });
    foreach (var tmp in finddocument.ToList())
    {
        Console.WriteLine(tmp);
    }

    ///////////////////////////////////////////////////////////////////////////////////////
    //更新系
    ///////////////////////////////////////////////////////////////////////////////////////
    Console.WriteLine("\n■更新系\n");
    var updateElem = Builders.Update.Set("FUGA", "FUGAFUGA");
    var result = collection.UpdateMany(new BsonDocument { { "HOGE", 10 } }, updateElem);//マッチした中の先頭1件のみ更新の場合はUpdateOne
    if (result.IsModifiedCountAvailable)
    {
        Console.WriteLine("【更新結果】マッチ:{0}件 更新:{1}件", result.MatchedCount, result.ModifiedCount);
    }
    //更新確認
    finddocument = collection.Find(new BsonDocument { { "HOGE", 10 } });
    foreach (var tmp in finddocument.ToList())
    {
        Console.WriteLine(tmp);
    }
    //既存のドキュメントにフィールドを追加する場合
    updateElem = Builders.Update.Set("AddElem", "足したよ");
    result = collection.UpdateMany(new BsonDocument { { "HOGE", 10 } }, updateElem);
    if (result.IsModifiedCountAvailable)
    {
        Console.WriteLine("【更新結果】マッチ:{0}件 更新:{1}件", result.MatchedCount, result.ModifiedCount);
    }
    //更新確認
    finddocument = collection.Find(new BsonDocument { { "HOGE", 10 } });
    foreach (var tmp in finddocument.ToList())
    {
        Console.WriteLine(tmp);
    }
    //既存のフィールドを別のオブジェクトで置き換える
    updateElem = Builders.Update.Set("AddElem", new BsonDocument { { "置換","ReplaceVal" }, { "この人","置換です"} });
    result = collection.UpdateMany(new BsonDocument { { "HOGE", 10 } }, updateElem);
    if (result.IsModifiedCountAvailable)
    {
        Console.WriteLine("【更新結果】マッチ:{0}件 更新:{1}件", result.MatchedCount, result.ModifiedCount);
    }
    //更新確認
    finddocument = collection.Find(new BsonDocument { { "HOGE", 10 } });
    foreach (var tmp in finddocument.ToList())
    {
        Console.WriteLine(tmp);
    }

    ///////////////////////////////////////////////////////////////////////////////////////
    //削除系
    ///////////////////////////////////////////////////////////////////////////////////////
    Console.WriteLine("\n■削除系\n");
    var delresult = collection.DeleteMany(new BsonDocument { { "HOGE", 10 } });//マッチした中の先頭1件のみ削除の場合はDeleteOne
    Console.WriteLine("削除件数:{0}", delresult.DeletedCount);
    //削除確認
    finddocument = collection.Find(new BsonDocument());
    foreach (var tmp in collection.Find(new BsonDocument()).ToList())
    {
        Console.WriteLine(tmp);
    }
}

基本的にはBsonDocumentでオブジェクト作って投げればいいだけですね。

ちょっと触ってて思ったのが、「これは危険だ」というところ。 特に既存のオブジェクトの置き換えはやヴぁい。気付かずに同じ名前で更新しちゃったら中身丸ごと変えられちゃうのね。

便利といえば便利だけども、怖い。

うすうす気付いてはいたけれども、結局のところはスキーマの管理が大事という 結論に至りそうなオチが見えてまいりました。

個別のフィールドを使う場合は下の感じになるのかしら。。。

string connectionString = "mongodb://localhost";
MongoClient client = new MongoClient(connectionString);
var database = client.GetDatabase("foo");
var collection = database.GetCollection("freedom");

var finddocument = collection.Find(new BsonDocument());
foreach (var tmp in collection.Find(new BsonDocument()).ToList())
{
    //ドキュメント全体
    Console.WriteLine("Document:",tmp);
    //フィールドの名前
    foreach (string tmp2 in tmp.Names)
    {
        Console.WriteLine("Name:{0}",tmp2);
    }
    //値の中身
    foreach (var tmp2 in tmp.Values)
    {
        Console.WriteLine("Value:{0}",tmp2.ToString());
    }
    //Elementでアクセス
    foreach (var tmp2 in tmp.Elements)
    {
        Console.WriteLine("{0}:{1}", tmp2.Name, tmp2.Value);
    }
    Console.WriteLine("Next...\n");
}

ついでに、普通のクラスのプロパティにBsonDocumentを入れておけば、そこだけスキーマレスに扱える模様。↓の感じ。

//クラス定義
public class Mixing
{
    public ObjectId Id { get; set; }
    public string str1 { get; set; }
    public BsonDocument Meta { get; set; }
    public BsonDocument Meta2 { get; set; }
    [BsonExtraElements]
    public BsonDocument ExMeta { get; set; }
}

//オブジェクト作る例
for (int i = 0; i < 20; i++)
{
    documents.Add(
        new Mixing
        {
            str1 = i.ToString(),
            Meta = new BsonDocument("metaId",i.ToString()),
            Meta2 = new BsonDocument{{ "x", 203 },{ "y", 102 }},
            ExMeta = new BsonDocument("ExMetaId",i*10)
        }
        );
}

//DB上は↓の感じで入る
{ 
"_id"      : ObjectId("5706aadb6446e821943aecf3"),
"str1"     : "10",
"Meta"     : { "metaId" : "10" },
"Meta2"    : { "x" : 203, "y" : 102 },
"ExMetaId" : 100 
}

結局はBsonDocumentでやれという話。注目は[BsonExtraElements]を使ってるプロパティ。 クラスのプロパティ名じゃなく、BsonDocumentで指定した名前になる。ちなみに[BsonExtraElements]の定義は「using MongoDB.Bson.Serialization.Attributes;」のusing入れておかないと使えない。はまった。

で、この動きは結構危険。別のプロパティと名前かぶったらどーなんでしょ。っていうか本家でもdangerって言ってるし、やっぱ危ないんでしょー。

まぁ使い方次第ということでしょうか。使いどころがあるかは知らないけど。。。

とりあえず、基本的な動きは見れたとゆーところですが、このままじゃ使いにくそー。 やりやすい何かを用意しておかないと厳しいかな。。。 次は、Linqをためしましょ。

MongoDBをC#から使う その1

C#からMongoDBを触るのを試してる。何となく検索して出てくる日本語の情報が古い気がする。 MongoDB.DriverはNuGetで取れる「2.2.3」を使用。まずは、基本的なとろこから。

  • 接続
//接続文字列(MongoDBをインストールしてデフォルトのままの場合)
string connectionString = "mongodb://localhost";
//MongoDBに接続
MongoClient client = new MongoClient(connectionString);
//データベースを選ぶ(use foo)
var database = client.GetDatabase("foo");
//コレクションを決める
var collection = database.GetCollection("bar");
//とりあえず最初の一件を取得()
var finddocument = collection.Find(new BsonDocument()).FirstOrDefault();
  • CRUD

↓ためし用のクラス。

public class Heroes
{
    public ObjectId Id { get; set; }
    public string ヒーロー { get; set; }
    public string 所属 { get; set; }
}

CRUD

public void TestCRUD()
{
    //接続文字列(MongoDBをインストールしてデフォルトのままの場合)
    string connectionString = "mongodb://localhost";
    //MongoDBに接続
    MongoClient client = new MongoClient(connectionString);
    //データベースを選ぶ(use foo)
    var database = client.GetDatabase("foo");
    //コレクションを決める
    var collection = database.GetCollection("heroes");

    /////////////////////////////////////////////////////////////
    //1件作成
    var oneDocument = new Heroes
    {
        ヒーロー = "ヤン",所属="同盟"
    };
    collection.InsertOne(oneDocument);

    /////////////////////////////////////////////////////////////
    //複数件作成
    List documents = new List();
    documents.Add(new Heroes { ヒーロー = "ラインハルト", 所属 = "帝国" });
    documents.Add(new Heroes { ヒーロー = "メルカッツ", 所属 = "帝国" });
    documents.Add(new Heroes { ヒーロー = "キルヒアイス", 所属 = "帝国" });
    collection.InsertMany(documents);

    /////////////////////////////////////////////////////////////
    //Find
    var filterbuilder = Builders.Filter;
    var filter = filterbuilder.Eq("所属", "同盟");
    var findDocuments = collection.Find(filter);
    foreach (var tmp in findDocuments.ToList())
    {
        Console.WriteLine(tmp.ヒーロー);
    }
    //結果 ⇒ ヤン

    /////////////////////////////////////////////////////////////
    //更新
    var updatefilter = filterbuilder.Eq("ヒーロー", "メルカッツ");
    var updateElem = Builders.Update.Set("所属", "同盟");
    var result = collection.UpdateMany(updatefilter, updateElem);//マッチした中の先頭1件のみ削除の場合はUpdateOne
    if (result.IsModifiedCountAvailable)
    {
        Console.WriteLine("【更新結果】マッチ:{0}件 更新:{1}件", result.MatchedCount, result.ModifiedCount);
    }
    findDocuments = collection.Find(filter);
    foreach (var tmp in findDocuments.ToList())
    {
        Console.WriteLine(tmp.ヒーロー);
    }
    //結果 ⇒ ヤン メルカッツ

    /////////////////////////////////////////////////////////////
    //削除
    var delfilter = Builders.Filter.Eq("ヒーロー", "ヤン");
    var delresult = collection.DeleteMany(delfilter);//マッチした中の先頭1件のみ削除の場合はDeleteOne
    Console.WriteLine("削除件数:{0}", delresult.DeletedCount);

    findDocuments = collection.Find(filter);
    foreach (var tmp in findDocuments.ToList())
    {
        Console.WriteLine(tmp.ヒーロー);
    }
    //結果 ⇒ メルカッツ
}

試してみて、分かった内容は次。

  1. ObjectIDは必須(無い場合、追加はできてもFindで落ちる)
  2. コレクション作った後に、クラスにプロパティを追加してもOK
  3. コレクション作った後に、クラスのプロパティを削るとアウト

とりあえず、ObjectIDは必須ですね。クラス定義で使う場合は無いとまず動かない。 あとはデータ作った後に、プロパティ追加してもFind出来た。追加したプロパティがフィールドに無い場合はもちろんNullでかえってくるけども。クラスの拡張はいけそー。コードファースト的なノリでいけそー。ただ、やっぱプロパティの削除はダメね。まぁ、当たり前か。

で、見かけるサンプルって、こんな感じでクラス定義して使うのがほとんど。 でもそれって、
スキーマレスじゃないと思うんだ
MongoDBの利点なのかどーかは色々あるだろーけどもスキーマレスなのが特徴だったよーな。 とゆーわけでスキーマレスにCRUDしましょ。まぁ、公式のQuickTourがスキーマレスでやってるからそれを見ろってことなのかしら。

次は、スキーマレスの操作と、性能回りの実験やる。

MongoDBをコマンドラインでインストールとサービス化もろもろ

  • インストール

最近、MongoDBを使ってみてる。製品と一緒にインストールさせるのに、 インストーラーみたいなのでやらずに、コマンドラインでのやり方。 本家のDocに書いてあるけど、いちを↓のコマンド

msiexec.exe /qb /i mongodb-win32-x86_64-3.2.4-signed.msi INSTALLLOCATION="I:\MongoDB" ADDLOCAL="all"

本家だとオプションが/qだけど、これだとマジで何も出なくて不安になるので、 進捗ステータスだけ表示したいから/qb。

ちなみに、すでにインストールされている場合は一度、インストーラ上げて、Removeやっておかないと 内部のパスの指定がめちゃくちゃになるっぽい。はまった。

インストールログを出したい場合は、上記のコマンドの一番最後に「/log installlog.log」みたいにつけてあげる。

  • サービス化

コマンドプロンプで「mongod」とか打ってるのは嫌なのでサービスにする。 まず、コンフィグファイルを作る。↓の感じ。

systemLog:
   destination: file
   path: I:\MongoDB\log\mongodb.log
   logAppend: true
storage:
   engine: wiredTiger
   dbPath: I:\MongoDB\data
net:
   bindIp: 127.0.0.1
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false

logのフォルダとdataのフォルダは自分で作っておく。 ついでに、32bitOSの場合はエンジンに「wiredTiger」が使え無いっぽい。デフォルトのままやろうとすると、mongodを実行した時点でエラーになってサーバが上がらない。この場合は↑のコンフィグのエンジンの指定を「mmapv1」に変える。

使えるなら「wiredTiger」の方がいいらしいです。DBサイズがかなり軽くなるとか。。。

で、上記のファイルを「mongodb.conf」あたりのファイル名でインストールしたフォルダあたりに保存しておく。 いちをファイルはUTF8で作った。

設定ファイルつり終わったら↓のコマンドでサービス化する。

mongod.exe --config "I:\MongoDB\mongodb.conf" --install --serviceName MongoDB

作った直後は開始されてないので、サービス開いて、MongoDBのサービスを開始する。

この設定だとローカルからのアクセスしかできない。外部からのアクセスを許可する場合は、Netのあたりをごにょごにょするけど、それはまた今度。

IISでWOFFが404になる

Webシステムで外字をWebフォント使って表示させるのを試してみたかっただけど、 CSSで指定しても全く反映されなかった。CSSは↓の感じ。

@font-face {
    font-family: 'EUDC';
    src: url('../fonts/EUDC.woff') format('woff');
}
.gaiziText{
    font-family: "EUDC";
}

ブラウザの開発者ツール使ってみると、フォントファイルをGETしてるところが404になってる。URLは正しい。どうにもわからなかったけど、調べてみるとIISのMIMEにWOFFが入って無くて404になってるらしい。

IISで↓の感じで追加してあげる。

01

02

03

追加するのは↓

拡張子: .woff
MIMEの種類: application/x-font-woff

IISによっては、「.woff」が入ってることもある。ただ、その場合、MIMEの種類が「application/x-woff」になってた。この場合、Chromeで有効にならないっぽい。なので、MIMEの種類は「application/x-font-woff」にしておかないとダメみたい。

IE9とChromeとFireFoxで↑の指定でうまく見れた。

参考にしたのはココ

問合せ