ラベル MySQL の投稿を表示しています。 すべての投稿を表示
ラベル MySQL の投稿を表示しています。 すべての投稿を表示

2009年4月24日金曜日

MySQLとMonoでASP.NET MVC - その2

ということで、続きました。

LINQ to EntityがMonoで使えないとなると、やっぱりLINQ to MySQLを使おうかなというところに戻りますよね。これがまたなんだかんだとがんばってみたんだけど...。ガッカリな結果でした。理由はサッパリ分からない。最初はライセンスの問題なのかな~、とかいろいろ試してみたけどできなかった。

mono1

↑これMySQLとxsp2(Mono)で動かしてるんだけど、ちゃんとログインできてるのが確認できると思います。なのでMembershipProvider経由のDBアクセスは成功ってことです。だけど、Sakilaを使ったサンプルはやっぱりダメで。

mono3 mono2

左がMonoの実行結果で、右が同じ物をVista上で動かしたもの。悲しいな~。エラーの意味が全く分からない。なんでリフレクションのエラーなんだろ...。どうしよっかな~。

ってことで、ふとNHibernateが気になりだした。Monoでも動くし。ドライバはDevartじゃなくてConnector/NETを使えばいいから、無料で構築できるのも魅力的。ただ...、xmlでマッピングはマッピラだ。韻を踏もうとして失敗した。それはいいとして、Fluent NHibernateっていうのがあったのも思い出した。NHibernateを使ったことないんだし、いっそのこと最初からFluent NHibernateでいいじゃない。

Fluent NHibernate

まずはNHibernateを調べなきゃ。近道するためにDime castでNHibernate Episodesを全部見て分かった気になったところでFluent NHibernate。

Getting Started: First Project in Fluent NHibernate

ここでいきなりSakilaを使うのはちょっと難しそう(マッピングの仕方もサッパリ分からないし)ので、チュートリアルに書かれてるデータベースをMySQLに構築して作ってみることにしてみました。何となくリレーションに使う項目名がStore_id/Product_idっていうのがカッコ悪いきがするのでStoreId/ProductIdにしてみる。

んで、言われるがままにコンソールアプリケーションを作って、エンティティ定義して、マッピング定義して...。いざ実行!動かず。泣ける。これだけじゃダメ?そりゃそうですね。接続の設定とか全然してないし。Fluentで出来るのかもしれないけど、よくわかんないのでapp.configに書く。

NHibernate - Learning with Code Samples

でも、これもFluent出来るんだね。まぁ、いいや。そこは早足で後回し。

Fluent NHibernate - Configuration - Chad Myers' Blog -

リレーションのカラム名を変えて作っちゃったおかげで、マッピングに失敗して動かず。面倒なことをしてしまった...。

なんだかんだでEomployeeMap。

  public class EmployeeMap : ClassMap<Employee>
 {
   public EmployeeMap()
   {
     Id(x => x.Id);

     Map(x => x.FirstName);
     Map(x => x.LastName);
    
     References(x => x.Store)
       .ColumnName("StoreId");
   }
 }
ProductMapにも挑戦。
  public class ProductMap : ClassMap<Product>
 {
   public ProductMap()
   {
     Id(x => x.Id);
     Map(x => x.Name);
     Map(x => x.Price);
     HasManyToMany(x => x.StoresStockedIn)
         .Cascade.All()
         .Inverse()
         .WithTableName("StoreProduct")
         .WithChildKeyColumn("ProductId");
   }
 }
最後にちょっと難しそうなStoreMap。
  public class StoreMap : ClassMap<Store>
 {
   public StoreMap()
   {
     Id(x => x.Id);
     Map(x => x.Name);

     HasManyToMany(x => x.Products)
         .Cascade.All()
         .WithTableName("StoreProduct")
         .WithChildKeyColumn("ProductId")
         .WithParentKeyColumn("StoreId");

     HasMany(x => x.Staff)
         .KeyColumnNames.Add("StoreId")
         .Cascade.All()
         .Inverse();
   }
 }
たったこれだけのマッピングにえらい時間がかかってしまいました。もっと簡単にAccess.As~CaseFieldでできるのかもしれないけど、ここもスキップして実行!

mono4

おぉ~。チュートリアルと同じ結果だ。そりゃそうだ。今度はコレをASP.NET MVCで実装。すったもんだの末に動くようになりました。

mono5 mono6

mono7 mono8

ぱっと見、普通ジャンって思えるけど、コレ実行環境はxsp2。Mono上で動かしてるんですよ!感動ですよ!もちろんNHibernate.Linqでアクセス!

