استفاده از Castle ActiveRecord در پروژه های تحت وب

در سری آموزشی Castle ActiveRecord با یک پروژه مثال تحت ویندوز پیش رفتیم و همانطور که مشاهده کردید نکته خاصی در مورد نوع پروژه که تحت ویندوز بود وجود نداشت. اما استفاده از یک ORM در پروژه های تحت وب به علت ماهیت خاص وب، استراتژی خاصی را نیز می طلبد. در یک پروژه تحت وب، ممکن است کاربران زیادی وجود داشته باشند که هر کدام درخواست هایی را برای برنامه ما می فرستند که اکثر این درخواست ها مربوط به دسترسی به داده ها باشند.

ساده ترین روش برای استفاده از Castle ActiveRecord در یک برنامه تحت وب اضافه کردن خاصیت isWeb=»true» در بخش کانفیگ CAR در فایل web.config است. این کار باعث می شود تا CAR مجبور به استفاده از استراتژی متفاوتی برای نگهداری نمونه های Sessionهای NHibernate بکار بگیرد. در اینجا قصد نوشتن توضیحات اضافه را ندارم، بهتر است نمونه کانفیگ ActiveRecord برای یک برنامه تحت وب را مشاهده کنید :

<activerecord
 isWeb="true"
 isDebug="true"
 threadinfotype="Castle.ActiveRecord.Framework.Scopes.HybridWebThreadScopeInfo, Castle.ActiveRecord">
 <config>
 <add key="connection.driver_class"
 value="NHibernate.Driver.SqlClientDriver"/>
 <add key="dialect"
 value="NHibernate.Dialect.MsSql2008Dialect"/>
 <add key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider"/>
 <add key="connection.connection_string"
 value="Data Source=.\SQLEXPRESS;Initial Catalog=RegisterUsers;Integrated Security=True;Pooling=False"/>
 <add key="proxyfactory.factory_class"
 value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
 <add key="SessionScopeWebModule"
 value="Castle.ActiveRecord.Framework.SessionScopeWebModule"
 name="SessionScopeWebModule"
 type="Castle.ActiveRecord.Framework.SessionScopeWebModule"/>
 </config>
 </activerecord>
 

برای اینکه بتوانیم از الگوی Session per Request استفاده کنیم (همانطور که برای NHibernate پیاده سازی می کنند) باید یک کلاس با کدهای زیر به پروژه خودمان اضافه کنیم :

public class SessionModule : HttpApplication
{
 public SessionModule()
 {
 BeginRequest += new EventHandler(OnBeginRequest);
 EndRequest += new EventHandler(OnEndRequest);
 }

 protected void Application_Start(Object sender, EventArgs e)
 {
 BeginRequest += new EventHandler(OnBeginRequest);
 EndRequest += new EventHandler(OnEndRequest);
 }

 public void OnBeginRequest(object sender, EventArgs e)
 {
 HttpContext.Current.Items.Add("ar.sessionscope", new SessionScope());
 }

 public void OnEndRequest(object sender, EventArgs e)
 {
 try
 {
 SessionScope scope = HttpContext.Current.Items["ar.sessionscope"] as SessionScope;

 if (scope != null)
 {
 scope.Dispose();
 }
 }
 catch (Exception ex)
 {
 HttpContext.Current.Trace.Warn("Error", "EndRequest: " + ex.Message, ex);
 }
 }
}

و البته خطوط زیر را به بخش system.web فایل web.config اضافه کنیم :

<httpModules>
 <add name="ar.sessionscope"
 type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord"/>
</httpModules>

هنوز کارمان تمام نشده. باید ActiveRecord را در فقط برای اولین اجرای برنامه راه اندازی کنیم. اگر یادتان باشد در یک برنامه تحت ویندوز، راه اندازه اولیه ActiveRecord را در هنگام اجرای برنامه در متد main فایل Program.cs می نوشتیم. در یک برنامه تحت وب این کار را باید در Application_Start انجام دهیم. این متد در یک برنامه تحت وب فقط یکبار و در زمان اجرای اولیه صدا زده می شود و بهترین مکان برای راه اندازی اولیه فریم ورک ActiveRecord است. این متد زمانی که یک فایل Global.asax به پروژه خود اضافه می کنید ایجاد می شود. بدنه این متد برای راه اندازی اولیه ActiveRecord چیزی شبیه کدهای زیر می تواند باشد :

protected void Application_Start(object sender, EventArgs e)
{
 IConfigurationSource configSource = ConfigurationManager.GetSection("activerecord")
 as IConfigurationSource;
 ActiveRecordStarter.Initialize(typeof(User).Assembly, configSource);
}

برای درک بهتر مطلب می توانید پروژه مثال این مطلب را از اینجا دریافت کنید.

Advertisements

یادگیری Castle ActiveRecord با مثال – قسمت هفتم

قسمت اول | قسمت دوم | قسمت سوم | قسمت چهارم | قسمت پنجم | قسمت ششم

نکته : اگر از گوگل ریدر یا هر فیدخوان دیگری استفاده می کنید و کدها را به درستی مشاهده نمی کنید، لطفاً مطلب اصلی را در وبلاگ بخوانید.

یکی از مزیت های مهم استفاده از NHibernate که بسیاری از شرکت ها و توسعه دهندگان فقط به همین دلیل از آن در پروژه ها استفاده می کنند، پشتیبانی از پایگاه داده های محبوبی مثل Oracle, SQL Server, MySQL, Firebird و SQLite است. در این قسمت یاد خواهیم گرفت که چطور Castle ActiveRecord را برای کار با پایگاه داده های مختلف تنظیم کنیم.

اگر خاطرتان باشد در برنامه ویندوزی مثالی که نوشتیم تنظیمات CAR را در فایل App.config ذخیره کردیم (در برنامه های وب در فایل Web.config). این فایل را باز کنید و برای دیتابیس های مختلف به صورت زیر تغییر دهید :

SQL Server

