CakePHPで2つのテーブルを連結させる

さて、極悪テーブル命名規則 T100,T200,T300 を magazines, publishers, users に変えることができたので、今度はテーブルの連結をしてみます。

<!-- views/magazines/index.ctp -->
<h2>雑誌一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>publisher_id</td>
		<td>publisher_id</td>
	</tr>
<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Magazine']['T200id'] ?></td>
		<td><?php echo $item['Magazine']['publisher_id'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

のように、publisher_id を表示させたって意味がないわけで、出版社名(Publisher.name)を表示させたい訳です。

<!-- views/magazines/index.ctp -->
<h2>雑誌一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>publisher_id</td>
		<td>publisher_id</td>
	</tr>
<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Magazine']['T200id'] ?></td>
		<td><?php echo $item['Magazine']['publisher_id'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td> ★
	</tr>
<?php endforeach ; ?>
</table>

みたいに使えればいいですよね。使えるのかな?

という訳で、ひとつの雑誌(magazine)は、出版社(publisher)に属するということで、Magazine モデルを書き換えます。

<!-- models/magazine.php -->
<?php
class Magazine extends AppModel
{
	var $name = 'Magazine';
	var $useTable = 'T100';
	var $virtualFields = array(
		'publisher_id'=>'T200id' );

	var $belongsTo = array('Publisher' =>
		array(
			'className' => 'Publisher',
			'conditions' => '',
			'order' => 'Magazine.id ASC',
			'foreignKey' => 'T200id' ));
}
?>

この外部キーの書き方はいくつかあるのですが、$belongsTo と $hasMeny は双方向なのでどちらか決めで使えばいいんでしょう。

こうすると、先の index.ctp が動きます。生成される SQL を見ると、

SELECT 
 `Magazine`.`id`, 
 `Magazine`.`name`, 
 `Magazine`.`T200id`, 
 `Magazine`.`price`, 
 (T200id) AS `Magazine__publisher_id`, 
 `Publisher`.`id`, 
 `Publisher`.`name` 
FROM `T100` AS `Magazine` 
 LEFT JOIN `T200` AS `Publisher` ON (`Magazine`.`T200id` = `Publisher`.`id`) 
WHERE 1 = 1 
 ORDER BY `Magazine`.`id` ASC 

となっているので、left join が使われていますね。なるほど。

残念ながら $virtualFields で別名定義をしたカラム名は、$belongsTo では使えないようで、あの不思議な T200id というカラム名が出てきますが、まぁ、良しとしましょう。適当なヘルパー関数なりを作れば、このあたりの設定も楽になると思います。$virtualFields が連想配列なので、逆引きして value から key を取り出せばよいので。

カテゴリー: 雑談, CakePHP | 2件のコメント

CakePHP のモデルでデータベースをリファクタリング

リファクタリングとは言え、いきなりデータベースを変えてしまうのではなくて、database-Model の関係を利用して、「管理のしやすいテーブル名」を、「意味のわかりやすいテーブル名」に変えましょう、という方法です。

# ASP.NET MVC で使う LINQ to Entities や LINQ to SQL なんかは、テーブル名=クラス名が固定(だと思う)なので、この技が使えないのです。

例えば、既存のテーブルが次の 3 つで校正されています。

  • T100 雑誌テーブル
  • T200 出版社テーブル
  • T300 読者テーブル

ああ、なんて【管理しやすい】テーブルなんでしょうかッ!!! おそらく、将来的に雑誌に関係するテーブル名は、「T101」とか「T102」になる、ってことが容易に想像できます。

…が、決して分かり易いテーブル≒プログラミングしやすいテーブルではないですよね。
# こういう管理の仕方は嫌いではないのですが、避けておいたほうがいいというかなんというか。
# ただし、テーブル数が数百を超えると、こんな風に管理せざるを得ない時もありますよね。名前がだぶらないようにとか、名前が長くなりすぎないようにとか、制限があるので。

さて、これをプログラムで扱うときには、次のようにしたいわけです。

  • Magazine 雑誌テーブル
  • Publisher 出版社テーブル
  • User 読者テーブル

ちなみに、CakePHP の命名規則に従えば、

  • magazines 雑誌テーブル
  • publishers 出版社テーブル
  • users 読者テーブル

のように、小文字かつ複数形にするのが良いのですが、あらかじめデータベースができていたり、社内的な命名規則があったりすると、この限りではないのです。

テーブルを次のように作ります。

-- 雑誌テーブル
create table T100 (
 id int,
 name varchar(50),
 T100id int, -- 出版社ID
 price int
);
-- 出版社テーブル
create table T200 (
 id int,
 name varchar(50)
 );
-- 読者テーブル
create table T300 (
 id int,
 name varchar(50),
 age int
 );

な風に、(極悪)テーブル名を付けておいても、Model で書き換えれば OK。

<!-- models/magzine.php -->
<?php
class Magazine extends AppModel
{
	var $name = 'Magazine';
	var $useTable = 'T100';
}
?>

<!-- models/publisher.php -->
<?php
class Publisher extends AppModel
{
	var $name = 'Publisher ';
	var $useTable = 'T200';
}
?>

<!-- models/user.php -->
<?php
class User extends AppModel
{
	var $name = 'User';
	var $useTable = 'T300';
}
?>

これでコントローラーで使うときは、Magazine、Publisher、User が使えるようになります。

<?php
class MagazinesController extends AppController {
	var $name = 'Magazines';
	var $uses = array('Magazine','Publisher');
	function index() {
		$this->set('Magazines',$this->Magazine->find('all'));
		$this->set('Publishers',$this->Publisher->find('all'));
	}
}
?>

まぁ、対応表を作ってちまちま見るよりはいいか、とは思いますけど、結局は「T100」は「magazine」だったしと、言葉の置き換えは必要になるわけですが。

ただし、これでも問題があって、T100.T200id っていう出版社テーブルへのリンクはモロにビューに出てしまうのですよね。

<!-- views/magazines/index.ctp -->
<h2>雑誌一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>publisher_id</td>
	</tr>
<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Magazine']['T200id'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>
<h2>出版社一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
	</tr>
<?php foreach($Publishers as $item) : ?>
	<tr>
		<td><?php echo $item['Publisher']['id'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

こういうヘンテコな名前「$item[‘Magazine’][‘T200id’]」が残ってしまうので、完全にリファクタリングって訳でもなさそうですなぁ。ま、テーブル名ぐらいは変えられるってことで。

と、思ったらですね。

virtualFields :: Model の属性 :: モデル :: CakePHPによる開発 :: マニュアル :: 1.3コレクション :: The Cookbook
http://book.cakephp.org/ja/view/1588/virtualFields

を見ると CakePHP 1.3 から モデルに仮の名前を付けられるそうです。
テーブル自体を更新できないので、CRUD 全体には使えませんが、ビューで表示するときには便利かと。

<!-- models/magazine.php -->
<?php
class Magazine extends AppModel
{
	var $name = 'Magazine';
	var $useTable = 'T100';
	var $virtualFields = array(
		'publisher_id'=>'T200id' );
}
?>

こんな風に別名を定義しておくと、

<!-- views/magazines/index.ctp -->
<h2>雑誌一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>publisher_id</td>
		<td>publisher_id</td>
	</tr>
<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Magazine']['T200id'] ?></td>
		<td><?php echo $item['Magazine']['publisher_id'] ?></td>
	</tr>
<?php endforeach ; ?>

のように、再定義した publisher_id で値を取得できます。

すべてのカラムを再定義するのは問題ありでしょうが、こんな風に一時的に変な名前が付いているカラムには適用して良いかと思います。特に *.ctp で表示するときには、「T200id」よりも「publisher_id」のほうが間違いが少なそうですよね。

カテゴリー: CakePHP | CakePHP のモデルでデータベースをリファクタリング はコメントを受け付けていません

CakePHP再入門(2)

覚書きとして。

CakePHP の View-Controller-Model-DataBase の名前の関係がどうなっているかというと。

こんな風になっている。

カテゴリー: CakePHP | 2件のコメント

wordpres に XML-RPC で画像を投稿する

以前、XML-RPC を使って wordpress に投稿するツールを作ったのですが、画像も投稿できないと意味がないですよね…ということで、画像の投稿(というか、ファイルアップロード)のツールを作成しています。

XML-RPC wp ≪ WordPress Codex
http://codex.wordpress.org/XML-RPC_wp#wp.uploadFile

を見ると、データ形式が base64 になっていて、一見 BASE64 でエンコードをしないと駄目なような気がしますが、実は違います。これは XML-RPC 内で作られるフォーマットなので、BASE64 形式の文字列を送るのではなくて、バイナリを BASE64 で送る(CDATA を使う)っていう意味なんですね。

ちょっとハマりました。

ソースは抜粋だけアップしておきます。後で整理して、

  • 記事本体の投稿(WpPost)
  • 画像ファイルの投稿(WpUpFile)

の2つのツールを公開します。

        /// <summary>
        /// 新しい画像を投稿する
        /// </summary>
        /// <param name="post">投稿記事</param>
        /// <returns>投稿した記事のID</returns>
        public RetUpFile NewImage(ImageFile post)
        {
            //プロキシクラスのインスタンスを作成
            IBlogger proxy = (IBlogger)
                CookComputing.XmlRpc.XmlRpcProxyGen.Create(
                typeof(IBlogger));

            //URLを指定
            proxy.Url = this.Url;

            // ファイル名を小文字に変換
            post.Filename = post.Filename.ToLower();

            // UpFile を生成
            UpFile upfile = new UpFile();
            upfile.name = post.Filename;
            upfile.type = "image/jpeg";
            upfile.bits = null;
            upfile.overwrite = true;

            // ファイルを読み込む
            long len = new FileInfo(post.Filename).Length;
            byte[] data = new byte[len];
            BinaryReader rd = new BinaryReader(
                File.OpenRead( post.Filename ));
            rd.Read(data, 0, (int)len);
            rd.Close();
            
            // BASE64に変換しない
            // upfile.bits = Convert.ToBase64String(data);
            upfile.bits = data;
            // ファイルタイプを変更
            if ( upfile.name.EndsWith(".jpg") ||
                 upfile.name.EndsWith(".jpeg") ) {
                upfile.type = "image/jpeg";
            } else if ( upfile.name.EndsWith(".png") ) {
                upfile.type = "image/png";
            } else if ( upfile.name.EndsWith(".gif")) {
                upfile.type = "image/gif";
            }

            RetUpFile ret;
            try
            {
                //blogger.getRecentPostsを呼び出す
                ret = proxy.uploadFile(
                    "1",            // 念のため1にしておく
                    this.Username,
                    this.Password,
                    upfile );
            }
            catch (Exception ex)
            {
                Console.WriteLine("エラー:" + ex.Message);
                return null;
            }

            return ret;
        }
    }

この手のツールを作っているのは、実は wordpress で 300 ページ位の記事をアップできないか?と考えたわけです。
ブログを適度に書くぐらいであれば、ブラウザ上でもいいのですが、ある程度まとまった記事であればオフラインで書きたい。

そこでオフラインエディタの BlogWrite や Windows Live Writer などを使っていたのですが、どうも記事本体の編集だけに集中できない。様々な装飾が邪魔なんですね。

記事を書くときには、2つのタイプがあって、

  • 記事本体(本文)に集中するためにエディタで書く。
  • 記事のレイアウトを整えるために GUI ベースのツールを使う。

例えば、書籍で云えば、最初のが「著者」、次のが「編集者」あるいは「DTP」ってことです。
BlogWrite なんかのツールは、文章を書くことと、レイアウトと整えることが同時にできる、のが非常に利点なのですが、逆に画像がどうのレイアウトがどうの、と気になってしまったときには「著者」の頭に切り替えづらいという欠点があります。

ま、どのようなタイプの本を書くのかというで別かもしれませんが、BlogWrite を使っているときの問題として、

コードを表示するプラグインのタグ [ code]…[/code] を非常に書きづらい、

という大きな欠点があります。今までブラウザ上でちまちま直したり、Windows Live Writer に切り替えてみたりとしたのですが、本来 HTML タグではないものは(当たり前ですが)オフラインのブログエディタが認識しないので、無理といえば無理な話ですよね。

という訳で、もう一度、エディタに立ち返ってみよう、という感じです。

そんな訳で、

1.QX エディタで文章を書く。
2.perl を使って HTML タグに変換([ code]タグなんかは残す)
3.WpPost, WpUpfile のようなツールで wordpress にアップロード

な流れで現在書いています。
ローテクですが、枯れた技術(テキストエディタ、perl)を使うとそれなりに作れますね。コマンドライン版ですがw

~~

wordpres の独自タグを使わずに、<code>…</code>で記述した後に、wordpress 側で Javascript で判別、なんてことでもいいんですけど、プラグイン作成時のお手軽さが失われるのが残念でして。

wordpress の拡張タグのプラグインは非常に簡単にできて、

function manabook_index($attss) {
	...
	return $links;
}
add_shortcode('manabook_index','manabook_index');

な感じでショートコードを作成して plugins に放り込んでおけば、

記事に [manabook_index] と書いておくだけで、目次に展開してくれるというプラグインが作れます。こっちは別の記事で紹介します。

カテゴリー: 開発, Wordpress | 1件のコメント

CakePHP はマニュアル無しでサイトを作れるのか?

どういう手順でアプリケーションを作成するのか、ってことですね。

CakePHPブログチュートリアル :: 開発例 :: マニュアル :: 1.2コレクション :: The Cookbook
http://book.cakephp.org/ja/view/219/CakePHPブログチュートリアル

最初はチュートリアルに手を付けるわけですが、さて、サイトを作る時はどこから作り始めるのか、というと、CakePHP は面白いことにブラウザに URL を入力するところから始めます。

ひとまず、ここまでデータベースを設定した後で、ユーザーがブラウザで入力するアドレスを入れます。

http://cake.moonmile.net/Products

すると、エラーがでます。当たり前ですが、このエラーを元に書き進めるわけです。

  • ProductsController クラスが無いよ。
  • app/controllers/products_controller.php のファイルに置けばいい。
  • ひとまず、下記を貼りつければ OK。
<?php
class ProductsController extends AppController {

	var $name = 'Products';
}
?>

という訳で、コントローラーを作ります。

再び http://cake.moonmile.net/Products のアドレスに接続すると、エラーが変わります。

  • モデルの Product のための products というテーブルが無いよ。

ってな感じです。

※ ただし、IIS+PHP+MySQL の環境では、ここで 500 のサーバーエラーが出ます。ちょっと、これは不便。何故、500 エラーなのかは不明ですが、Linux 上で PHP+MySQL の組み合わせでは、この通りに動きます。

※ 暫定ですが、/cake/libs/error.php の254 行目ぐらいにある missingTable 関数内の $this->controller->header(“HTTP/1.0 500 Internal Server Error”) の行をコメントアウトすると、テーブルが無いときの 500 エラーを回避できます。

そういう訳で、phpMysqlAdmin などを使って products テーブルを作ります。

create table products (
  id int not null,
  name varchar(50) not null,
  price int not null
)

データも適当に入れておきます。

insert into products values ( 1, 'product 1st', 1000 );
insert into products values ( 2, 'product 2nd', 1000 );
insert into products values ( 3, 'product 3rd', 1000 );

再び http://cake.moonmile.net/Products のアドレスに接続します。

今度は、

  • ProductsController コントローラーに index というメソッドが無い。
  • まあ、だいたいこんな風に書く。
<?php
class ProductsController extends AppController {

	var $name = 'Products';

	function index() {

	}

}
?>

という訳で、index メソッドの中身を作ります。

再び、ブラウザをリロードすると、またエラーが変わります。

今度は ProductsController::index() に対応するビューが無いそうです。
ビューは、app/views/products/index.ctp に作りなさい、ということになっていますね。

ここでビューを適当に作ります。

<p>これは products テーブルのためのビューです。</p>
<?php

?>

再び、ブラウザをリロードすると、

めでたくエラーがなくなります。

…って、テーブルが表示されていないので、コントローラーとビューを直します。

<?php
class ProductsController extends AppController {

	var $name = 'Products';

	function index() {
		$this->set('Products', $this->Product->find('all'));
	}
}
?>

Product モデルから検索して、ビューに Products という名前で引き渡します。

これに合わせて、ビューも変えていきます。

<p>これは products テーブルのためのビューです。</p>

<table>
	<tr>
		<th>id</th>
		<th>name</th>
		<th>price</th>
	</tr>
<?php foreach($Products as $item) : ?>
	<tr>
		<td><?php echo $item['Product']['id'] ?></td>
		<td><?php echo $item['Product']['name'] ?></td>
		<td><?php echo $item['Product']['price'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

ここまできたら、きちんと表示ができます。

こういう風にひな型を作るって感じでしょうね。

よくできているというか感心するのが、足りない部分を単純なコンパイルエラーの表示ではなくて、足りないファイル、足りないメソッドの内容までを表示しているところです。 ASP.NET MVC の場合は、こんな風にはできていなくて、コンパイルエラーにならないように、どうしても、モデル→コントローラー→ビューの順序で作る必要があります(エラーを見ながらやってもいいんでしょうけど)。

このあたりは、PHP がスクリプト言語である利点を有効に活用しているのかなと。

カテゴリー: CakePHP | CakePHP はマニュアル無しでサイトを作れるのか? はコメントを受け付けていません

CakePHP 再入門(1)

ASP.NET MVC の連載記事の関係から、他の MVC ものも抑えておかないと、と言いますか、CakePHP は抑えないと今日びの WEB ものはできないですよね~。かどうかは分かりませんが…

おととし CakePHP 祭りに行った頃から注目はしていたのですが、最初のチュートリアルをした後で止まっておりました。MVC 自体は色々と興味があるところなのですが、果たして PHP で実装をする側に立つことが今までなかったもので。

さて、ASP.NET MVC の場合は Model は、LINQ to Entities やら独自の Model やらを選べます。と言うのも、ひとつの画面にひとつのテーブルだけが表示される、ってことはほとんどないんですよね。

例えば、Amazon のトップページを例にとれば、

  • 最近発売された商品の一覧
  • あなたにおすすめの商品の一覧
  • カテゴリ一覧

な感じで、いくつかのテーブルから検索結果を出します。

このようにする場合は、ASP.NET MVC の Model の場合には、

public class TopModel
{
	// 最近発売された商品
	public TNewGoods { get; set; }
	// おすすめ商品
	public TYourGoods { get; set; }
	// カテゴリ一覧
	public TCategories { get; set; }
}

な感じで、3 つのテーブルを内部に持つ必要があるわけです。

これって、CakePHP でできるんだっけ? ってのが、いままでの私の疑問でありました。
CakePHP の Model って、ひとつのテーブルがバインドされている、という固定観念があったわけですね。

が、違います。当たり前だったのですが、CakePHP でも、複数のテーブルをモデルの含めることができます。モデルというか、コントローラーから複数のモデルをビューへ渡すことができます。

/controllers/goods_controller.php

<?php
class GoodsController extends AppController {
	var $name = 'Goods';
	var $uses = array('NewGood','YourGood','Category');

	function index() {
		$this->set('NewGoods', $this->NewGood->find('all'));
		$this->set('YourGoods', $this->YourGood->find('all'));
		$this->set('Categories',$this->Category->find('all'));
	}
}
?>

のように作成しておくと、

http://servername/goods

で次のような画面が表示できます

この時のビューは次な感じ。
/views/goods/index.ctp

<h1>NewGoods</h1>
<table>
	<tr>
		<th>Id</th>
		<th>name</th>
		<th>price</th>
	</tr>
</p>
<p>
<?php foreach ($NewGoods as $item): ?>
	<tr>
		<td><?php echo $item['NewGood']['id']; ?></td>
		<td><?php echo $item['NewGood']['name']; ?></td>
		<td><?php echo $item['NewGood']['price']; ?></td>
	</tr>
<?php endforeach; ?>
</table>
</p>
<p>
<hr/>
<h1>YourGoods</h1>
</p>
<p>
<table>
	<tr>
		<th>Id</th>
		<th>name</th>
	</tr>
</p>
<p>
<?php foreach ($YourGoods as $item): ?>
	<tr>
		<td><?php echo $item['YourGood']['id']; ?></td>
		<td><?php echo $item['YourGood']['name']; ?></td>
	</tr>
<?php endforeach; ?>
</table>
<hr/>
</p>
<p>
<h1>Categories</h1>
<table>
	<tr>
		<th>Id</th>
		<th>name</th>
	</tr>
</p>
<p>
<?php foreach ($Categories as $item): ?>
	<tr>
		<td><?php echo $item['Category']['id']; ?></td>
		<td><?php echo $item['Category']['name']; ?></td>
	</tr>
<?php endforeach; ?>
</table>

複数の Model(NewGoods,YourGoods,Categories)を、コントローラー GoodsController でひとまとめにして、ビューで表示させるという方式です。

ASP.NET MVC の場合は、View に対してひとつの Model しか引き渡せないので、このあたりの動きが違います。逆に言えば、CakePHP の場合は、ひとつの Model にひとつのテーブル(あるいは複数のテーブルのクエリ結果)を渡すことになるので、このあたりが「違い」ますね。

カテゴリー: 雑談, CakePHP | 2件のコメント

newQX でクリップボードを使う

先日、やっとこさ newQX のマクロが起動しました…と、言いますか、なんか拡張子を vbs にしてもマクロが起動しなかったんですよね。

な感じで「マクロファイルを編集していないため実行できません」ってエラーがでるのです。サンプルマクロを動かしても駄目という現象。

で、結果的には「その他」→「書式設定」の「テキスト/キーワード」のところで、テキスト種別が「マクロ」になっていないと、マクロが動かないという…別の書式設定をコピーしたものだから、ここが上書きになってしまっていたのですね。

そんな訳で、めでたくマクロが動きました。

な、だけでは詰まらないので、早速 QX32 に似た環境を作ろうと画策しております。
まずは、編集中のフルパスをクリップボードにコピーしたい、からスタート、ってことなんですが、いきなり躓いてしまいました。QX32 のマクロでは clipboard$ という関数(プロパティ?)があったのですが、newQX だと vbscript ベースになっているので、このあたりの実装がないッ!!! 困った。

wscript/WSH で使う場合にもクリップボードは難問がありまして、IE を使ったり Excel を使ったりするのですが、一番手っ取り早いのがクリップボード用の COM を作ってしまうことです。
レジストリへの登録は必要なのですが、一度、登録してしまえば他にも再利用できるし、というわけで C# で作りました。

C# で COM コンポーネントを公開する – てっく煮ブログ
http://d.hatena.ne.jp/nitoyon/20080731/c_sharp_com

上記を参照しながら C# でクラスを作る、そして登録。登録する場合は regasm なんですね。このあたりは、人に渡す時にちょっと問題があります(管理者権限で動かさないといけないという)。

ひとまず、C# のコードはこんな感じです。

namespace QxNet
{
    public class Clipboard
    {
        /// <summary>
        /// クリップボードをテキスト形式で取得/保存
        /// </summary>
        public string Text {
            get {
                return System.Windows.Forms.Clipboard.GetText();
            }
            set {
                System.Windows.Forms.Clipboard.SetText( value );
            }
        }

        public void SetText(string text)
        {
            this.Text = text;
        }
        public string GetText()
        {
            return this.Text;
        }
    }
}

ProgId などの属性を付けていませんが、まあ、これでも大丈夫。

登録する場合は、管理モードでコマンドプロンプトを起動して

regasm QxNet.dll

のように登録します。

そして newQX のマクロは以下な感じで。

'現在のフルパスをクリップボードへ保存
sub FilenameToClip
	set clip = createobject("QxNet.Clipboard")
	clip.setText(ActiveDocument.fullname)
end sub

CliateObject してからクリップボードにフルパスをコピーします。

こうすると先のコマンドラインへのファイル名渡しが楽なんですよね。
名前は「QxNet」にしてあるので、他にも QX を拡張するような .NET ライブラリを作っていこうかなと。

実行ファイルなんかは、後日公開します。

カテゴリー: 開発, QXエディタ | newQX でクリップボードを使う はコメントを受け付けていません

LINQ to Entities では ToString が使えない

Entity Data ModelとLINQ to SQL は同時に使えない | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1688

前回の続きということで、Entity Data Model の使いどころ、というか私が陥った落とし穴を紹介します。

さて実は、ASP.NET MVC の連載記事を書いている途中で、Html.DropDownList メソッドを使いたかったのですね。連載記事のほうは、Entity Data Model を使うようにしているので、DropDownList に連結するならば、やっぱり「データバインド」ですね、って形にしたかったのです。

以前の ASP.NET コントロールであれば、

dropDownList.DataSource = items ;

みたいな形でデータソースを指定するわけですが、HTML ヘルパーメソッドの場合は、

Html.DropDownList( “states” );

のように ViewData を使ってバインドさせる方法もあれば、

Html.DropDownList( “states”, Model.items );

のようにモデルのパラメータを使って設定する方法もあります。

そこで都道府県コードをバインドしようかなぁ、と思って、都道府県のテーブルを作成。

create table TStates (
	id int, -- 都道府県コード
	name varchar(20) -- 都道府県名
)

そして、コントローラーに以下のコードを作成。

public ActionResult StatesList()
{
    Models.mvcdbEntities ent = new Models.mvcdbEntities();

    var items = from t in ent.TStates
                select new SelectListItem
                {
                    Value = t.id.ToString(),
                    Text = t.name
                };
    ViewData["states"] = items;

    return View();
}

DropDownList は、SelectListItem のコレクションしか受け付けないので、あらたに new して追加します。データベースから検索できたほうがいいので、LINQ が便利ですよね。って訳で、LINQ を使っています。
SelectListItem は、Text は表示文字列、Value は値なんですがブラウザなので ToString で変換しています(ここが落とし穴です)。

さて、これを動かすと、

▼画像001

20110121_01.jpg

ってなエラーがでます。

System.NotSupportedException はユーザー コードによってハンドルされませんでした。
  Message=メソッド ‘System.String ToString()’ は LINQ to Entities では認識されないため、ストア式に変換できません。
  Source=System.Data.Entity

なんだか良くわかりませんが、ToString は動きません…ってな具合です。

どうやら、「LINQ to Entities」ってのが動いているようで、仕方がない「LINQ to SQL」を動かそう。という訳で、他の方法で試してみます(これを同じアプリケーションに組み込もうとして、前回の落とし穴なんですなw)。

public ActionResult StatesList()
{
    DataContext dc = getDataContext();
    var items = from t in dc.GetTable<TStates>()
                select new SelectListItem
                {
                    Value = t.id.ToString(),
                    Text = t.name
                };

    ViewData["states"] = items;


    return View();
}

のように、LINQ to SQL のほうを動かすとですね。すんなり動きます。う~む。

▼画像002

20110121_02.jpg

何処が引っ掛かっているのかというと、ToString なんですね。どうやら、LINQ to Entities の中では、この手の他のメソッドが動かないらしい。しかも、LINQ の実行が遅延されるので(LINQを定義した箇所ではなくて実際に実行される場所という意味で)、aspx 内でエラーが発生するという不思議な具合になります。

いずれ、LINQ to SQL が LINQ to Entities に全て置き換わるのか…は不明なのですが、こんな風に LINQ 内で文字列処理をしようとするとちょっと不思議な現象に出会います。

なので、LINQ to Entities を使っている場合は、

public ActionResult StatesList2()
{
    Models.mvcdbEntities ent = new Models.mvcdbEntities();
    List<SelectListItem> items = new List<SelectListItem>();
    foreach (var it in ent.TStates)
    {
        items.Add(new SelectListItem
        {
            Value = it.id.ToString(),
            Text = it.name
        });
    }
    ViewData["states"] = items;

    return View();
}

のように、一度 コレクションに代入しないといけないという…ダサい感じに…てなわけで連載のほうでは却下しました(苦笑)。このあたりの説明が主目的ではないので orz

さて、この現象ですが、実は Visual Basic 2010 では起こりません。

Function StatesList() As ActionResult
    Dim ent = New mvcdbEntities
    Dim items = From t In ent.TStates
                Select New SelectListItem With {
                    .Value = t.id,
                    .Text = t.name
                }

    ViewData("states") = items

    Return View()
End Function

のように VB を使って書くとすんなり動きます。

これは、ToString 等の変換を使っていないからなんですね。.Value = t.id のところで、自動的に Integer から String に変換されているわけで、これがうまく動く要因なのです。C# の場合は、自動変換が効かないから ToString メソッドを使うところでアウト、なんですなぁ。

というわけで、同じ LINQ を使っている場合でも、Entity Data Model(LINQ to Entities)を使っている場合と、LINQ to SQL を使っている場合とで区別しないといけないとう、ちょっと不思議な落とし穴(実装が違うといれば、わかりやすいんですが、コード的には LINQ だけなので見つけづらい)。

カテゴリー: ASP.NET | 2件のコメント

Entity Data ModelとLINQ to SQL は同時に使えない

ASP.NET MVC では Entity Data Model と LINQ to SQL が同時に使えません。つーか、同時に使うな、ってのがホントなんでしょうが、まぁ、Html.DropDownListFor メソッドが、あっちが動いたのに、こっちが動かない、という現象があったもので(この話が後日)。

まだ、公開されていませんが

連載! コードで学ぶ ASP.NET MVC アプリケーション開発入門 | Code Recipe | MSDN
http://msdn.microsoft.com/ja-jp/asp.net/gg490787

の次回は、Entity Data Model なんですね。

ですが、日経さんで出している「ひと目でわかる ASP.NET MVC」のほうは、実は、LINQ to SQL を使っているのです。
このあたり、執筆時に悩んだのですが、書籍のほうにはデータベースのややこしいところを取り込みたくなかったわけです。

ADO,NET Entity Data Model が ADO.NET の後継であることはわかっていたのですが(多少、不勉強ということもありますが)、このあたり、モデルを直結させてしまうとモデル自身の効用がわかりづらくなる、っていうのと、モデルの中にテーブルを含めるとちょっと、ってな感じで、薄さ(苦笑)を優先させたのです。

さて、

Entity Data Model の良いところは、SQL Server 2008 と直結しているところです。Visual Studio 2010 上でデザイナが動くのは、Entity Framework としては一部のところで、本音のところはコマンドラインで DDL と直結させたりする「親和性」のところにある気がします。DI コンテナなんてのを想起させます。なので、リレーション(外部キー)や主キーなんかが結構な役割を果たしています。

一方で、LINQ to SQL ってのは、(乱暴に言えば)オブジェクト指向に持ち込んだ SQL みたいなもので、ラムダ式やら無名関数やら拡張メソッドやらで、F# の影響を強く受けているわけですね。つまりは、プログラム言語からのアプローチ。

この話、どちらが「勝ち」という訳でもないわけで、ADO から続く DataSet やら、オブジェクト指向アプローチの O/R マッピングやらの歴史があるので、こんな感じなんですね。他にも、データベースには、ストアドプロシージャとビューがあるわけで、このあたり、「ビジネスロジック」を何処に置くのかとか、ある意味で「開発者に任せるのか」、「データベース管理者に任せるのか」というアプローチの違いも反映されていたりします。

だからという訳ではないんでしょうが(笑)、

こんな風に、Entity Data Model を追加した後に、LINQ to SQL も使いたいかなぁ、と思って追加すると、

20110119_01.jpg

ありゃ、LINQ to SQL のほうで、コンパイルエラー

20110119_02.jpg

20110119_03.jpg

どうやら、テーブルをインポートしたときの、TProduct クラスで既に _id が使われている模様。

実は namespace が両方とも、MvcApplication7.Models という形で Models に入っていて、TProduct クラスは partial で作ってある(分割されている)ので、クラス名の重複よりも、各メンバの重複が優先されてエラーになっているんですね…って、ややこしい。

ちなみに、別のテーブルをインポートすると大丈夫なので、正確には、

「同じテーブルを Entity Data Model と LINQ to SQL では使えない」

ですかね? namespace を変えればよいのかな?

 

 

カテゴリー: ASP.NET | 1件のコメント

ASP.NET MVC 3 で Entity Data Model を利用する

実は、ASP.NET MVC 2 と全く同じですね。

  • データベースから Entity Data Model を作る。
  • モデルを利用したコントローラーを作成する。
  • リストなどを表示させるビューを自動生成する。

ってな手順です。

こんな風なモデルを使って、

20110118_06.jpg

次のようにコントローラーを書いておきます。

このあたりは、ASP.NET MVC 2 と同じ。


        public ActionResult List()
        {
            Models.mvcdbEntities ent = new Models.mvcdbEntities();
            var model = ent.TProduct;
            return View(model);
        }

View の追加では、ASP.NET MVC 2 と違うのは、View engine の選択ですね。

これもデフォルトが Rasor になっています。ビューエンジン(レンダリングエンジン)は、ページごとに切り替えられるようです。

20110118_07.jpg

で、自動生成した結果がこれ。


@model IEnumerable<MvcApplication1.Models.TProduct>

@{
    ViewBag.Title = "List";
}

<h2>List</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>
            name
        </th>
        <th>
            price
        </th>
        <th>
            cateid
        </th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.id }) |
            @Html.ActionLink("Details", "Details", new { id=item.id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.id })
        </td>
        <td>
            @item.name
        </td>
        <td>
            @item.price
        </td>
        <td>
            @item.cateid
        </td>
    </tr>
}