これだけだと、Fluent NHibernate(r453で試しました)は含まれて無いし、データベース定義も入って無いから動かないけど、その辺はチュートリアルを見ながら試してみて下さいまし。

一応、ダウンロードできるようにして置きます。興味があるならどうぞ。とりあえず、コードを説明。

namespace MonoTest.FluentNHibernate.Controllers
{
 [HandleError]
 public class HomeController : Controller
 {
   IStoreRepository _repository;

   public HomeController()
   {
     _repository = new StoreRepository();
   }

   protected override void Dispose(bool disposing)
   {
     base.Dispose(disposing);
    
     _repository.Dispose();
   }

   public ActionResult Index()
   {
     var list = _repository.AllStore();

     return View(list);
   }

   public ActionResult Details(int id)
   {
     var store = _repository.GetById(id);

     return View(store);
   }

   public ActionResult Edit(int id)
   {
     var store = _repository.GetById(id);

     return View(store);
   }

   [AcceptVerbs(HttpVerbs.Post)]
   public ActionResult Edit(int id, FormCollection forms)
   {
     var store = _repository.GetById(id);
     TryUpdateModel<IStore>(store, forms.ToValueProvider());

     if (!ModelState.IsValid)
       return View(store);

     try
     {
       _repository.Save(store);

       return RedirectToAction("Index");
     }
     catch{
       ModelState.AddModelError("", "保存に失敗デス");
     }

     return View();
   }

   public ActionResult About()
   {
     return View();
   }
 }
}

まずは、コントローラ。HomeControllerを書き換えてます。普通ですね。はい。データアクセスをRepositoryにしといたくらいです。でもDI使ってないデス。

ViewはMonoで動かすのに型付きには出来ないので、スキャッフォールディングで作成(コンテキストメニューのAdd View)した後に、PageディレクティブのInheritsは修正。さらに、LINQ to SQLでもLINQ to Entityでもないので、ID列も自動でリンクに反映されないのを修正。

Repositoryは↓これだけ。

  public interface IStoreRepository : IDisposable
 {
   IQueryable<Store> AllStore();
   Store GetById(int id);
   void Save(Store store);
 }
  public class StoreRepository : IStoreRepository
 {
   ISession _session;

   public StoreRepository() : this(DbSession.GetSession()) { }

   public StoreRepository(ISession session)
   {
     _session = session;
   }

   public IQueryable<Store> AllStore()
   {
     return from store in _session.Linq<Store>()
            select store;
   }

   public Store GetById(int id)
   {
     return AllStore().Where(s => s.Id == id).FirstOrDefault();
   }

   public void Save(Store store)
   {
     using (var transaction = _session.BeginTransaction())
     {
       _session.SaveOrUpdate(store);
       transaction.Commit();
     }
   }

   public void  Dispose()
   {
     if (_session == null )
       return;

     if (_session.IsConnected)
       _session.Close();

     _session.Dispose();
   }
 }

データベースのSession(DataContext的な?)は↓こう。

  public class DbSession
 {
   private static ISessionFactory CreateSessionFactory()
   {
     return Fluently.Configure()
         .Database(
           MySQLConfiguration.Standard.ConnectionString(c =>
             c.FromConnectionStringWithKey("MySqlServices")))
         .ExposeConfiguration(c => c.Properties.Add("use_proxy_validator", "false"))
         .Mappings(m =>
             m.FluentMappings.AddFromAssemblyOf<DbSession>())
         .BuildSessionFactory();
   }

   public static ISession GetSession()
   {
     return CreateSessionFactory().OpenSession();
   }
 }

この辺、どうやって作るのがいいのかまだよく分かってないけど、とりあえずは動くものってことで。

Fluent NHibernateをORMに使って、データアクセスにはLINQ、データベースはMySQLでドライバはMySQL.Data(Connector/Net)。実行環境はCent OS 5.3上のMono 2.4。完全にフリーで動かす環境で、ASP.NET MVCを使ったプロダクト開発が出来ることが分かりました!過負荷の時にも上手く動き続けるのかは今後の課題。運用時にはxspじゃなくてApache+mod_monoで動かしましょう。

で、ここまでやってやっとSharp Architectureの良さに気がつくわけですね。

sharp-architecture - Google Code

Sharp Architectureを使うと、ASP.NET MVC用のヘルパーがちゃんと用意されてて、NHibernateが更に使いやすいみたい。T4 Toolboxでのマッピングの自動生成や、Sessionを管理出来るTransactionAttributeなんかがあるし、プロジェクトのフォルダ構成なんかがきちんと区切られてて、もっともっとちゃんと調べて使えるようになれば、Windows環境だけじゃなく、Monoにもデプロイ出来るプロジェクトを綺麗に作れること間違いなし。

