شروع کار با Fluent NHibernate با یک نمونه کد

دسامبر 8, 2009 6 دیدگاه

اگر Fluent NHibernate را downdoad کرده‌اید آنرا اجرا کنید. وگر نه با استفاده از این لینک آن را دانلود کنید.

در این مثال ما یک domain ساده را برای یک کمپانی خرده‌فروشی در نظرگرفته‌ایم. فروشگاه کارمند و کالا دارد که هر کدام با یکدیگر ارتباط دارند. برای روشن شدن مسئله شکل زیر را ملاحظه کنید.

ابتدا یک console Application ایجاد کنید و fluent NHibernate.dll را به آن ارجاع دهید. و هر کدام از نسخه‌های NHibernate را که تمایل داشتید در آن قرار دهید. برای این مثال ما از دیتابیس SQLite استفاده می‌کنیم. همچنین شما به کتابخانهٔ  System.Data.SQLite که با fluent NHibernate ساخته شده است نیز نیاز دارید.

شما می‌توانید ساختار پروژه را در شکل روبرو ببینید. پوشهٔ Entities بخش واقعی آبجکت‌هاست و پوشهٔ mappings محل قرارگیری کلاس‌های mapping شما است.

پوشه Enteties :

اجازه دهید با ساخت entity ها شروع کنیم. ما سه جدول داریم که با هم ارتباط دارند و باید مپ شوند. برای هر کدام، کلاسی جداگانه و مخصوص به خود را در پوشهٔ Enteties می‌سازیم.

ابتدا با کلاس employee کارمان را آغاز می‌کنیم.

public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set;}
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}

همانطور که ملاحظه می‌کنید به ازای تمام فیلدهای جدول موجود در پایگاه داده، در این فایل یک صفت (property) داریم.

اگر شما با NHibernate ناآشنا هستید باید بدانید که دو چیز مهم و برجسته در اینجا وجود دارد. اول اینکه مشخصهٔ id به صورت private ، set شده است و این به این خاطر است که فقط NHibernate است که می‌تواند این مقدار را set کند. دوم اینکه همهٔ property ها virtual هستند، بخاطر اینکه NHibernate در حین اجرا از موجودیت‌ها proxy هایی می‌سازد که اجازه می‌دهد Lazy load باشد، یعنی اینکه قابلیت override را برای property ها داشته باشد.

دو کلاس دیگر را هم به صورت زیر می‌سازیم. (توجه کنید که به ازای فیلدهای جدول خودشان و با Type های صحیح اقدام به ایجاد صفت کنید، درغیر این صورت شما با مشکل روبرو خواهید شد. )

پیاده سازی کلاس محصول

public class Product
{
 public virtual int Id { get; private set; }
 public virtual string Name { get; set; }
 public virtual double Price { get; set; }
 public virtual IList<Store> StoresStockedIn { get; private set;{
 public Product()   //constructor
 {
 StoresStockedIn = new List<Store>();
 }
}
public class Store
{
 public virtual int Id { get; private set; }
 public virtual string Name { get; set; }
 public virtual IList<Product> Products { get; set; }
 public virtual IList<Employee> Staff { get; set; }
<pre>

پیاده سازی کلاس خرده‌فروشی (مغازه)


</pre>
public Store()
 {
 Products = new List<Product>();
 Staff = new List<Employee>();
 }
public virtual void AddProduct(Product product)
 {
 product.StoresStockedIn.Add(this);
 Products.Add(product);
 }

 public virtual void AddEmployee(Employee employee)
 {
 employee.Store = this;
 Staff.Add(employee);
 }
}

متدهای add employee و add product کمی به ساده‌تر شددن کد ما کمک می‌کنند. این متدها برای اضافه کردن یک item به collection و تنظیم کردن سمت دیگر رابطه استفاده می‌شوند. (برای اضافه کردن کالا و کارمند به فروشگاه)
پوشه Mappings :
حالا که entity ها را ساختیم وقت آن رسیده که آنها را با استفاده از fluent NHibernate مپ کنیم. با یک کلاس ساده مثل employee شروع می‌کنیم. Mapper های زیر باید در پوشهٔ mappings ساخته شوند.
برای مپ کردن هر entity شما باید یک کلاس مپ اختصاصی برایش ایجاد کنید. این کلاس مپینگ‌ها از ClassMapp<T> به ارث می‌روند و T ، درواقع نام entity ی ورودی شما است.

public class EmployeeMap : ClassMap<Employee>
{
 public EmployeeMap()
 {
 Id(x => x.Id);
 Map(x => x.FirstName);
 Map(x => x.LastName);
 References(x => x.Store);
 }
}

ستون id یک شناسه برای fluent NHibernate است و x مثالی از یک نمونه از employee است که fluent NHibernate از آن برای بدست آوردن جزئیات property ها استفاده می‌کند.

public class StoreMap : ClassMap<Store>
{
 public StoreMap()
 {
 Id(x => x.Id);
 Map(x => x.Name);
 HasMany(x => x.Staff).Inverse().Cascade.All();
 HasManyToMany(x => x.Products).Cascade.All().WithTableName("StoreProduct");
 }
}

همان طور که ملاحضه می‌کنید در اینجا فراخوان‌های دیگری نیز به چشم می‌خورد. اگر employee را بخاطر بیاورید در آنجا یک ارتباط یک به چند با store داشتیم. حالا هم در مپ کردن store باید این ارتباط را نیز در نظر بگیریم. HasMany یک ارتباط یک به چند را با employee برقرار می‌کند. در اینجا یک متد جدید HasManyToMany وجود دارد که ارتباط چند به چند با product را ایجاد می‌نماید.
Inverse در HasMany یک عبارت (کلمه کلیدی) در NHibernate است و در پایان ارتباط مسئولیت save را به عهده دارد.
CascadeAll در HasManyToMany به NHibernate می‌گوید که در سلسلهٔ آبشاری عمل save را انجام دهد.
WithTableName نام طرف دیگر ارتباط را در چند به چند مشخص می‌کند. برای سایر ارتباطات به وجود این قسمت نیاز نمی‌باشد.

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");
 }
}

