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

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が動かないらしい...。凹む。ベコっと音が出た。

続く。

dotnetConf2015 Japan

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