<activerecord>
 <config>
 <add key="connection.driver_class"
 value="NHibernate.Driver.SqlClientDriver"/>
 <add key="dialect"
 value="NHibernate.Dialect.MsSql2008Dialect"/>
 <add key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider"/>
 <add key="connection.connection_string"
 value="Data Source=.\SQLEXPRESS;Initial Catalog=MyBookStore;Integrated Security=True;Pooling=False"/>
 <add key="proxyfactory.factory_class"
 value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
 </config>
</activerecord>

Oracle

<activerecord>
 <config>
 <add key="connection.driver_class"
 value="NHibernate.Driver.OracleClientDriver" />
 <add key="dialect"
 value="NHibernate.Dialect.Oracle10gDialect" />
 <add key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider" />
 <add key="connection.connection_string"
 value="Data Source=./Oracle;User ID=MyBookStore;Password=MyBookStore;Unicode=true" />
 </config>
</activerecord>

MySQL

<activerecord>
 <config>
 <add
 key="connection.driver_class"
 value="NHibernate.Driver.MySqlDataDriver" />
 <add
 key="dialect"
 value="NHibernate.Dialect.MySQLDialect" />
 <add
 key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider" />
 <add
 key="connection.connection_string"
 value="Database=MyBookStore;Data Source=localhost;User Id=root;Password=" />
 </config>
</activerecord>

Firebird

<activerecord>
 <config>
 <add
 key="connection.driver_class"
 value="NHibernate.Driver.FirebirdDriver" />
 <add
 key="dialect"
 value="NHibernate.Dialect.FirebirdDialect" />
 <add
 key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider" />
 <add
 key="connection.connection_string"
 value="Server=localhost;Database=MyBookStore.fdb;User=;password=;ServerType=1;Pooling=false" />
 <add
 key="query.substitutions"
 value="true 1, false 0" />
 </config>
</activerecord>

PostgreSQL

<activerecord>
 <config>
 <add
 key="connection.driver_class"
 value="NHibernate.Driver.NpgsqlDriver" />
 <add
 key="dialect"
 value="NHibernate.Dialect.PostgreSQLDialect" />
 <add
 key="connection.provider"
 value="NHibernate.Connection.DriverConnectionProvider" />
 <add
 key="connection.connection_string"
 value="Server=localhost;initial catalog=MyBookStore;User ID=;Password=;" />
 </config>
</activerecord>

SQLite

<activerecord>
 <config>
 <add key="connection.driver_class"
 value="NHibernate.Driver.SQLite20Driver"/>
 <add key="dialect"
 value="NHibernate.Dialect.SQLiteDialect"/>
 <add key="connection.provider"
 value="MyBookStore.Model.SQLite.SQLiteConnectionProvider, MyBookStore.Model"/>
 <add key="connection.connection_string"
 value="Data Source=Data.db"/>
 <add key="proxyfactory.factory_class"
 value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
 </config>
 </activerecord>

اگر به کدهای بالا توجه کنید متوجه خواهید شد که Connection Provider مربوط به تنظیمات SQLite با بقیه فرق دارد. این تفاوت به این علت است که NHibernate در نسخه 2 از System.Data.SQLite به صورت توکار پشتیبانی نمی کند. برای اینکه این پشتیبانی را خودمان اضافه کنیم یک کلاس جدید به پروژه Model برنامه مثال اضافه می کنیم که کدهای آن را در زیر مشاهده می کنید :

public class SQLiteConnectionProvider : NHibernate.Connection.DriverConnectionProvider
 {
 private static System.Data.IDbConnection m_Connection;

 public override System.Data.IDbConnection GetConnection()
 {
 if (m_Connection == null)
 m_Connection = base.GetConnection();
 return m_Connection;
 }

 public override void CloseConnection(System.Data.IDbConnection conn)
 {
 //Do nothing
 }
 }

همانطور که مشاهده می کنید انتخاب دیتابیس برای ذخیره داده ها هنگامی که از NHibernate استفاده می کنید کار آسانی است. پروژه مثال این مطالب با SQL Server، Oracle و SQLite با موفقیت آزمایش شده است. مستقل شدن از دیتابیس برای ذخیره داده های یک برنامه، ویژگی بسیار کارآمدی است که با استفاده از Castle ActiveRecord آسان و قابل دسترس است.

این مطلب آخرین قسمت سری آموزشی Castle ActiveRecord به همراه مثال بود اما باز هم در مورد آن در این وبلاگ مطلب خواهیم داشت. اگر خواننده این سری مطالب بودید، لطفاً نظر کلی خودتان را در قسمت نظرات همین پست با من در میان بگذارید.

پ.ن : برای دانلود پروژه مثال این سری مطالب باید چند روز صبر کنید تا کامل شود.

یادگیری Castle ActiveRecord با مثال – قسمت ششم

قسمت اول | قسمت دوم | قسمت سوم | قسمت چهارم | قسمت پنجم

نکته : اگر از گوگل ریدر یا هر فیدخوان دیگری استفاده می کنید و کدها را به درستی مشاهده نمی کنید، لطفاً مطلب اصلی را در وبلاگ بخوانید.

مشکل Select N+1 در ORMها

استفاده از یک ORM می تواند توسعه یک پروژه را بسیار سریع و آسان کند اما آگاهی نداشتن از برخی مسائل در مورد ORMها ممکن است باعث پایین آمدن شدید کارایی اپلیکیشن شما شود. یکی از مهم ترین و جدی ترین مشکلاتی که می تواند به شدت کارایی اپلیکیشن شما را پایین بیاورد، مشکل Select N+1 است. برای درک این مشکل بهتر است نمونه ای در مورد پروژه مثال کتاب فروشی ذکر شود. فرض کنید از Lazy Loading در سطح کلاس های این پروژه استفاده نمی کردیم، به نظر شما با اجرای یک خط کد زیر چه اتفاقی می افتد؟

Book[] books = Book.FindAll();

