Sweets Junkie

もぅマヂ無理。ムズかしぃコト。。。ぉぼぇてらんなぃから。。。ブログかコ。。。デザィンみづらくてぇ。。。それどころじゃなぃ。。。

【C#】Dapper Extensions を使おう!

Dapper Extensions は、Dapperで簡単なCRUD操作を可能としてくれる軽量で素敵なライブラリです。 基本的な使い方は以下のgithubページにまとまっていますが、自分なりにポイントをまとめます。

https://github.com/tmsmith/Dapper-Extensions

まず前提として以下のようなテーブルを操作すると仮定して説明します。 テーブル名:Users カラム:UserId, Name, Age, Sex

マッピングクラスを準備する

テーブル名と同じ名前でクラスを準備します。 別名にすることもできますが、その方法については別途記載します。

class Users
{
    public int UserId { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Sex { get; set; }
}

GET操作

主キーで検索する

主キー検索は引数に主キーの値を設定します。 ちなみにマッピングクラス内で最初に登場する"id"がつく変数を主キーとして認識します。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // GET 引数にUserIdを指定
    var user = connection.Get<Users>(1);
}

主キー以外で検索する

主キー以外で検索する場合は、検索条件をPredicatesに設定してからおこないます。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // 検索条件を作成
    var predicate = Predicates.Field<Users>(f => f.Name, Operator.Eq, "神谷浩史");
    // GET 引数に検索条件を設定
    // FirstOrDefault で先頭の検索結果のみ取得
    var user = connection.GetList<Users>(predicate).FirstOrDefault();
}

複数条件指定で検索する

複数条件設定する場合は、PredicateGroupを作ります。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // 検索条件を作成
    var pg = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
    pg.Predicates.Add(Predicates.Field<Users>(f => f.Age, Operator.Eq, 20));
    pg.Predicates.Add(Predicates.Field<Person>(f => f.Sex, Operator.Eq, "男"));

    // GET 引数に検索条件を設定
    var users = connection.GetList<Users>(pg);
}

INSERT操作

Insertする場合は、マッピングクラスに値を設定してInsertメソッドに渡すだけです。 この例では主キーがidentityですが、identityでない場合は別の機会に説明します。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // INSERTする値を設定
    var user = new Users
    {
        Name = "神谷浩史",
        Sex = "男"
    };
    // INSERT
    // identity の値が返ってくる
    var id = connection.Insert(user);
}

UPDATE操作

Updateする場合もマッピングクラスに値を設定してUpdateメソッドに渡すだけです。 ただし全てのカラムを更新するので、直前で現在のDBの値を取得してから更新しましょう。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // 現在の値を取得して、更新する値を設定
    var user = connection.Get<Users>(1);
    user.Age = 40;
    // UPDATE
    connection.Update(user);
}

DELETE操作

Deleteする場合はマッピングクラスに主キーを設定してDeleteメソッドに渡すだけです。

using (var connection = new SqlConnection("接続文字列"))
{
    connection.Open();
    // 主キーを設定
    var user = new Users { UserId = 1 };
    // DELETE
    connection.Delete(user);
}

基本的な使い方は以上です。 非常に簡単に使い始めることができ、Dapperをさらに使いやすくしてくれます。

今回は説明しませんでしたが、マッピングクラスとテーブル名が異なる場合や、主キーをIdentityとしない場合でもDapper Extensionsを使用することができます。 これらの実装方法についてはまた別の機会にまとめようと思います。

【C#】【html5】.NET Framework 4 で複数ファイルアップロード

今まではFlashなどを使わなければ実現不可能だった複数ファイルアップロードですが、html5ではinput type="file"にmuliple属性を付けるだけでできるようになりました。

<input type="file" name="example" multiple>


この機能はもちろん.NETでもサポートされており、.NET4.5で追加された機能を使えば簡単に実装できます。
しかし、私の会社の環境は.NET4.0…。なんだ結局複数ファイルアップロードできないのかよ!!と思いきやあっさりできたので忘備しておきます。

以下は.NET4.5で実装した例。

■html

<asp:FileUpload ID="FileUpload" runat="server" AllowMultiple="true"/>

C#

protected void btnUpload_Click(object sender, EventArgs e)
{
	if(FileUpload.HasFiles)
	{
		foreach(var file in FileUpload.PostedFiles)
		{
			Response.Write(file.FileName + " <br />");
		}
	}
}