#75 - Introdction to S#arp Architecture

Dime Castsにもビデオあるから、これも興味あれば見てみるといいと思います。10分くらいだし。

しかし、分からないことだらけで、時間かかりすぎたな~。もっと、サクッと動くコードが書けると思ったのに。

追記

Monoで動かす際に、System.Web.Mvc/System.Data.Servicesはアセンブリのローカルコピーを忘れずにね。

MySQLとMonoでASP.NET MVC

思い出す度にいろいろ試してみたけど、どうにもこうにも...。

Using MySQL with Entity Framework and ASP.NET MVC – Part I « Christopher Patterson

ホントにConnector/Net 6.0でデザイナーつかってORマッピングの定義できるの~??どんだけやってもできないんだけどな~。とりあえず現バージョンではできないってことにしとこう。自分で定義ファイルを書けばできるんだろうけど、それじゃ~、全然便利じゃないしよくわかんないし。あ、コマンドラインで実行したらできたりするのかな?まぁ、いいや。

なので、有料だけどトライアルもあるし、DevartのdotConnect for MySql(Download dotConnect for MySQL)を使う事にする。流石にコレならできるでしょう。買う事になってもDeveloper Editionで$229.95だしね。個人でも買える値段で助かる(買わずにトライアルだけど)。

まずは、インストール。サクッと終了。チュートリアル「dotConnect LINQ to MySQL Tutorial」を見ながら、コードを書いてみようと思ったけど、どうも"Devart LINQ to SQL Model"のItemTemplateが見つからない。おやや?インストールに失敗したのかな?と思いきや、なんの事はない、VSのテンプレートフォルダを見てみたら1033(英語)には入ってるけど1041(日本語)には入って無かっただけでした。DevartLINQtoSQL.zipを1041フォルダにコピーしてdevenv /InstallVSTemplates。これでプロジェクトに追加出来るようになりました。ちなみにテストで使うデータベースのサンプルをどうしようか。検索してみたらMySqlだとSakilaっていうのがNorthwindやAdventureWorks的なデータベースっぽいのでそれをインストールして使う事にしました。

SakilaSampleDB - MySQL Forge Wiki

で、Sakila.lqmlという名前で作成したので、デザイナーでテーブルをチョイチョイ登録しようと思ったら、外部デザイナーが立ち上がった。

devart1

ん?なんか違和感。そういうものなのかな、と思いつつ、適当にデータベース登録してテーブル追加して作成。

devart2

で?えーと、あ、ProjectメニューにGenerateってある。これをクリックして、なにやらファイルを出力してみる。で、VSに戻ってコンパイル。おぉ~。なんか普通に使える~。

※新しいバージョンで試してみたら、勝手に生成してくれるようになってた。

でも、待てよ。Entity Frameworkってこんなじゃなかったような...。ちゃんと見たら全然違うの作ってた。LINQ to MySqlって書いてるジャン...。やりたかったのはコレじゃなくて"dotConnect for MySQL Entity Framework Tutorial"こっちでした。これはこれで普通に取得やら更新もできたんだけど、リレーションのデータがちゃんと取れなくて、なんでなんで~と頭を抱えて損した。まぁ、いっかと、気を取り直してEntity Framework。今度は間違えないように"ADO.NET Entity Data Model"をちゃんと選んでテーブルを追加。そうそう、これこれ。このウィザードでテーブルを選ぶところでエラーが出て先に進まなかったんだけど、今度はなんの問題も無くクリア。

devart3 devart9

あとは、Scaffoldでちゃちゃっと作る。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

using System.Transactions;
using MonoTest.Models;

namespace MonoTest.Controllers
{
    public class SakilaController : Controller
    {
      //
      // GET: /Sakila/
      sakilaEFEntities context;

      public SakilaController()
      {
        context = new sakilaEFEntities();
      }

      public ActionResult Index()
      {
        var list = (from act in context.actor
                    select act).Take(10).ToList();
        return View(list);
      }

      public ActionResult Details(int id)
      {

        var actor = (from act in context.actor
                                        .Include("film_actor")
                                        .Include("film_actor.film")
                     where act.actor_id == id
                     select act).FirstOrDefault();
        if (actor == null)
          return RedirectToAction("Index");
        
        return View(actor);
      }

      public ActionResult Edit(int id)
      {
        var actor = (from act in context.actor
                     where act.actor_id == id
                     select act).FirstOrDefault();
        if (actor == null)
          return RedirectToAction("Index");

        return View(actor);
      }