</table>

実行結果は、ASP.NET MVC 2 と同じ。

20110118_08.jpg

Rasor のレンダリングエンジンを使うと、「@なんとか」で C# や Visual Basic のコードが書けます。

今までの aspx のコードは「<% … %>」で書いていたので、「@{ … }」と書けて便利。と言いますか、メリットはそれだけなのか?ちょっと、これはもう少し調べてみないとわかりませんが。

ただ、aspx と Razor の違いは、

  • aspx は、HTML 形式の中に、<% … %> でコードを埋め込む。
  • Razor は、HTML 形式の中に、@{ … } で、コードを埋め込む。
  • Razor は、コードの中に、ヒアドキュメント風に <text>…</text> で HTML 形式を埋め込む。

のところですね。

プロパティを使って表示するところは <%: item.name %> から、@item.name のように「@」マークひとつで書けるので、コードがすっきりしているといえばそうなのですが、PHP に慣れてくると、<?php … ?> で十分かぁと思ってしまうからなぁ。

このあたりは、

CakePHP: 高速開発 php フレームワーク。 Home
http://cakephp.jp/

との相互運用(相互補完?)とか考えてみたり。

 

 

カテゴリー: 開発, ASP.NET | ASP.NET MVC 3 で Entity Data Model を利用する はコメントを受け付けていません