สร้าง mapping fluently Domain Model ด้วย Fluent NHibernate… Actions!

แนะนำ NHibernate

NHibernate คือ open source Object Relational Mapper (ORM) ที่เก่าแก่ และเป็นที่นิยมมาก โดยพื้นฐานการพัฒนาของมันมาจากโปรเจ็ค Hibernate ของ Java ดังนั้นมันจึงเป็นที่สนใจของนักพัฒนา ORM ในโลกของ Java เมื่อต้องการพัฒนา ORM ในโลกของ .NET และในโลก ORM ของ .NET ที่ต้องการเข้าไปพัฒนา ORM ในโลกของ JAVA มันก็คล้ายๆกับ Spring กับ Spring.NET Framework สำหรับพัฒนา Dependency Injection(DI) และ cross cutting concern หรือ Aspect Oriented Programming(AOP) ซึ่งเป็นที่น่าสนใจของทั้งสองโลกนั่นเอง

เครื่องมือพัฒนา ORM อย่างเช่น LINQ to SQL, Entity Framework, และ NHibernate, มันก็คือกระบวนการแปลงไปแปลงมาระหว่าง Relational Model โดยมี Tables, Columns, และ Keys ของ Database กับ Domain Models โดยมี Classes และ Properties ของ application

เครื่องไม้เครื่องมือ

บทความนี้ผมจะนำเสนอการพัฒนาในแบบ Code-First หรือ Domain Model-First Development โดยเริ่มจากออกแบบ Domain Model ก่อน ต่อมาก็สร้าง mapping แบบ code config ด้วยรูปแบบ fluently จาก Domain model ไปยัง table ของ Database server ด้วย Fluent NHibernate ขั้นต่อนต่อมาก็ generate schema table จาก Domain Model กับ Mapping Classes ด้วย library NHibernate ที่เตรียมไว้ให้ใช้แล้ว และปิดท้ายด้วย Unit Test ง่ายๆอีกเล็กน้อย  เครื่องมือที่คุณต้องมีก่อนทำ

1. Microsoft Visual Studio 2010 สำหรับพัฒนา Software Application

2. install plugin Nuget เตรียมไว้ก่อน download ได้จาก http://nuget.org/

3. Database Server ผมเลือกใช้  PostgreSQL คุณ download ได้จาก http://www.postgresql.org/download/

สร้าง Domain Model

1. เปิด Visual Studio สร้าง New Projects… แบบ Class Library ตั้งชื่อว่า Enterprise.Domains

2. ออกแบบ และ Code Domain Model ซึ่งผมได้ออกแบบง่ายๆ เรื่องของ Product ที่มี hierarchy คือ Book กับ Movie โดย Moview มี relationship one-to-many กับ ActorRole ตาม UML Class diagram ข้างล่างนี้ครับ

ดังต่อไปนี้คือ Code

// Domain.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Enterprise.Domains
{
    public abstract class Domain<T>
    {
        public virtual T Id { get; protected set; }
        public virtual int Version { get; protected set; }
        public override bool Equals(object obj)
        {
            return Equals(obj as Domain<T>);
        }
    }
}

// Product.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Enterprise.Domains
{
    public class Product : Domain<int>
    {
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }
        public virtual decimal UnitPrice { get; set; }
    }
}

// Book.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Enterprise.Domains
{
    public class Book : Product
    {
        public virtual string ISBN { get; set; }
        public virtual string Author { get; set; }
    }
}

// Movie.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Enterprise.Domains
{
    public class Movie : Product
    {
        public virtual string Director { get; set; }

        public virtual IList<ActorRole> Actors { get; set; }
    }
}

// ActorRole.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Enterprise.Domains
{
    public class ActorRole : Domain<int>
    {
        public virtual string Actor { get; set; }
        public virtual string Role { get; set; }
    }
}

สร้าง Mapping Model

1. เปิด Visual Studio สร้าง New Projects… แบบ Class Library อีก 1 projects ครับตั้งชื่อว่า Infrastructure.Orm.FluentNHibernate ที่ผมสร้างอีก projects เพราะไม่ต้องการให้ objects ทางเทคนิคใดๆ เข้าไปผสมรวมกับ library ของ Domain Model ครับ ดังภาพนี้ครับ

2. ติดตั้ง Fluent NHibernate  โดย download จาก http://fluentnhibernate.org/downloads แล้วแตก zip ออกมา หรือ install ผ่าน Nuget จะง่ายกว่าครับผมแนะนำ โดยไปที่ Menu > Tools > Library Package Manager > Package Manager Console ตามภาพครับ

พอเลือกแล้วจะขึ้นหน้าจอ Package Manager Console ให้เลือก Default projects เป็น Infrastructure.Orm.FluentNHibernate แล้วใส่คำสั่ง PM> Install-Package FluentNHibernate กด enter ครับ รอมัน download สักพักจะได้แบบนี้ครับ

3. มาที่ project Infrastructure.Orm.FluentNHibernate  แล้ว Add References library Enterprise.Domains เข้ามา และเพิ่ม Class mapping fluently Domain Model ได้แก่ ProductMapping, BookMapping,
MovieMapping, ActorRoleMapping ได้ code ดังนี้