شاید منظورمان از نوشتن کد بالا این بوده که تمام کتاب ها را از دیتابیس بخوانیم، اما با این کار در واقع کل جدول های دیتابیس مان را خواهیم خواند! اجرای این خط باعث می شود تا برای هر کتاب، اطلاعات موضوعات، نویسنده، انتشارات، سفارشات و مشتریان هم از دیتابیس خوانده شوند. به این مشکل در ORMها، Select N+1 گفته می شود که خوشبختانه در Caslte ActiveRecord با تعریف رابطه ها به صورت Lazy قابل حل است.
در پروژه مثالی که مورد بررسی قرار گرفت تمام کلاس ها و تمام رابطه ها به صورت Lazy تعریف شده اند. برای تعریف یک کلاس به صورت Lazy کافیست تا صفت [ActiveRecord(Lazy=true)] را به آن اضافه کنید. با این کار شما باید تمام پراپرتی های کلاس را به صورت virtual تعریف کنید تا NHibernate بتواند در زمان اجرا، متد ها و پراپرتی های کلاس شما را override کند و فقط داده هایی را برای شما بارگذاری کند که واقعاً به آن ها نیاز دارید. فقط به این نکته توجه داشته باشید که Lazy Loading فقط زمانی ممکن است که یک Session در حافظه وجود داشته باشد، در غیر این صورت با یک استثنای NHibernate Lazy Initialization failure مواجه خواهید شد. برای اینکه با این استثنا مواجه نشوید می توانید در صورت نیاز از SessionScope در CAR استفاده کنید.

using (new SessionScope())
 {
 Category category = Category.Find(SelectedCategoryId);
 Books = Book.GetByCategory(category);
 }

Lazy Loading در سطح رابطه ها

فعال کردن Lazy Loading برای رابطه های یک موجودیت روی کارایی برنامه تاثیر بسیار زیادی دارد. Lazy مربوط به تمام رابطه ها به صورت پیش فرض برابر false است. برای رابطه های HasMany و HasAndBelongsToMany باید Lazy را برابر true و برای رابطه های BelongsTo صفت Lazy را برابر FetchWhen.OnInvoke قرار دهید. در این مورد نیز شما باید تمام پراپرتی ها و رابطه ها را به صورت virtual تعریف کنید. زمانی که یک رابطه مجموعه ای را به صورت Lazy تعریف می کنید، NHibernate تنها زمانی آیتم های آن را لود خواهد کرد که برنامه شما به یکی از اعضای آن دسترسی پیدا کند. در مورد رابطه های Lazy نیز حتماً باید یک SessionScope وجود داشته باشد تا NHibernate بتواند داده ها را فقط هر موقع نیاز بود برای شما لود کند.

لاگ کردن کدهای SQL تولید شده توسط NHibernate

برای اینکه بفهمیم Castle ActiveRecord چطور با دیتابیس حرف می زند و چه کارهایی در پشت پرده انجام می دهد تا ما از وجود دیتابیس بی خبر باشیم، می توانیم با استفاده از کامپوننت Log4net کوئری های SQL که توسط NHibernate ایجاد می شوند را به صورت فایل های log ذخیره و در صورت نیاز مشاهده کنیم. برای این کار شما باید به اسمبلی log4net.dll (که معمولاً همراه CAR هم وجود دارد) در پروژه خود ارجاع دهید. در مورد پروژه مثال ما که تحت ویندوز است، باید فایل App.config را به صورت زیر تغییر دهیم (در صورتی که از دات نت فریم ورک 4.0 استفاده می کنید) :

<?xml version="1.0"?>
<configuration>
 <configSections>
 <section name="activerecord"
 type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler,
 Castle.ActiveRecord"/>
 <section name="log4net"
 type="log4net.Config.Log4NetConfigurationSectionHandler,
 log4net" />
 </configSections>
 <startup useLegacyV2RuntimeActivationPolicy="true">
 <supportedRuntime version="v4.0" />
 </startup>
 <activerecord>
 <config>
 <add key="connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
 <add key="dialect" value="NHibernate.Dialect.MsSql2008Dialect"/>
 <add key="connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
 <add key="connection.connection_string" value="Data Source=.\SQLEXPRESS;Initial Catalog=MyBookStore;Integrated Security=True;Pooling=False"/>
 <add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
 </config>-->
 </activerecord>
 <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>

 <log4net>
 <appender name="trace" type="log4net.Appender.TraceAppender, log4net">
 <layout type="log4net.Layout.PatternLayout,log4net">
 <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%P{user}&amp;gt; - %m%n" />
 </layout>
 </appender>
 <appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
 <layout type="log4net.Layout.PatternLayout,log4net">
 <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%P{user}&amp;gt; - %m%n" />
 </layout>
 </appender>
 <appender name="rollingFile" type="log4net.Appender.RollingFileAppender,log4net" >
 <param name="File" value="log.txt" />
 <param name="AppendToFile" value="true" />
 <param name="RollingStyle" value="Date" />
 <param name="DatePattern" value="yyyy.MM.dd" />
 <param name="StaticLogFileName" value="true" />
 <layout type="log4net.Layout.PatternLayout,log4net">
 <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%X{auth}&amp;gt; - %m%n" />
 </layout>
 </appender>
 <root>
 <priority value="ALL" />
 <appender-ref ref="rollingFile" />
 </root>
 </log4net>
</configuration>

هنگامی که show_sql را در تنظیمات CAR تغییر می دهیم، در واقع به NHibernate می فهمانیم که قصد مشاهده کدهای SQL تولیدی اش را داریم. برای اینکه log4net را برای انجام وظیفه اش آماده کنیم باید یک خط کد به فایل Program.cs پروژه خود اضافه کنیم. این خط کد را قبل از اجرای فرم اصلی برنامه در فایل Program.cs اضافه کنید :

log4net.Config.XmlConfigurator.Configure();

با انجام این تغییرات از این به بعد با هر بار اجرای برنامه یک فایل log.txt در کنار فایل اجرایی پروژه ایجاد خواهد شد که در آن تمام مسائل پشت پرده CAR و NHibernate ثبت خواهد شد. این فایل برای عیب یابی و مشاهده کوئری های اجرا شده توسط NHibernate بر روی دیتابیس کاربرد خواهد داشت. در واقع این فایل تمام اطلاعاتی را که برای Debug کردن یک پروژه مبتنی بر NHibernate نیاز است را در خود ذخیره می کند.