そして.NET4.0で実装した例。

■html

<asp:FileUpload ID="FileUpload" runat="server"/>

JavaScript(jQuery使用)

/*
DOMロード時の処理を実装する。
*/
$(document).ready(function () {
	// ファイルアップロードフォームの初期化
	// multiple属性を設定
	$('#<%=FileUpload.ClientID %>')
		.attr("multiple", "multiple");
});

C#

protected void btnUpload_Click(object sender, EventArgs e)
{
	HttpFileCollection files = Request.Files;
	for (int i = 0; i < files.Count; i++)
	{
		Response.Write(files[i].FileName + " <br />");
	}
	
}

JavaScriptで属性を設定するあたりはエレガントさに欠けるけど、それで複数ファイルアップロードが実現できるなら安いものよ!
プライドよりも実益!可読性より仕様実現!

ちなみに、html5を使用した複数ファイルアップロードですが、Win版のSafari5.1系では動作しません。
理由はWin版Safariの不具合。Mac版だと動く(確認済)。古いWin版Safariでも動く。
なんじゃそりゃーって感じですが、動かないものは仕方がないので、うちのツールでは使用しないように注意書きして済ましました。
一般ユーザーが使用する可能性があるなら、Win版Safariの場合は普通のファイルアップロードに切り替えるのが良いかもしれないですね。

【C#】CMYK形式の画像ファイルをRGB形式に変換する

印刷するわけでもないのに、なぜCMYK形式の画像ファイルが紛れているのか…。

文句言ってもしかたないので、変換する処理を実装してみた。
例の如く情報が少ないのでリファレンスと超睨めっこしたけど、日本語のリファレンスがあるだけましか…。

public class ConvertColorMode
{
    /// <summary>
    /// CMYK形式の画像ファイルをRGB形式に変換する。
    /// </summary>
    /// <param name="src">元画像のパス</param>
    /// <param name="dest">変換後のパス</param>
    public void Convert(String src, String dest)
    {
        using (FileStream stream = new FileStream(src, FileMode.Open))
        {
            // 変換はColorConvertedBitmapで行う。
            BitmapSource source = BitmapFrame.Create(stream);
            BitmapFrame frame = (BitmapFrame)source;
            ColorContext srcColor = frame.ColorContexts[0];
            ColorContext destColor =
                new ColorContext(PixelFormats.Bgra32);
            ColorConvertedBitmap ccb = new ColorConvertedBitmap(
                source,
                srcColor,
                destColor,
                PixelFormats.Bgr32);

            // Bitmapに変換してから出力する。
            MemoryStream ms = new MemoryStream();
            BitmapEncoder encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(ccb));
            encoder.Save(ms);
            Bitmap bitmap = new Bitmap(ms);
            bitmap.Save(dest);
        }
    }
}

見ての通り変換処理はColorConvertedBitmapを使用しておこなう。
コンストラクタの引数にたくさん変数を渡しているけど、第3引数と第4引数で目的のカラーモードを指定している。
上記の実装ではCMYKRGBの変換だけど、引数を切り替えればRGBCMYKの変換もおこなうことができる。

画像ファイルはJPGの想定なのでJPG形式でエンコードして、Bitmapに変換して出力している。
実は、変換部分より出力方法のがわからなくて結構悩んだ…。

【C#】画像ファイルのカラーモードを判断する方法

画像ファイルのカラーモードを判別する方法について1週間くらい悩んだ末、やっとこさ方法が判明!
C#って意外に日本語の情報が少なくて難儀な言語ですね…。

とりあえず、画像ファイルを読み込んでカラーモードを判別したいなら以下のような実装で実現することができる。

public class IsCmyk
{
    /// <summary>
    /// 画像ファイルのカラーモードがCMYKかチェックする。
    /// </summary>
    public Boolean Check()
    {
        using (FileStream stream =
                new FileStream("cmyk.jpg", FileMode.Open))
        {
            BitmapDecoder dec = 
            	BitmapDecoder.Create(
                    stream, 
                    BitmapCreateOptions.PreservePixelFormat, 
                    BitmapCacheOption.Default);
            if (dec.Frames[0].Format == PixelFormats.Cmyk32)
            {
                return true;
            }
        }
        return false;
    }
}