// ProductMapping.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using FluentNHibernate.Mapping;
using Enterprise.Domains;

namespace Infrastructure.Orm.FluentNHibernate
{
    public class ProductMapping : ClassMap<Product>
    {
        public ProductMapping()
        {
            Id(p => p.Id).GeneratedBy.Increment();
            DiscriminateSubClassesOnColumn("ProductType");
            Version(p => p.Version);
            NaturalId()
            .Not.ReadOnly()
            .Property(p => p.Name);
            Map(p => p.Description);
            Map(p => p.UnitPrice)
            .Not.Nullable();
        }
    }
}

// BookMapping.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Enterprise.Domains;
using FluentNHibernate.Mapping;

namespace Infrastructure.Orm.FluentNHibernate
{
    public class BookMapping : SubclassMap<Book>
    {
        public BookMapping()
        {
            Map(p => p.Author);
            Map(p => p.ISBN);
        }
    }
}

// MovieMapping.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentNHibernate.Mapping;
using Enterprise.Domains;

namespace Infrastructure.Orm.FluentNHibernate
{
    public class MovieMapping : SubclassMap<Movie>
    {
        public MovieMapping()
        {
            Map(m => m.Director);
            HasMany(m => m.Actors)
            .KeyColumn("MovieId")
            .AsList(l => l.Column("ActorIndex"));
        }
    }
}

// ActorRoleMapping.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentNHibernate.Mapping;
using Enterprise.Domains;

namespace Infrastructure.Orm.FluentNHibernate
{
    public class ActorRoleMapping : ClassMap<ActorRole>
    {
        public ActorRoleMapping()
        {
            Id(ar => ar.Id).GeneratedBy.Increment();
            Version(ar => ar.Version);
            Map(ar => ar.Actor)
            .Not.Nullable();
            Map(ar => ar.Role)
            .Not.Nullable();
        }
    }
}

สร้าง database จาก Mapping Model

ตอนนี้เรามี mapping fluently models และ domain models พร้อมแล้ว ขั้นตอนต่อไป เราจะสร้าง database กันครับ โดยเราจะสร้าง table ทั้งหมดจาก mapping class ของเราโดยใช้ library NHibernate ตามนี้

1. เปิด database PostgreSQL  เพื่อสร้าง database ตั้งชื่อว่า enterprise แล้วเลือก owner เป็นชื่อที่คุณได้สร้างไว้ตอนติดตั้ง PostgreSQL(ของผมชื่อ kading)เตรียมไว้ตามภาพ

2. กลับมาที่ visual studio projects เพิ่ม reference Npgsql.dll ซึ่งเป็น library database provider สำหรับ .NET กับ PostgreSQL  โดยตอนที่คุณ install PostgreSQL  มันจะมี options ให้คุณเลือกติดตั้งไว้ อยู่ใน folder

C:\Program Files (x86)\PostgreSQL\Npgsql หรือถ้าลืมก็ให้ติดตั้งได้จากเครื่องมือ Application Stack Builder ของ PostgreSQL ก็ได้ โดยไปที่ start > App Programs > PostgreSQL 9.1 > Application Stack Builder ตามภาพข้างล่าง

หน้าจอต่อมาให้เลือกที่ database PostgreSQL บนเครื่องของเราแล้วกด Next ตามภาพ

สุดท้ายที่ Catagories เลือก Database Drivers แล้ว check ที่  Npgsql v2.xx แล้วกด Nextๆไปจนเสร็จครับ ตามภาพ(จากภาพจะสังเกตุว่าผมได้ทำการติดตั้งมันแล้ว)

3. เขียน code คำสั่งนี้เพื่อสร้าง table จาก class mapping ตามนี้

//Program.cs
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
using NHibernate.ByteCode.Castle;

public class Program
{
    static void Main()
    {
        var nhConfig = Fluently.Configure()
        .ProxyFactoryFactory<ProxyFactoryFactory>()
        .Database(PostgreSQLConfiguration.Standard
        .ConnectionString(connstr => connstr
        .Host("localhost")
        .Port(5432)
        .Database("enterprise")
        .Username("kading")
        .Password("1234")))
        .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ProductMapping>())
        .BuildConfiguration();

        var schemaExport = new SchemaExport(nhConfig);
        schemaExport.Create(false, true);//สร้าง table โดย NHibernate Configure จาก class mapping ทั้งหมดที่เพิ่มมาจาก assenbly Infrastructure.Orm.FluentNHibernate ที่ได้ทำไว้แล้ว
    }

}

4. หลังจาก run code ข้างต้นแล้วกลับไปเปิด database enterprise ที่ PostgreSQL ดูครับคุณจะพบว่า table ได้ถูกสร้างขึ้นเรียบร้อยแล้ว ตามภาพนี้

ทดสอบกันหน่อย