ایجاد فایل های نگاشت NHibernate از روی کلاس های CAR

هر چند Castle ActiveRecord شما را از فایل های کانفیگ XML معمول NHibernate بی نیاز می کند، اما اگر می خواهید بدانید که CAR چطور عملیات Mapping را برای شما انجام می دهد، می توانید از آن بخواهید تا فایل های Mapping را برای شما تولید کند. برای این کار باید حالت isDebug مربوط به CAR را در فایل کانفیگ خود برابر true قرار دهید تا CAR مجبور شود فایل های نگاشت NHibernate را در کنار پروژه شما تولید کند. در این صورت باید به قسمت تنظمیات Castle ActiveRecord در فایل App.config پروژه مثال خود صفت isDebug=»true» را اضافه کنید. پس از اجرای برنامه خواهید دید که فایل های Mapping توسط NHibernate در کنار پروژه شما به ازای هر کلاس ایجاد خواهد شد. این فایل ها برای مشاهده جزئیات نگاشت، پیدا کردن و رفع یک مشکل و یا حتی گزارش یک باگ به تیم NHibernate کاربرد دارد.

farasun.wordpress.com

این مطلب ادامه دارد…

برای اینکه مطالب بعدی را از دست ندهید، مشترک فید فراسان شوید!

یادگیری Castle ActiveRecord با مثال – قسمت پنجم

قسمت اول | قسمت دوم | قسمت سوم | قسمت چهارم

نکته : اگر از گوگل ریدر یا هر فیدخوان دیگری استفاده می کنید و کدها را به درستی مشاهده نمی کنید، لطفاً مطلب اصلی را در وبلاگ بخوانید.

اعتبار سنجی موجودیت ها در Castle ActiveRecord

یک ORM خوب بدون داشتن ویژگی Validation یا اعتبار سنجی کامل نیست. در Castle ActiveRecord با استفاده از کلاس ActiveRecordValidationBase می توان خصوصیات موجودیت ها را قبل از رسیدن به دیتابیس اعتبار سنجی کرد. برای این کار باید کلاس مورد نظر را از کلاس ActiveRecordValidationBase به جای کلاس ActiveRecordBase به ارث برد. با استفاده از امکانات موجود در فضای نام Castle.Components.Validator می توانید قوانین اعتبار سنجی مورد نظر خود را برای پراپرتی های کلاس خود تعریف کنید تا در صورت اشتباه بودن مقادیر وارد شده برای آن ها، آبجکت از ذخیره شدن اطلاعات در دیتابیس جلوگیری کند.   به مثال زیر توجه کنید :

[ActiveRecord(Table="Customers", Lazy=true)]
 public class Customer: ActiveRecordValidationBase<Customer>
 {
 [PrimaryKey(PrimaryKeyType.Native)]
 public virtual long ID { get; set; }

 [ValidateNonEmpty]
 [Property(NotNull=true, Length=250)]
 public virtual string Title { get; set; }

 [Property(Length=50)]
 public virtual string ContactNumber { get; set; }

 [ValidateEmail("Not like an email")]
 [Property(Length=250)]
 public virtual string Email { get; set; }

 [ValidateLength(10,400)]
 [Property(Length=400)]
 public virtual string Address { get; set; }
}

این همان کلاس Customer پروژه مثال MyBookStore است که این بار از کلاس ActiveRecordValidationBase به ارث برده شده است که دارای یک متد مهم به نام IsValid است. در فضای نام Castle.Components.Validator صفت های اعتبارسنجی معمول برای استفاده در سنارویوهای معمول وجود دارد که می توانید از آن ها برای انجام اعمال اعتبارسنجی در سطح موجودیت ها استفاده کنید. در اینجا از صفت ValidateNonEmpty برای اطمینان از خالی یا null نبودن مقدار پراپرتی Title، از صفت ValidateEmail برای اطمینان از صحت فرمت ایمیل وارد شده توسط کاربر و از ValidateLenght برای اعتبارسنجی طول رشته وارد شده کاربر برای پراپرتی Address استفاده شده است.

از Validatorهای مهم دیگر این کتابخانه می توان به ValidateIsUnique که چک می کند مقدار خصوصیت در دیتابیس وجود نداشته باشد، ValidateRegExp که چک می کند مقدار خصوصیت با یک Regular Expression هماهنگ باشد و ValidateIsGreater و ValidateIsLesser که مقدار خصوصیت را با مقدار خصوصیت دیگری از نظر بزرگتر یا کوچکتری مقایسه می کند، اشاره نمود.

اگر کاربر بخواهد مقدار نامعتبری را به هر یک از خصوصیت های این کلاس نبست دهد، یک استثنا رخ خواهد داد. برای جلوگیری از این موضوع شما باید اول مطمئن شوید که مقادیر وارد شده برای خصوصیات یک موجودیت با توجه به Validatorهای تعریف شده برای هر کدام، معتبر باشند. متد IsValid چک می کند که تمام اعتبار سنجی ها با موفقیت انجام شده اند و سپس True برمی گرداند.
ممکن است برخی نیازهای اعتبارسنجی شما برای تعریف به صورت Attribute یک خصوصیت مناسب نباشد. اگر اینطور بود می توانید متد IsValid را Override کنید و متد base را زمانی صدا بزنید که مطمئن شدید اعتبار سنجی شما با موفقیت انجام شده است. برای مثال می خواهیم مطمئن شویم که برای خصوصیت Title این کلاس حتماً بیشتر از 6 کاراکتر وارد شود :

public override bool IsValid()
 {
 if (Title.Length > 6)
 return base.IsValid();
 else
 return false;
 }

اگر هر کدام از مقادیر وارد شده برای پراپرتی های این کلاس با توجه به Validation تعریف شده برای آن ها، معتبر نباشد، یک استثنای ValidationException رخ خواهد داد. برای تغییر این رفتار می توانید متد NotIsValid این کلاس را بر اساس نیاز خود Override کنید.