      [AcceptVerbs(HttpVerbs.Post)]
      public ActionResult Edit(int id, actor actor)
      {
        if (ModelState.IsValid)
        {
          using (var ts = new TransactionScope())
          {
            var entity = (from act in context.actor
                          where act.actor_id == id
                          select act).FirstOrDefault();
            if (entity != null && actor != null)
            {
              actor.actor_id = id;
              actor.last_update = DateTime.Now;
              context.ApplyPropertyChanges(entity.EntityKey.EntitySetName, actor);
              context.SaveChanges();
            }
            ts.Complete();

            return RedirectToAction("Details", new { id });
          }
        }

        return View(actor);
      }
    }
}

Controllerはこんなので。

devart4 devart5 devart6

ちゃんと動く~。CentOS側のデータを確認してみる。

devart7

ちゃんと更新できてる~。なんか嬉しい。でも、Monoで動かしてるわけじゃないので当たり前っすね。これをMonoで動かすのが目的なのでxsp2!

devart8

ガッカリです。エラー出て動きませんでした。

ASP.NET Provider Model Support in dotConnect for MySQL

色々試してみたけど、どうやっても動かない。うぎゃ~!!と思って調べて見たら...。MonoではLINQ to Entityが動かないらしい...。凹む。ベコっと音が出た。

続く。

2009年4月13日月曜日

ASP.NET MVCをMonoで動かしてみる

長い戦いだった...。現時点での結論から言えばそれなりに動くけど、型付きViewPageだけはどうにもならない感じでしょうか。

そもそもMonoでASP.NET MVCを動かすことにどれほどの意味があるのかという所だけど、これは凄く意味のあることですよね。なんせ実行環境はすべてオープンソースで構築できるんだから。こうなってしまえば個人的にはJavaに何もメリットを感じない。Google App Engineが羨ましいくらいか。でもPythonあるし、そもそもAzureがあるし。

構築した環境はVista Ultimate 32bitにVMWare Server 1.0.9を入れ(2.0.1だとどうも上手く動かなかった)、Linuxディストリビューション(っていうの?)はCentOS 5.3openSUSE 11.1、Mono 2.4とMonoDevelop 2.0、データベースにMySQL 5.0.45(CentOS上にyum install)。

My Adventures Installing mono 2.0 on CentOS 4 to work with apache via mod_mono « Pale Musings

Linuxを生まれて初めて触ってみたけど、さっぱりチンプンカンプンですね。でも、ネットで検索すればいくらでも情報が出てくるから、とりあえず動かすくらいならどうとでもなるもんです。ちゃんとまともに運用環境を作るにはかなり難しそうな気がするけどね。

何でCentOSとopenSUSEを入れたかたというと、MonoDevelopをCentOSにインストールするのに3日がんばったけどできなかったから。GLibのバージョンが入ってるのじゃダメと言われ、ソースコンパイルからやってみたけど、芋ずる式にアレもコレもダメだと言われ、コンパイルできてmake installで実行しても、Gtk+からちゃんと新しいのが見えないと言われ...。うぎゃ~!!となってやめました。Monoはすんなり入るのにね。で、一番簡単なのはNovellが出してるディストリビューションを使う事だと思って、openSUSEも入れることにしたんです。まぁ一緒だろと思ったらyumじゃなくてzypper(ソフトウエア管理のコマンドライン比較 – openSUSE)だとかよくわかんないことに時間を取られたものの、流石Novellなだけあってワンクリックでインストールもあっという間に完了。

mono1 mono2

MonoDevelopでASP.NET MVCのプロジェクトを新規作成したところ、流石にテンプレートは空っぽでControllerもViewも何もない状態でした。T4無いしね(たぶん)。なので、簡単にControllerとViewを作って動かしてみると、あら素敵。アッサリ動いた。簡単なものだけど、動くとビックリ。スゴイね~。んじゃ、ってことで、コレをCentOSの方にコピーしよと思ってハタと手が止まる。あれ?こういう時ってFTP?それともSSHっていう何かよくわかんないシェル?ふぬ~。あ、産婆。じゃなくてsamba。

Windowsファイルサーバー構築(Samba) - CentOSで自宅サーバー構築

これをCentOSとoenSUSE両方で起動してコピペ。ちなみにVMWareで動かしてるときにopenSUSEは何もしなくてもマウスが動くしGNOME端末での日本語入力もできるのに、CentOSはなんかやらなきゃいけないみたいで。VMWare toolsっていうんですかね。よくわかんないけど、さんざんやって上手く出来なかったから諦めた。いいもん。毎回Alt+Ctrl押すもん。