Application :
در این بخش data ها مقداردهی اولیه می‌شوند.

static void Main()
{
 var sessionFactory = CreateSessionFactory();

 using (var session = sessionFactory.OpenSession())
 {
 using (var transaction = session.BeginTransaction())
 {
 // create a couple of Stores each with some Products and Employees
 var barginBasin = new Store { Name = "Bargin Basin" };
 var superMart = new Store { Name = "SuperMart" };

 var potatoes = new Product { Name = "Potatoes", Price = 3.60 };
 var fish = new Product { Name = "Fish", Price = 4.49 };
 var milk = new Product { Name = "Milk", Price = 0.79 };
 var bread = new Product { Name = "Bread", Price = 1.29 };
 var cheese = new Product { Name = "Cheese", Price = 2.10 };
 var waffles = new Product { Name = "Waffles", Price = 2.41 };

 var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" };
 var jack = new Employee { FirstName = "Jack", LastName = "Torrance" };
 var sue = new Employee { FirstName = "Sue", LastName = "Walkters" };
 var bill = new Employee { FirstName = "Bill", LastName = "Taft" };
 var joan = new Employee { FirstName = "Joan", LastName = "Pope" };

 // add products to the stores, there's some crossover in the products in each
 // store, because the store-product relationship is many-to-many
 AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese);
 AddProductsToStore(superMart, bread, cheese, waffles);

 // add employees to the stores, this relationship is a one-to-many, so one
 // employee can only work at one store at a time
 AddEmployeesToStore(barginBasin, daisy, jack, sue);
 AddEmployeesToStore(superMart, bill, joan);

 // save both stores, this saves everything else via cascading
 session.SaveOrUpdate(barginBasin);
 session.SaveOrUpdate(superMart);

 transaction.Commit();
 }

 // retreive all stores and display them
 using (session.BeginTransaction())
 {
 var stores = session.CreateCriteria(typeof(Store))
 .List<Store>();

 foreach (var store in stores)
 {
 WriteStorePretty(store);
 }
 }

 Console.ReadKey();
 }
}

public static void AddProductToStore(Store store, params Product[] products)
{
 foreach (var product in products)
 {
 store.AddProduct(product);
 }
}

public static void AddEmployeeToStore(Store store, params Employee[] employees)
{
 foreach (var employee in employees)
 {
 store.AddEmployee(employee);
 }
}

اما شما الان قادر به run  کردن برنامه نیستید زیرا ما نیاز داریم متد CreateSessionFactory را پیاده سازی کنیم.

private static ISessionFactory CreateSessionFactory()
{
 return Fluently.Configure()
.Database(
 SQLiteConfiguration.Standard
 .UsingFile("firstProject.db")
 )
 .Mappings(m =>
 m.FluentMappings.AddFromAssemblyOf<Program>())
 .BuildSessionFactory();
}

اگر شما برای برنامه نمی‌خواهید که Schema را دستی تهیه کنید، اجرای دفعهٔ اول fail می‌شود. و آن به این خاطر است که یک چیز دیگر در آبجکت NHibernate configuration نیاز دارید و آن متد expose configuration  است . با استفاده از آن شما قادرید در زمان اجرا (Run Time) Schema را بسازید. (اگر مایلید که این کار را انجام دهید) کد را به صورت زیر تغییر بدهید.

private static ISessionFactory CreateSessionFactory()
{
 return Fluently.Configure()
 .Database(
 SQLiteConfiguration.Standard
 .UsingFile("firstProject.db")
 )
 .Mappings(m =>
 m.FluentMappings.AddFromAssemblyOf<Program>())
 .ExposeConfiguration(BuildSchema)
 .BuildSessionFactory();
}

private static void BuildSchema(Configuration config)
{
 // delete the existing db on each run
 if (File.Exists(DbFile))
 File.Delete(DbFile);
 // this NHibernate tool takes a configuration (with mapping info in)
 // and exports a database schema from it
 new SchemaExport(config)
 .Create(false, true);
}

با استفاده از کد بالا Schema شما بصورت RunTime ایجاد می‌شود و شما لازم نیست که بصورت دستی Schema را ایجاد نمایید.
شما میتوانید نمونه‌های بیشتری از fluent configuration  را در این لینک مشاهده کنید.
لینک‌های استفاده شده برای تهیهٔ این مطلب :
http://wiki.fluentnhibernate.org/show/HomePage
http://wiki.fluentnhibernate.org/show/GettingStarted:+First+Project
http://wiki.fluentnhibernate.org/show/FluentConfiguration

دسته‌ها:Programming