همانطور که مشاهده کردید، استفاده از Validationها و تعریف اعتبارسنجی های سفارشی در سطح موجودیت ها در Castle ActiveRecord بسیار آسان در عین حال قدرتمند است. شما بر اساس نیاز پروژه خود می توانید پیچیده ترین اعتبار سنجی ها را با استفاده از امکانات این کتابخانه در سطح موجودیت های خود پیاده سازی کنید. این یکی از دلایل مهمی است که Castle ActiveRecord را در نظر من و بسیاری دیگر محبوب ساخته است.

farasun.wordpress.com

این مطلب ادامه دارد…

برای اینکه مطالب بعدی را از دست ندهید، مشترک فید فراسان شوید!

یادگیری Castle ActiveRecord با مثال – قسمت چهارم

قسمت اول | قسمت دوم | قسمت سوم

نکته : اگر از گوگل ریدر یا هر فیدخوان دیگری استفاده می کنید و کدها را به درستی مشاهده نمی کنید، لطفاً مطلب اصلی را در وبلاگ بخوانید.

از آنجا که در حال حاضر انجام آزمایش های واحد یا Unit Testing نقش مهمی را در توسعه یک نرم افزار ایفا می کند، در این قسمت می خواهیم نحوه نوشتن آزمایش واحد برای لایه دسترسی به داده هایی که با Castle ActiveRecord توسعه داده شده است را مورد بررسی قرار دهیم. برای این کار از فریم ورک اوپن سورس NUnit استفاده خواهیم کرد. قبل از هر کاری کتابخانه NUnit را از اینجا دریافت کنید. خواهید دید که انجام تست های واحد بر روی کلاس های Castle ActiveRecord به راحتی امکان پذیر است.

برای اینکه مطمئن شویم مدل برنامه مثال ما درست کار می کند، از تست واحد استفاده می کنیم. شاید بهتر بود قبل از نوشتن پست های قبلی، ابتدا تست واحد را می نوشتم اما به نظرم اینطوری توضیح دادن کدها مشکل تر بود. دست بکار شوید و یک پروژه جدید به نام MyBookStore.Test به Solution خود اضافه کنید و به فایل های Castle.ActiveRecord ، NHibernate.dll ، NUnit.Framework.dll و MyBookStore.Model.dll ارجاع دهید.

اگر خاطرتان باشد اولین کاری که پس از مشخص شدن مدل برنامه کردیم، راه اندازی اولیه CAR در هنگام اجرای برنامه بود. کلاس ActiveRecordStarter برای این مورد کاربرد دارد. در برنامه اصلی تنظیمات ActiveRecord را از فایل App.config خواندیم اما در اینجا می خواهیم از یک فایل جداگانه تنظمیات را بخوانیم چون قرار است تست واحد با استفاده از NUnit Test Runner اجرا شود. یک کلاس جدید به نام TestCase به این پروژه اضافه کنید و کدهای زیر را در آن بنویسید :

using System;
using System.Collections.Generic;
using NUnit.Framework;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;
using MyBookStore.Model;

namespace MyBookStore.Test
{
[TestFixture]
 public class TestCase
 {

 [TestFixtureSetUp]
 public virtual void FixtureSetup()
 {
 IConfigurationSource configSource =
 new XmlConfigurationSource("ActiveRecordConfig.xml");
 ActiveRecordStarter.Initialize(typeof(Book).Assembly,configSource);
 }

 [SetUp]
 public virtual void OneTimeSetup()
 {
 ActiveRecordStarter.DropSchema();
 ActiveRecordStarter.CreateSchema();
 }

 [Test]
 public void InsertMyBook()
 {
 using (new SessionScope())
 {
 Book book = new Book()
 {
 Title = "Learn Castle ActiveRecord",
 BuyPrice = 1000,
 SalePrice = 2000
 };
 Category category = new Category() { Title = "ORM" };
 category.Create();
 Author author = new Author() { Title = "Iman Nemati" };
 author.Create();
 Publisher publisher = new Publisher() { Title = "Farasun" };
 publisher.Create();
 book.Categories = new List<Category>();
 book.Categories.Add(category);
 book.Author = author;
 book.Publisher = publisher;
 book.Create();
 Book[] books = Book.FindAll();
 Assert.AreEqual(1, books.Length);
 Assert.AreEqual("Iman Nemati", books[0].Author.Title);
 }
 }
 }
}
}

پروژه را کامپایل کنید تا اسمبلی MyBookStore.Test ساخته شود. حالا یک فایل XML به نام ActiveRecordConfig.xml با محتویات زیر به پروژه خود اضافه کنید :

<?xml version="1.0" encoding="utf-8" ?>
<activerecord>
<config>
<add key="connection.driver_class"
value="NHibernate.Driver.SqlClientDriver"/>
<add key="dialect"
value="NHibernate.Dialect.MsSql2008Dialect"/>
<add key="connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="connection.connection_string"
value="Data Source=.\SQLEXPRESS;Initial Catalog=MyBookStoreTest;Integrated Security=True;Pooling=False"/>
<add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
</config>
</activerecord>

بر روی این فایل کلیک راست کنید و گزینه Properties را کلیک کنید و خاصیت  Copy to Output Directory را برابر Copy always قرار دهید. برای انجام تست واحد نیاز به یک دیتابیس جدا از دیتابیس اصلی داریم. برای این کار در Server Explorer دیتابیس جدیدی به نام MyBookStoreTest بسازید و اگر نیاز است تغییراتی در فایل Config بالا اعمال کنید تا مشکلی پیش نیاید. بعد از اتمام این کار، NUnit Test Runner را اجرا کنید. این فایل معمولاً در مسیر کتابخانه ای که دانلود کرده اید قرار دارد. پس از اجرای این برنامه، از منوی File گزینه Open Project را برگزینید و فایل اسمبلی MyBookStore.Test.dll در مسیر bin\Debug پروژه خود را انتخاب کنید. پس از این کار باید متدهای تست برنامه خود را مشاهده کنید. در اینجا ما فقط یک تست واحد نوشتیم که یک موجودیت Book را با خاصیت ها و روابط مشخص شده در دیتابیس ایجاد می کند و سپس چک می کند که آیا این موجودیت با موفقیت به دیتابیس اضافه شده است یا نه و بعد از آن نام نویسنده را با رشته مورد انتظار ما مقایسه می کند تا از صحت اطلاعات درج شده اطمینان پیدا کنیم. اگر تمام کارها را به درستی انجام داده باشید با کلیک روی دکمه Run، تست واحد شما با موفقیت Pass خواهد شد.