ポイントはBitmapDecoderに「BitmapCreateOptions.PreservePixelFormat」を設定して初期化しているところ。
これを設定しないと、システムが勝手に最適なPixelFormatを選択してしまうため、CMYKの画像ファイルを読み込ませていても、PixelFormatがRGBになってしまったりする。
Bitmapで試してみて思うような結果が得られなかったのは、この辺が原因みたい。

【C#】Dapperについてまとめてみた

C#で使える軽量なORマッパー「Dapper」についてのナレッジが貯まってきたのでまとめてみます。

#1 Dapperとは

C#で書かれた軽量なORマッパー。
ただし、クエリ生成やソース自動生成などの機能は備えていない。
その代り高速。ORマッパーを嫌う人は速度面を指摘するけど、気になることはないと思う。

とにかく多機能なORマッパーを使いたい人はEntityFramework使ってどうぞ。


#2 導入方法

NuGetから「Dapper」検索GO!!


#3 使い方

①単一テーブルをSelectしてみる。
対象のテーブルは以下。

テーブル名:Users
カラム:UserName、Age

Dapperを使用してDBの取得結果をオブジェクトにマップするにはマップ先となるクラスを用意する必要がある。
基本的にはカラム名=変数名とする。大文字小文字は区別しない。
カラム名=変数名とならない場合はマッピングできないので、クエリ側でAS句を使い変数名と合わせる必要がある。
今回は以下のようなクラスを用意。

class User
{
     public String UserName { get; set; }
     public int Age { get; set; }
}

では早速Usersテーブルの情報をUserクラスにマッピング

public User GetUser()
{
   // DBコネクションを取得(ここは適当
  Connection con = ConnectionUtil.GetConnection("接続文字列");
 
  // DBに投げるクエリ @UserName はパラメータ
  String sql = @"SELECT * FROM Users WHERE UserName = @UserName";
  // クエリを実行して、取得結果をUserクラスにマッピング
  // パラメータは dynamic型で指定
  // SingleOrDefault() を使うと、複数件取得できた場合に例外、0件取得の場合にNullを返してくれる。
  // Single() を使うと、複数件取得、0件取得の場合に例外を発射する。
  return con.Query<User>(sql, new { UserName = "三木眞一郎" }).SingleOrDefault();
}

以上。これだけでマッピングされる。シンプルで超素敵。
ソースの説明はコメントを参照されたし。

②複数レコード取得してみる。
対象テーブルは①と同じ。

 public List<User> GetAllUser()
{
  // DBコネクションを取得(ここは適当
  Connection con = ConnectionUtil.GetConnection("接続文字列");
 
  // DBに投げるクエリ
  String sql = @"SELECT * FROM Users";
  // クエリを実行して、取得結果をUserクラスにマッピング
  // List<User>にキャストする
  return List<User> con.Query<User>(sql);
}

以上。キャストするだけでListにもマッピングできる。

③1対1の関係のテーブルをJOINして取得してみる。
対象のテーブルは以下。

テーブル名:Users
カラム:UserName、Age、JobId

テーブル名:Jobs
カラム:JobId、JobName

Jobsテーブルのマップ先としてUserクラスにJobクラスを定義する。

class User
{
     public String UserName { get; set; }
     public int Age { get; set; }
     public int JobId { get; set; }
     public Job Job { get; set; }
}
 
class Job
{
     public int JobId { get; set; }
     Public String JobName { get; set; }
}

UsersテーブルとJobsテーブルを結合してUsersクラスにマッピングしてみる。

public User GetUserAndJob()
{
  // DBコネクションを取得(ここは適当
  Connection con = ConnectionUtil.GetConnection("接続文字列");
 
  // DBに投げるクエリ @UserName はパラメータ
  String sql = @"SELECT * FROM Users u INNER JOIN Jobs j ON u.JobId = j.JobId WHERE UserName = @UserName";
  // クエリを実行して、取得結果をUserクラスにマッピング
  // Query<User, Jobs, User> は、<1つ目のテーブルのクラス、2つ目のテーブルのクラス、returnするクラス>
  // パラメータは dynamic型で指定
  // splitOnには2つ目のテーブルの開始位置のカラム名を指定
  // SingleOrDefault() を使うと、複数件取得できた場合に例外、0件取得の場合にNullを返してくれる。
  // Single() を使うと、複数件取得、0件取得の場合に例外を発射する。
  return con.Query<User, Jobs, User>(
     sql, 
     (user, job) => { user.Job = job; return user }, 
     new { UserName = "三木眞一郎" },
     splitOn: "JobId").SingleOrDefault();
}