とりあえず、MonoDevelopだとVisual Studioに慣れた体では開発しにくいし、どうせ動かすのはCentOSだしってことで、ここヵらはCentOSにデプロイ、Vista+VSで開発に戻ります。openSUSEで動かしてもいいんだけどさ~。なんとなくきかん坊が気になるからさ~。

mono3

VSで作ったデフォルトのプロジェクトをそのままの状態でビルド>発行して、CentOSにコピー。んで、Apacheで動かすためにmod_monoの設定をしようかなってところで、XSPっていうのが簡易Webサーバーの役割を果たしてくれるということなんで、コピーしたフォルダをカレントに"xsp2"!アッサリ動くの図↑。

続いてMembershipを使いたいから、データベースを入れなきゃいけないよね。なのでMySQL。入れるのは簡単。とにかくyumさん。なんだこいつ、スゴイヤツだ。sudoさんと仲がいいのか?最初はsuで切り替えてやってたけど、普通のユーザーアカウントで読み取れないフォルダがばかばかできちゃって面倒なことになったから、結局何度もやり直して須藤さんsudo(SuperUser DO?)に任せる事に。

でもって、MembershipProviderがなんかあるはず。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 24.2.2.2 Mono を使用した Unix に Connector/NET をインストールする

あるのね...。Connector/Netと言われても...。GACに登録したいけど、インストールパスってどこなんでしょう。そもそもどこにインストールされてるのかが分からない。"/usr/lib/mono/2.0"にいろいろ入ってみたいだけどここでいいのかな...。とりあえずコピーしてgacutilでグローバルアセンブリキャッシュに入れて見る。エラーは出てないからいいのかな。

んで"nolan bailey's blog: MySQL ASP.NET Membership and Role Provider"や”knowledge shared is knowledge²: ASP.NET and MySQL - membership provider (part 1)”ここに書かれてるようにweb.configをセット。autogenerateschema=”true”にしておくと勝手にテーブルを準備してくれるからaspnet_regsqlやInstallMembership.sqlをどうのこうの気にしなくてもいいから楽ちんです。忘れずにセットしなきゃね。

mono4

うほほ。ちゃんとデータベースに登録されました。

でも、本当はここが最初ちゃんと動かなくて、あれれな感じだったのがプロジェクトのアセンブリに追加したMySql.Webをローカルコピーしとくようにしたらなんとなく先に進むようになるものの、そこからさらにエラーでなんとも動かなくて。

mono5 mono6

ユーザー登録はできたのに、ログインでエラーになるの図。これはなんだも。

System.ArgumentNullException: Argument cannot be null.
Parameter name: type
 at System.ComponentModel.TypeDescriptor.GetConverter (System.Type type) [0x00000]
 at System.Web.Mvc.DefaultModelBinder.BindModel (System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext) [0x00000]
 at System.Web.Mvc.ControllerActionInvoker.GetParameterValue (System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ParameterDescriptor parameterDescriptor) [0x00000]
 at System.Web.Mvc.ControllerActionInvoker.GetParameterValues (System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ActionDescriptor actionDescriptor) [0x00000]
 at System.Web.Mvc.ControllerActionInvoker.InvokeAction (System.Web.Mvc.ControllerContext controllerContext, System.String actionName) [0x00000] 
なんでこんなエラーが...。全然ダメじゃないか。いろいろ検索してみたら同じ現象が起きてる人もいて。

http://go-mono.com/forums/#nabble-to23006435

特に返信もなくて途方に暮れかかったけど、Forumあるならスレッド見ていけばなにかヒントがあるかもと、見ていくと、↓こんな書き込みがありました。

http://go-mono.com/forums/#nabble-to22854337

Windowsで使ってるアセンブリをローカルコピーしとけと。なるほど。確かにバイナリ互換なMonoなんだからそういう手もありだ。System.Web.Mvc.dllをコピーして動かす。

mono7

拡大すると見えるけど、ちゃんとログインできてますね。グレート!

あとはLINQでデータベースにアクセスできれば普通に開発できる。でも、最初に書いたとおり型付きViewPageだけは今のところ解決策が見つからずなので、当面はコードビハインド指定で乗り切ることになるのかな。そんなこんなでここまで5日ほどかかったけど、面白い発見もいろいろあって実行環境としてLinuxも選択肢に入れていこうと思ったり思わなかったり(EC2もWindows Server+SQLServerよりもLinux+Mono+MySQLの方が同じインスタンスでも安く運用できるからね)。

dotnetConf2015 Japan

https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。