این تست چطور کار می کند؟

اگر با انجام تست واحد آشنایی دارید می توانید این بخش را نخوانید. کلاس TestCase با خاصیت TestFixture همراه شده که این مشخص می کند این کلاس دارای متدهایی برای تست است. متد FixtureSetup همانطور که از نامش مشخص است و با توجه به خاصیت TestFixtureSetup که برای آن تعریف شده، وظیفه مقداردهی های اولیه برای متدهای تست کلاس را بر عهده دارد و فقط یکبار در طول تست اجرا می شود. متد OneTimeSetup نیز که با خاصیت SetUp مشخص شده فقط یکبار برای تمام تست ها اجرا خواهد شد. متد InsertMyBook که با خاصیت Test مشخص شده، متدی است که Fail یا Pass شدن آن برای ما مهم است. در این متد با استفاده از Assert می توان شرط های مختلفی را آزمایش کرد تا از صحت کارکرد متد مطمئن شد. شما می توانید غیر از این متد، متدهای دیگری نیز با مشخصه Test به این کلاس اضافه کنید و تست های واحد خود را برای متدهای مختلف بنویسید.

farasun.wordpress.com

این مطلب ادامه دارد…

برای اینکه مطالب بعدی را از دست ندهید، مشترک فید فراسان شوید!

یادگیری Castle ActiveRecord با مثال – قسمت سوم

قسمت اول | قسمت دوم

در ادامه می خواهیم اعمال CRUD را برای هر یک از کلاس های برنامه پیاده سازی کنیم. اگر از هر یک از کلاس های دامین برنامه یک نمونه بسازید متوجه خواهید شد که به علت مشتق شدن تمامی این کلاس ها از کلاس ActiveRecordBase تمامی اعمال مورد نظر ما توسط CAR پیاده سازی شده اند! یعنی شما نیازی نیست برای انجام اعمال CRUD متدهایی جدیدی به کلاس های دامین برنامه اضافه کنید. در واقع Castle ActiveRecord یک لایه دسترسی به داده های کامل برای شما مهیا می کند. کافیست شما قوانین تجاری برنامه خود را با override کردن متدهای کلاس ActiveRecordBase اعمال کنید.

برای درک بهتر فرض کنید می خواهیم یک کتاب جدید ایجاد و به دیتابیس اضافه کنیم. کد زیر را برای این کار می نویسیم :

using (new SessionScope())
 {
 Book book = new Book()
 {
 BuyPrice = 1000,
 SalePrice = 1200,
 Title = "Castle ActiveRecord"
 };
 book.Categories = new List<Category>();
 Category category = new Category() { Title = "ORM" };
 category.Save();
 book.Categories.Add(category);
 Author author = new Author() { Title = "Iman Nemati" };
 author.Save();
 book.Author = author;
 Publisher publisher = new Publisher() { Title = "Farasun" };
 publisher.Save();
 book.Publisher = publisher;
 book.Save();
 }

خوشبختانه این کد آنقدر واضح است که نیازی به توضیح اضافه ندارد. یکی از مزایای مهم استفاده از یک ORM همین واضح بودن کدهاست. متد Save ابتدا شیء را برای داشتن یک ID معتبر بررسی می کند و سپس بر اساس آن عمل Insert یا Update را انجام خواهد داد. در اینجا از SessionScope برای Insert کردن آبجکت های مختلف استفاده کردیم. با این کار Castle ActiveRecord یک ISession را برای ذخیره کردن تمام آبجکت های ما در دبتابیس بکار می گیرد. برای ذخیره کردن یک آبجکت به تنهایی نیاز به استفاده از SessionScope ندارید.

حالا فرض کنید می خواهیم شیء کتابی که در بالا به دیتابیس اضافه کردیم را به روز رسانی کنیم. مثلاً عنوان کتاب را عوض کنیم. برای این کار ابتدا باید اطلاعات کتاب مورد نظرمان را در یک آبجکت Load کنیم، اطلاعات آن را تغییر دهیم و برای ذخیره به دیتابیس بفرستیم. کلاس ActiveRecordBase متدهایی مناسبی را برای خواندن و به روز رسانی اطلاعات در اختیارمان قرار می دهد. با متد Find می توانیم اطلاعات یک آبجکت را بوسیله ID یکتای آن از دیتابیس بخوانیم و پس از اعمال تغییرات با استفاده از متد Save تغییرات را به دیتابیس بفرستیم.

Book book = Book.Find(SelectedBookId);
 book.Title = "Learn Castle ActiveRecord with Example";
 book.Save();

همانطور که می بینید انجام اعمال CRUD با استفاده از CAR به سادگی انجام پذیر هستند. به همین ترتیب برای Delete و Update نیز متدهای مناسب وجود دارد. فقط باید توجه داشته باشید که برای انجام Delete و Update آبجکت شما باید دارای یک ID معتبر باشد، اما برای Insert احتیاجی به ID معتبر نیست.

بازیابی رکوردها (Retrive)