以上。これも簡単。
ちなみに3つ以上のテーブルを結合する場合は、「con.Query」に3つ目のテーブルを追加して、splitOnにはカンマ区切りで区切り位置のカラム名を指定すればよい。

④ストアドプロシージャをよんでみる。
「sp_getUsers」というストアドプロシージャがあるとして。

public List<User> GetAllUser()
{
  // DBコネクションを取得(ここは適当
  Connection con = ConnectionUtil.GetConnection("接続文字列");
 
  // DBに投げるクエリ
  String procedure = "sp_getUsers";
  // クエリを実行して、取得結果をUserクラスにマッピング
  // CommandTypeにストアドプロシージャを指定
  // List<User>にキャストする
  return List<User> con.Query<User>(procedure, commandType: CommandType.StoredProcedure);
}

以上。CommandTypeを指定する以外は①~③と一緒。

ものすごいざっとまとめたけど、これで事足りるくらいDapperはシンプルなライブラリです。
ORマッパーなのにクエリの自動生成は無いの?と思うかもしれないけど、世の中にはクエリ自動生成にアレルギーのある人とか、ストアドプロシージャー教の人も多いので、個人的にはこれくらいの機能で丁度よく感じています。
特に私の会社ではストアドプロシージャでまみれているのでとても役に立ちました。ありがとうDapper!!

【C#】CDATAセクションを付けたり付けなかったりする

XmlSerializerでオブジェクトをシリアライズする際に、あるメンバーにCDATAセクションを付けたい場合は以下のような実装をすると思う。

public class Program
{
    public static void Main(string[] args)
    {
        // 保存先
        String file = @"C:\tmp\sample.xml";

        // シリアイズするオブジェクト
        User user = new User();
        user.Name = "中村悠一";
        user.Comment = new XmlDocument().CreateCDataSection("めっちゃオシャレな家、めっちゃオシャレな家できた");

        XmlSerializer serializer = new XmlSerializer(typeof(User));
        using (FileStream fs = new FileStream(file, FileMode.Create))
        {
            //シリアライズ
            serializer.Serialize(fs, user);
        }
    }
    public class User
    {
        public String Name { get; set; }
        // CDATAセクションで囲むメンバー
        public XmlCDataSection Comment { get; set; }
    }
}


上記を実行すると以下のようなXMLファイルができる。

<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>中村悠一</Name>
  <Comment><![CDATA[めっちゃオシャレな家、めっちゃオシャレな家できた]]></Comment>
</User>


ここでUser.Commentに入れる値が空文字の場合にのみCDATAセクションを付けないなどの制御をおこなう場合は、XmlCDataSectionは使用せずにXmlNodeを使用する。
Commentに値を設定する際に値をチェックして、空文字でなければCDATAセクションとして設定し、そうでない場合は空文字をする。

public class Program
{
    public static void Main(string[] args)
    {
        // 保存先
        String file = @"C:\tmp\sample.xml";

        // シリアイズするオブジェクト
        User user = new User();
        user.Name = "中村悠一";
        user.SetComment("");

        XmlSerializer serializer = new XmlSerializer(typeof(User));
        using (FileStream fs = new FileStream(file, FileMode.Create))
        {
            //シリアライズ
            serializer.Serialize(fs, user);
        }
    }
    public class User
    {
        public String Name { get; set; }
        // CDATAセクションで囲むメンバー
        public XmlNode Comment { get; set; }

        // setter
        public void SetComment(String comment)
        {
            // Null or 空文字の場合はCDATAセクションを付けない。
            if (String.IsNullOrEmpty(comment))
            {
                this.Comment = new XmlDocument().CreateWhitespace("");
            }
            else
            {
                this.Comment = new XmlDocument().CreateCDataSection(comment);
            }
        }
    }
}


実行結果。

<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>中村悠一</Name>
  <Comment></Comment>
</User>


こんな感じ!以上!