Code สร้าง product ใหม่

    [TestMethod]
    public void TestCreateBooks()
    {
        var nhConfig = Fluently.Configure()
        .ProxyFactoryFactory<ProxyFactoryFactory>()
        .Database(PostgreSQLConfiguration.Standard
        .ConnectionString(connstr => connstr
        .Host("localhost")
        .Port(5432)
        .Database("enterprise")
        .Username("kading")
        .Password("1234")))
        .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ProductMapping>())
        .BuildConfiguration();

        var sessionFactory = nhConfig.BuildSessionFactory();

        using (var session = sessionFactory.OpenSession())
        using (var tran = session.BeginTransaction())
        {
            var book001 = new Book
            {
                ISBN = "0684841487",
                Author = "Michael E. Porter",
                Name = "Competitive Strategy: Techniques for Analyzing Industries and Competitors",
                UnitPrice = 23.60m,
            };

            session.Save(book001);

            tran.Commit();
        }
    }

เปิด table produce ดูผล

2. ลองแก้ไขราคาหนังสือดูอีกที

    [TestMethod]
    public void TestUpdateBooks()
    {
        var nhConfig = Fluently.Configure()
        .ProxyFactoryFactory<ProxyFactoryFactory>()
        .Database(PostgreSQLConfiguration.Standard
        .ConnectionString(connstr => connstr
        .Host("localhost")
        .Port(5432)
        .Database("enterprise")
        .Username("kading")
        .Password("1234")))
        .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ProductMapping>())
        .BuildConfiguration();

        var sessionFactory = nhConfig.BuildSessionFactory();

        using (var session = sessionFactory.OpenSession())
        using (var tran = session.BeginTransaction())
        {
            var book001 = session.Get<Book>(1);
            book001.UnitPrice = 16.40m;//เปลี่ยนจาก 23.60 เป็น 16.40
            book001.Description = "You Save";//เพิ่มคำอธิบายนิดนึง

            session.Update(book001);

            tran.Commit();
        }
    }

เปิด table produce ดูผล

คุณจะเห็นว่า version เพิ่มไปอีก 1 โดยอัตโนมัติ ผมตั้งใจทำให้คุณเห็นแบบนั้นเพราะมันมีประโยชน์หากคุณต้องการทำ data concurrency

3. ทดสอบเพิ่ม relationship one-to-many อีกสักตัวอย่างครับ

    [TestMethod]
    public void TestCreateOneToMany()
    {
        var nhConfig = Fluently.Configure()
        .ProxyFactoryFactory<ProxyFactoryFactory>()
        .Database(PostgreSQLConfiguration.Standard
        .ConnectionString(connstr => connstr
        .Host("localhost")
        .Port(5432)
        .Database("enterprise")
        .Username("kading")
        .Password("1234")))
        .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<ProductMapping>())
        .BuildConfiguration();

        var sessionFactory = nhConfig.BuildSessionFactory();

        using (var session = sessionFactory.OpenSession())
        using (var tran = session.BeginTransaction())
        {
            var movie001 = new Movie
            {
                Name = "The Tourist",
                Description = "ทริปลวงโลก",
                Director = "ฟลอเรียน เฮนซ์เคล วอน ดอนเนอร์สมาร์ก",
            };

            var actor1 = session.Save(new ActorRole { Actor = "จอห์นนี่ เดปป์", Role = "พระเอก" }) as ActorRole;
            var actor2 = session.Save(new ActorRole { Actor = "แองเจลิน่า โจลี่", Role = "นางเอก" }) as ActorRole;

            movie001.Actors = new ActorRole[] { actor1, actor2 };

            session.Save(movie001);

            tran.Commit();
        }
    }

เปิด table produce และ actor ดูผล

จบแล้วครับ กับบทความ สร้าง mapping fluently Domain Model ด้วย Fluent NHibernate… Actions! หวังว่าคุณคงจะได้รับประโยชน์บ้างหากคุณต้องพัฒนา Software Application ที่มีการจัดเก็บ แก้ไข สืบค้น และลบออก ข้อมูล Domain Model ของคุณโดยใช้ ORM จาก NHibernate ไปยัง database PostgreSQL ซึ่งทั้งคู่ เป็น Open Source ยกเว้นก็แต่ .NET ครับที่อาจจะต้องยอมจ่ายเพื่อให้ได้ความเร็ว สะดวก ง่ายและรวดเร็วในการพัฒนา Software Application

ขอบคุณครับ 🙂

” ใครบอกว่าความสำเร็จเกิดจากความพยายาม ผมคิดว่ามันไม่ใช่

ความสำเร็จมันเกิดจากความพยายามอย่าง สุดกำลัง-สุดปัญญา

เพียงเพื่อทำให้มัน ดีที่สุดในโลก เจ๋งที่สุดในโลก เลิศที่สุดในโลก

และมันต้องคงอยู่อย่างนั้นตลอดไปยันโลกแตก(ลูกบวช)ไปเลย ต่างหากละ “

— Chav:P —

ข้อมูลอ้างอิง:

  • NHibernate 3.0 Cookbook โดย Jason Dentler
  • Domain-Driven Design: Tackling Complexity in the Heart of Software โดย Eric Evans
Advertisements

#net, #nhibernate