همانطور که در اولین قسمت این سری آموزشی گفته شد، در الگوی ActiveRecord متدهای استاتیک بر روی کل رکوردهای دیتابیس و متدهای داینامیک (آن هایی که حتماً باید اول نمونه سازی شوند و سپس مورد استفاده قرار گیرند) بر روی یک رکورد خاص عمل می کنند. کلاس ActiveRecordBase شامل یکسری متد استاتیک است که بیشتر به منظور یافتن رکوردها بر اساس یک شرط پیاده سازی شده اند. برای دستیابی به رکوردها در دیتابیس می توانید از متدهای استاتیک زیر کمک بگیرید :

  • Find : آبجکتی شامل اطلاعات رکوردی از دیتابیس با ID مشخص شده را برمی گرداند.
  • FindAll : آرایه ای از آبجکت ها شامل اطلاعات تمام رکوردهای موجود در دیتابیس برای این کلاس را بر می گرداند.
  • FindAllByProperty : آرایه ای از آبجکت ها شامل اطلاعات تمام رکوردهایی که با شرط Property = Value برابر باشند را برمی گرداند.
  • TryFind : مانند Find است با این تفاوت که اگر رکوردی که با ID پاس داده شده وجود نداشته باشد هیچ خطایی رخ نخواهد داد.

بحثی در مورد بقیه متدهای این کلاس نخواهیم کرد چون می خواهیم برای دستیابی به رکوردها از کوئری های LINQ استفاده کنیم. برای این کار در پروژه Model برنامه مثال به فایل Castle.ActiveRecord.Linq ارجاع دهید. با استفاده از این کتابخانه می توانید از قابلیت های معمولی Linq برای اجرای کوئری روی کلاس های مدل خود برخوردار شوید. متدهای زیر را به کلاس Book خود اضافه کنید : (یادتان نرود اول فضای نام Castle.ActiveRecord.Linq را using کنید)

public static Book GetByID(long id)
 {
 return (from x in ActiveRecordLinq.AsQueryable<Book>()
 where x.ID == id
 select x).First();
 }

 public static List<Book> GetByAuthor(long authorID)
 {
 return (from x in ActiveRecordLinq.AsQueryable<Book>()
 where x.Author.ID == authorID
 select x).ToList();
 }

 public static List<Book> SearchWithTitle(string title)
 {
 return (from x in ActiveRecordLinq.AsQueryable<Book>()
 where x.Title.Contains(title)
 select x).ToList();
 }

 public static List<Book> GetBooksByPriceBetween
(decimal lowPrice, decimal highPrice)
 {
 return (from x in ActiveRecordLinq.AsQueryable<Book>()
 where x.SalePrice >= lowPrice &&
 x.SalePrice <= highPrice
 select x).ToList();
 }

همانطور که مشاهده می کنید با استفاده از سینتاکس آشنای Linq هر نتیجه ای که بخواهید می توانید از اطلاعات موجود در دیتابیس بدست آورید. حالا باید خودتان بتوانید متدهای مورد نیاز برای دسترسی به رکوردها را برای بقیه کلاس های پروژه بنویسید. پس دست به کار شوید!

farasun.wordpress.com

این مطلب ادامه دارد…

برای اینکه مطالب بعدی را از دست ندهید، مشترک فید فراسان شوید!

یادگیری Castle ActiveRecord با مثال – قسمت دوم

قسمت اول

در ادامه می خواهیم امکان فروش کتاب ها را به برنامه خود اضافه کنیم. برای این کار باید یک کلاس برای نگهداری اطلاعات فروش کتاب ها و یک کلاس برای نگهداری اطلاعات فروش مربوط به مشتریان بسازیم. برای درک بهتر می توان گفت کلاس Order اطلاعات صورت حساب مشتری را نگهداری می کند که در این صورت حساب ممکن است یک یا چند OrderItem وجود داشته باشد که هر کدام از این OrderItemها مشخص کننده تعداد یک کتاب مشخص سفارش داده شده توسط مشتری است. کلاس های زیر را به پروژه خود اضافه کنید:

کلاس Order برای نگهداری صورت حساب مشتری (Order.cs) :

[ActiveRecord(Table="Orders", Lazy=true)]
 public class Order: ActiveRecordBase<Order>
 {
 [PrimaryKey(Generator=PrimaryKeyType.Native)]
 public virtual long ID { get; set; }

 [Property(NotNull=true)]
 public virtual DateTime OrderDate { get; set; }

 [Property(NotNull=true)]
 public virtual decimal TotalPrice { get; set; }

[HasMany(typeof(OrderItem), Inverse=true, Lazy=true, Cascade=ManyRelationCascadeEnum.AllDeleteOrphan)]
 public virtual IList<OrderItem> Items { get; set; }

 [BelongsTo(Column="CustomerID", NotNull=true)]
 public virtual Customer Customer { get; set; }
 }

کلاس OrderItem برای نگهداری اطلاعات فروش کتاب ها (OrderItem.cs) :

[ActiveRecord(Table="OrderItems", Lazy=true)]
 public class OrderItem: ActiveRecordBase<OrderItem>
 {
 [PrimaryKey(PrimaryKeyType.Native)]
 public virtual long ID { get; set; }

 [BelongsTo(Column="BookID", NotNull=true)]
 public virtual Book Book { get; set; }

 [Property(NotNull=true)]
 public virtual int Count { get; set; }

 [BelongsTo(Column="OrderID")]
 public virtual Order Order { get; set; }
 }

همانطور که مشاهده می کنید هر Order متعلق به یک Customer است که با صفت BelongsTo در تعریف کلاس مشخص شده و دارای یک یا چند OrderItem است که با صفت HasMany تعریف شده است. چند نکته به نظر می رسد که باید بیشتر در موردشان توضیح داده شود :

  • مشخص کردن کلاس به عنوان Lazy از این جهت است که NHibernate بتواند مجموعه ای از اشیای از نوع این کلاس را به صورت Lazy بار گذاری کند. یعنی هر زمانی که نیاز به اطلاعات این کلاس ها بود، NHibernate اطلاعات را از دیتابیس بخواند.
  • NHibernate برای انجام Lazy Loading در سطح یک کلاس احتیاج دارد که شما تمام پراپرتی ها را به صورت virtual تعریف کنید.
  • در رابطه های HasMany و HasAndBelongsToMany نیاز است تا نوع کلاس طرف دیگر رابطه را با آوردن typeof و نام کلاس مورد نظر مشخص کنید. در مورد رابطه های BelongsTo نیازی به مشخص کردن نوع طرف دیگر رابطه نداریم و نوع پراپرتی همان نوع طرف دیگر رابطه است.
  • در رابطه های مختلف می توانید حالت Cascade را نیز مشخص کنید که اگر مثلاً رکورد یک طرف رابطه حذف شد، رکوردهای وابسته به آن در طرف دیگر رابطه حذف شوند تا داده یتیم در سیستم وجود نداشته باشد.
  • مقدار دهی درست Inverse در رابطه ها بسیار مهم است.معمولاً  شما باید Inverse طرفی را true قرار دهید که با صفت HasMany مشخص شده است.
  • استفاده به جا از Lazy=true در صفت رابطه ها باعث بالا رفتن کارایی برنامه شما خواهد شد.

خب تا اینجا تمام کلاس های مورد نیازمان برای ساختن یک برنامه کتاب فروشی را نوشتیم. حالا می توانید یک کلاس دیاگرام به پروژه خود اضافه کنید (با Add New item و انتخاب گزینه ClassDiagram) و تمام کلاس های ایجاد شده را در آن Drag & Drop کنید تا رابطه های میان کلاس ها را به صورت ویژوال مشاهده کنید. تقریباً کارمان با این پروژه به پایان رسیده. بر روی پروژه خود کلیک راست کنید و گزینه Build را بزنید تا Class Library شما ساخته شود.

حالا بر روی Solution کلیک راست کنید و پروژه جدیدی از نوع Windows Application اضافه کنید. فایل های Castle.ActiveRecord.dll و NHibernate.ByteCode.Castle و البته فایل کتابخانه مدل برنامه خود که ساخته اید را به ارجاعت این پروژه اضافه کنید. سپس یک فایل App.config را با کلیک راست بر روی نام پروژه، برگزیدن Add New Item و انتخاب Application Configuration file به این پروژه اضافه کنید و محتویات زیر را به جای محتویات پیش فرض آن کپی کنید:

<?xml version="1.0"?>
<configuration>
 <configSections>
 <section name="activerecord" type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord"/>
 </configSections>

 <activerecord>

 <config>
 <add key="connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>

 <add key="dialect" value="NHibernate.Dialect.MsSql2008Dialect"/>
 <add key="connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
 <add key="connection.connection_string" value="Data Source=.\SQLEXPRESS;Initial Catalog=MyBookStore;Integrated Security=True;Pooling=False"/>

 <add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
 </config>

 </activerecord>

<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

سپس در پنجره Server Explorer یک دیتابیس جدید به نام MyBookStore بسازید. در اینجا می خواهیم جداول مورد نیاز مدل برنامه خودمان را در یک دیتابیس SQL Server بسازیم. برای این کار فایل Program.cs را باز کنید و به صورت زیر تغییر دهید:

using System;
using System.Windows.Forms;
using System.Configuration;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using MyBookStore.Model;

namespace MyBookStore
{
 static class Program
 {
 /// <summary>
 /// The main entry point for the application.
 /// </summary>
 [STAThread]
 static void Main()
 {
 Application.EnableVisualStyles();
 Application.SetCompatibleTextRenderingDefault(false);
 IConfigurationSource configSource = ConfigurationManager.GetConfig("activerecord") as IConfigurationSource;
 ActiveRecordStarter.Initialize(configSource, typeof(Book), typeof(Author), typeof(Publisher),
 typeof(Category), typeof(Customer), typeof(Order), typeof(OrderItem));
 ActiveRecordStarter.CreateSchema(); //اینجا دیتابیس از روی کلاس ها ساخته می شود
 Application.Run(new Form1());
 }
 }
}

اگر تمام کار ها را به درستی انجام داده باشید با زدن دکمه F5 دیتابیس شما به صورت زیر ساخته خواهد شد:

Server Explorerهمانطور که مشاهده می کنید به ازای هر یک از کلاس های برنامه ما یک Table در دیتابیس ساخته شده و برای ایجاد رابطه چند به چند میان موجودیت Category و Book نیز یک Table واسط به نام BookCategories ساخته شده است.

برای تنظیم اولیه Castle ActiveRecord چند راه وجود دارد که در اینجا من از IConfigurationSource استفاده کردم و تنظیمات را از فایل کانفیگ برنامه خواندم و به ActiveRecordStarter.Initialize فرستادم. در این متد باید نوع تمام کلاس هایی که قرار است Map شوند را برای CAR مشخص کنید. سپس با استفاده از متد CreateSchema از کلاس ActiveRecordStarter دیتابیس را بر روی SQL Server می سازم. CAR برای انجام این کار از تنظیماتی که در فایل App.config مشخص شده است، استفاده می کند. همانطور که می بینید در تنظیمات مشخص کرده ام که قرار است دیتابیس بر روی SQL Server 2008 با Connection String مشخص شده ساخته شود. البته نوشتن CreateSchema در متد Main برنامه کار درستی نیست. شما باید فقط زمانی دیتابیس را از ابتدا بسازید که دیتابیس برنامه از قبل وجود نداشته باشد. در واقع فقط هنگام نصب برنامه (یا اجرای اول برنامه) دیتابیس باید بوسیله این متد ساخته شود و در دفعات بعدی از آن برای ذخیره و بازیابی داده ها استفاده شود.

اگر بخواهیم Database Diagram یا همان ERD این پروژه را مشاهده کنیم، باید نموداری شبیه به نمودار زیر را داشته باشیم:

MyBookStore ERDدر تصویر بالا می توانید تمام ارتباط هایی که قبلا در کلاس ها تعریف کرده ایم را مشاهده کنید.

دریافت پروژه مثال تا اینجا (لطفاً پس از دریافت، پسوند فایل را به zip تغییر داده و سپس فایل را باز کنید!)

یک نکته : اگر از گوگل ریدر یا هر فیدخوان دیگری استفاده می کنید و کدها را به درستی مشاهده نمی کنید، لطفاً پست اصلی را در وبلاگ بخوانید.

farasun.wordpress.com

این مطلب ادامه دارد…

برای اینکه مطالب بعدی را از دست ندهید، مشترک فید فراسان شوید!