۱۳۸۹/۱۰/۰۸

ذخيره سازي SQL توليدي در NH3


همانطور كه در مطلب "NHibernate 3.0 و عدم وابستگي مستقيم به Log4Net" عنوان شد، از اينترفيس جديد IInternalLogger آن مي‌توان جهت ثبت وقايع داخلي NHibernate استفاده كرد. اگر در اين بين صرفا بخواهيم SQL هاي توليدي را لاگ كنيم، خلاصه‌ي آن به صورت زير خواهد بود:
public class LoggerFactory : ILoggerFactory
{
public IInternalLogger LoggerFor(System.Type type)
{
if (type == typeof(NHibernate.Tool.hbm2ddl.SchemaExport))
//log it
}

public IInternalLogger LoggerFor(string keyName)
{
if (keyName == "NHibernate.SQL")
//log it
}
}
يا كليد NHibernate.SQL بايد پردازش شود (جهت ثبت SQL هاي كوئري‌ها) يا نوع NHibernate.Tool.hbm2ddl.SchemaExport جهت ثبت SQL ساخت ساختار جداول بانك اطلاعاتي بايد بررسي گردد.
سورس كامل اين كتابخانه‌ي كوچك را از اينجا مي‌توانيد دريافت كنيد. جهت استفاده از آن تنها كافي است چند سطر زير به فايل app.config يا web.config برنامه‌ي شما اضافه شوند:

<appSettings>
<add key="nhibernate-logger" value="NH3SQLLogger.LoggerFactory, NH3SQLLogger" />
</appSettings>

كليد nhibernate-logger ، به صورت مستقيم توسط NHibernate بررسي مي‌شود و صرف نظر از اينكه از كداميك از مشتقات NHibernate استفاده مي‌كنيد، با تمام آن‌ها كار خواهد كرد.
لازم به ذكر است كه اگر برنامه‌ي شما از نوع ASP.NET است، اين كتابخانه اطلاعات را در پوشه‌ي استاندارد App_Data ثبت خواهد كرد؛ در غيراينصورت فايل‌ها در كنار فايل اجرايي برنامه تشكيل خواهند شد.

۱۳۸۹/۱۰/۰۶

مقدار دهي كليدهاي خارجي در NHibernate و Entity framework


ORM هاي NHibernate و Entity framework روش‌هاي متفاوتي را براي به روز رساني كليد خارجي با حداقل رفت و برگشت به ديتابيس ارائه مي‌دهند كه در ادامه معرفي خواهند شد.

صورت مساله:
فرض كنيد مي‌خواهيم برنامه‌اي را بنويسيم كه ريز پرداخت‌هاي روزانه‌ي ما را ثبت كند. براي اينكار حداقل به يك جدول گروه‌هاي اقلام خريداري شده، يك جدول حساب‌هاي تامين كننده‌ي مخارج، يك جدول فروشنده‌ها و نهايتا يك جدول صورتحساب‌هاي پرداختي بر اساس جداول ذكر شده نياز خواهد بود.

الف) بررسي مدل برنامه



در اينجا جهت تعريف ويژگي‌ها يا Attributes تعريف شده در اين كلاس‌ها از NHibernate validator استفاده شده (+). مزيت اينكار هم علاوه بر اعتبارسنجي سمت كلاينت (پيش از تبادل اطلاعات با بانك اطلاعاتي)، توليد جداولي با همين مشخصات است. براي مثال Fluent NHibernate بر اساس ويژگي Length تعريف شده با طول حداكثر 120 ، يك فيلد nvarchar با همين طول را ايجاد مي‌كند.

public class Account
{
public virtual int Id { get; set; }

[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام بايد بين 3 و 120 كاراكتر باشد")]
public virtual string Name { get; set; }
}

public class Category
{
public virtual int Id { get; set; }

[NotNullNotEmpty]
[Length(Min = 3, Max = 130, Message = "طول نام بايد بين 3 و 130 كاراكتر باشد")]
public virtual string Name { get; set; }
}

public class Payee
{
public virtual int Id { get; set; }

[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام بايد بين 3 و 120 كاراكتر باشد")]
public virtual string Name { get; set; }
}

public class Bill
{
public virtual int Id { get; set; }

[NotNull]
public virtual Account Account { get; set; }

[NotNull]
public virtual Category Category { get; set; }

[NotNull]
public virtual Payee Payee { get; set; }

[NotNull]
public virtual decimal Amount { set; get; }

[NotNull]
public virtual DateTime BillDate { set; get; }

[NotNullNotEmpty]
[Length(Min = 1, Max = 500, Message = "طول توضيحات بايد بين 1 و 500 كاراكتر باشد")]
public virtual string Description { get; set; }
}




ب) ساختار جداول متناظر (توليد شده به صورت خودكار توسط Fluent NHibernate در اينجا)


در مورد نحوه‌ي استفاده از ويژگي AutoMapping و همچنين توليد خودكار ساختار بانك اطلاعاتي از روي جداول در NHibernate قبلا توضيح داده شده است. البته بديهي است كه تركيب مقاله‌ي Validation و آشنايي با AutoMapping در اينجا جهت اعمال ويژگي‌ها بايد بكار گرفته شود كه در همان مقاله‌ي Validation مفصل توضيح داده شده است.
نكته‌ي مهم database schema توليدي، كليد‌هاي خارجي (foreign key) تعريف شده بر روي جدول Bills است (همان AccountId، CategoryId و PayeeId تعريف شده) كه به primary key جداول متناظر اشاره مي‌كند.
    create table Accounts (
AccountId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (AccountId)
)

create table Bills (
BillId INT IDENTITY NOT NULL,
Amount DECIMAL(19,5) not null,
BillDate DATETIME not null,
Description NVARCHAR(500) not null,
AccountId INT not null,
CategoryId INT not null,
PayeeId INT not null,
primary key (BillId)
)

create table Categories (
CategoryId INT IDENTITY NOT NULL,
Name NVARCHAR(130) not null,
primary key (CategoryId)
)

create table Payees (
PayeeId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (PayeeId)
)

alter table Bills
add constraint fk_Account_Bill
foreign key (AccountId)
references Accounts

alter table Bills
add constraint fk_Category_Bill
foreign key (CategoryId)
references Categories

alter table Bills
add constraint fk_Payee_Bill
foreign key (PayeeId)
references Payees

ج) صفحه‌ي ثبت صورتحساب‌ها

صفحات ثبت گروه‌هاي اقلام، حساب‌ها و فروشنده‌ها، نكته‌ي خاصي ندارند. چون اين جداول وابستگي خاصي به جايي نداشته و به سادگي اطلاعات آن‌ها را مي‌توان ثبت يا به روز كرد.
صفحه‌ي مشكل در اين مثال، همان صفحه‌ي ثبت صورتحساب‌ها است كه از سه كليد خارجي به سه جدول ديگر تشكيل شده است.
عموما براي طراحي اين نوع صفحات، كليدهاي خارجي را با drop down list نمايش مي‌دهند و اگر در جهت سهولت كار كاربر قدم برداشته شود، بايد از يك Auto complete drop down list استفاده كرد تا كاربر برنامه جهت يافتن آيتم‌هاي از پيش تعريف شده كمتر سختي بكشد.



اگر از Silverlight يا WPF استفاده شود، امكان بايند يك ليست كامل از اشياء با تمام خواص مرتبط به آن‌ها وجود دارد (هر ركورد نمايش داده شده در دراپ داون ليست، دقيقا معادل است با يك شيء متناظر با كلاس‌هاي تعريف شده است). اگر از ASP.NET استفاده شود (يعني يك محيط بدون حالت كه پس از نمايش يك صفحه ديگر خبري از ليست اشياء بايند شده وجود نخواهد داشت و همگي توسط وب سرور جهت صرفه جويي در منابع تخريب شده‌اند)، بهتر است datatextfield را با فيلد نام و datavaluefield را با فيلد Id مقدار دهي كرد تا كاربر نهايي، نام را جهت ثبت اطلاعات مشاهده كند و برنامه از Id موجود در ليست جهت ثبت كليدهاي خارجي استفاده نمايد.
و نكته‌ي اصلي هم همينجا است كه چگونه؟! چون ما زمانيكه با يك ORM سر و كار داريم، براي ثبت يك ركورد در جدول Bills بايد يك وهله از كلاس Bill را ايجاد كرده و خواص آن‌را مقدار دهي كنيم. اگر به تعريف كلاس Bill مراجعه كنيد، سه خاصيت آن از نوع سه كلاس مجزا تعريف شده است. به به عبارتي با داشتن فقط يك id از ركوردهاي اين كلاس‌ها بايد بتوان سه وهله‌ي متناظر آن‌ها را از بانك اطلاعاتي خواند و سپس به اين خواص انتساب داد:

var newBill = new Bill
{
Account = accountRepository.GetByKey(1),
Amount = 1,
BillDate = DateTime.Now,
Category = categoryRepository.GetByKey(1),
Description = "testestest...",
Payee = payeeRepository.GetByKey(1)
};
يعني براي ثبت يك ركورد در جدول Bills فوق، چهار بار رفت و برگشت به ديتابيس خواهيم داشت:
- يكبار براي دريافت ركورد متناظر با گروه‌ها بر اساس كليد اصلي آن (كه از دراپ داون ليست مربوطه دريافت مي‌شود)
- يكبار براي دريافت ركورد متناظر با فروشند‌ه‌ها بر اساس كليد اصلي آن (كه از دراپ داون ليست مربوطه دريافت مي‌شود)
- يكبار براي دريافت ركورد متناظر با حساب‌ها بر اساس كليد اصلي آن (كه از دراپ داون ليست مربوطه دريافت مي‌شود)
- يكبار هم ثبت نهايي اطلاعات در بانك اطلاعاتي

متد GetByKey فوق همان متد session.Get استاندارد NHibernate است (چون به primary key ها از طريق drop down list دسترسي داريم، به سادگي مي‌توان بر اساس متد Get استاندارد ذكر شده عمل كرد).

SQL نهايي توليدي هم به صورت واضحي اين مشكل را نمايش مي‌دهد (4 بار رفت و برگشت؛ سه بار select يكبار هم insert نهايي):
SELECT account0_.AccountId as AccountId0_0_, account0_.Name as Name0_0_
FROM Accounts account0_ WHERE account0_.AccountId=@p0;@p0 = 1 [Type: Int32 (0)]

SELECT category0_.CategoryId as CategoryId2_0_, category0_.Name as Name2_0_
FROM Categories category0_ WHERE category0_.CategoryId=@p0;@p0 = 1 [Type: Int32 (0)]

SELECT payee0_.PayeeId as PayeeId3_0_, payee0_.Name as Name3_0_
FROM Payees payee0_ WHERE payee0_.PayeeId=@p0;@p0 = 1 [Type: Int32 (0)]

INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:48:33 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]

كساني كه قبلا با رويه‌هاي ذخيره شده كار كرده باشند (stored procedures) احتمالا الان خواهند گفت؛ ما كه گفتيم اين روش كند است! سربار زيادي دارد! فقط كافي است يك SP بنويسيد و كل عمليات را با يك رفت و برگشت انجام دهيد.
اما در ORMs نيز براي انجام اين مورد در طي يك حركت يك ضرب راه حل‌هايي وجود دارد كه در ادامه بحث خواهد شد:

د) پياده سازي با NHibernate
براي حل اين مشكل در NHibernate با داشتن primary key (براي مثال از طريق datavaluefield ذكر شده)، بجاي session.Get از session.Load استفاده كنيد.
session.Get يعني همين الان برو به بانك اطلاعاتي مراجعه كن و ركورد متناظر با كليد اصلي ذكر شده را بازگشت بده و يك شيء از آن را ايجاد كن (حالت‌هاي ديگر دسترسي به اطلاعات مانند استفاده از LINQ يا Criteria API يا هر روش مشابه ديگري نيز در اينجا به همين معنا خواهد بود).
session.Load يعني فعلا دست نگه دار! مگر در جدول نهايي نگاشت شده، اصلا چيزي به نام شيء مثلا گروه وجود دارد؟ مگر اين مورد واقعا يك فيلد عددي در جدول Bills بيشتر نيست؟ ما هم كه الان اين عدد را داريم (به كمك عناصر دراپ داون ليست)، پس لطفا در پشت صحنه يك پروكسي براي ايجاد شيء مورد نظر ايجاد كن (uninitialized proxy to the entity) و سپس عمليات مرتبط را در حين تشكيل SQL نهايي بر اساس اين عدد موجود انجام بده. يعني نيازي به رفت و برگشت به بانك اطلاعاتي نيست. در اين حالت اگر SQL نهايي را بررسي كنيم فقط يك سطر زير خواهد بود (سه select ذكر شده حذف خواهند شد):
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:58:22 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]

ه) پياده سازي با Entity framework

Entity framework زمانيكه بانك اطلاعاتي فوق را (به روش database first) به كلاس‌هاي متناظر تبديل/نگاشت مي‌كند، حاصل نهايي مثلا در مورد كلاس Bill به صورت خلاصه به شكل زير خواهد بود:
public partial class Bill : EntityObject
{
public global::System.Int32 BillId {set;get;}
public global::System.Decimal Amount {set;get;}
public global::System.DateTime BillDate {set;get;}
public global::System.String Description {set;get;}
public global::System.Int32 AccountId {set;get;}
public global::System.Int32 CategoryId {set;get;}
public global::System.Int32 PayeeId {set;get;}
public Account Account {set;get;}
public Category Category {set;get;}
}
به عبارتي فيلدهاي كليدهاي خارجي، در تعريف نهايي اين كلاس هم مشاهده مي‌شوند. در اينجا فقط كافي است سه كليد خارجي، از نوع int مقدار دهي شوند (و نيازي به مقدار دهي سه شيء متناظر نيست). در اين حالت نيز براي ثبت اطلاعات، فقط يكبار رفت و برگشت به بانك اطلاعاتي خواهيم داشت.

۱۳۸۹/۱۰/۰۵

خلاصه‌اي در مورد SQL Server CE


SQL Server CE براي اولين بار جهت استفاده در SmartPhones طراحي شد؛ جزو خانواده‌ي Embedded databases قرار مي‌گيرد و اين مزايا را دارد:
- نيازي به نصب ندارد و از چند DLL تشكيل شده است (براي مثال جهت استفاده در كارهاي تك كاربره‌ي قابل حمل ايده‌آل است).
- رايگان است (جهت استفاده در كارهاي تجاري و غيرتجاري).
- حجم كمي دارد (جمعا كمتر از دو مگابايت).
- پروايدر ADO.NET آن موجود است (توسط فضاي نام System.Data.SqlServerCe كه به كمك اسمبلي System.Data.SqlServerCe.dll قرار گرفته در مسير C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Desktop ارائه مي‌شود).
- با كمك ORM هايي مانند Entity framework و يا NHibernate نيز مي‌توان با آن كار كرد.
- نسخه‌ي 4 نهايي آن كه قرار است در زمان ارائه‌ي SP1 مربوط به VS.NET 2010 ارائه شود، جهت استفاده در برنامه‌هاي ASP.NET (برنامه‌هاي چند كاربره) ايي كه تعداد كاربر كمي دارند، بهينه سازي شده و اين مورد يك مزيت مهم نسبت به SQLite است كه اساسا با تردهاي همزمان جهت كار با بانك اطلاعاتي مشكل دارد.
- امكان گذاشتن كلمه‌ي عبور بر روي بانك اطلاعاتي آن وجود دارد كه سبب رمزنگاري خودكار آن نيز خواهد شد (اين مورد به صورت پيش فرض در SQLite پيش بيني نشده و جزو مواردي كه است كه بايد براي آن هزينه كرد). الگوريتم رمزنگاري آن به صورت رسمي معرفي نشده، ولي به احتمال زياد AES مي‌باشد.
- از ADO.NET Sync Framework پشتيباني مي‌كند.

ملاحظات:
- به آن مي‌توان به صورت نسخه‌ي تعديل شده‌ي SQL Server 2000 با توانايي‌هاي كاهش يافته نگاه كرد. در آن خبري از رويه‌هاي ذخيره شده، View ها ، Full text search ، CLR Procs، CLR Triggers و غيره نيست (سطح توقع را بايد در حد همان 2 مگابايت پايين نگه داشت!). ليست كامل : (+)
- Management studio مربوط به SQL Server 2005 به هيچ عنوان از آن پشتيباني نمي‌كند و تنها نسخه‌ي 2008 است كه نگارش 3 و نيم آن‌را پشتيباني مي‌كند آن هم نه با توانايي‌هايي كه جهت كار با SQL Server اصلي وجود دارد. مثلا امكان rename يك فيلد را ندارد و بايد براي اينكار كوئري نوشت. خوشبختانه يك سري پروژه‌ي رايگان در سايت CodePlex اين نقايص را پوشش داده‌اند؛ براي مثال : ExportSqlCe
- از آنجائيكه DLL هاي SQL CE از نوع Native هستند، بايد دقت داشت كه حين استفاده از آن‌ها در دات نت فريم ورك اگر platform target قسمت build برنامه بر روي ALL CPU تنظيم شده باشد، برنامه به احتمال زياد در سيستم‌هاي 64 بيتي كرش خواهد كرد (اگر در حين توسعه برنامه از DLL‌هاي بومي 32 بيتي آن استفاده شده باشد). بنابراين نياز است DLL هاي 64 بيتي را به صورت جداگانه جهت سيستم‌هاي 64 بيتي ارائه داد. اطلاعات بيشتر: (+) و (+) و (+)
- Entity framework يك سري از قابليت‌هاي اين بانك اطلاعاتي را پشتيباني نمي‌كند. براي مثال اگر يك primary key از نوع identity را تعريف كرديد، برنامه كار نخواهد كرد! ليست مواردي را كه پشتيباني نمي‌شوند، در اين آدرس مي‌توان مشاهده كرد.

و اخبار مرتبط با SQL CE را در اين بلاگ مي‌توانيد دنبال كنيد.

۱۳۸۹/۱۰/۰۲

NHibernate 3.0 و خواص تنبل (lazy properties)!


احتمالا مطلب "دات نت 4 و كلاس Lazy" را پيشتر مطالعه كرده‌ايد. هر چند NHibernate 3.0 بر اساس دات نت فريم ورك 3 و نيم تهيه شده، اما شبيه به اين مفهوم را در مورد بارگذاري به تاخير افتاده‌ي مقادير خواص يك كلاس كه به ندرت مورد استفاده قرار مي‌گيرند، پياده سازي كرده است. Lazy را در اينجا تنبل، به تعويق افتاده، با تاخير و شبيه به آن مي‌توان ترجمه كرد؛ خواص معوقه!
براي مثال فرض كنيد يكي از خواص كلاس مورد استفاده، متن، تصوير يا فايلي حجيم است. در مكاني هم كه قرار است وهله‌اي از اين كلاس مورد استفاده قرار گيرد نيازي به اين اطلاعات حجيم نيست؛ با ساير خواص آن سر و كار داريم و نيازي به اشغال حافظه و منابع سيستم در اين مورد خاص نيست.

سؤال: چگونه آن‌را تعريف كنيم؟
اگر از NHibernate سنتي استفاده مي‌كنيد (يا به عبارتي فايل‌هاي hbm.xml را دستي تهيه مي‌كنيد)، ويژگي Lazy را به صورت زير مي‌توان مشخص كرد:
<property name="Text" lazy="true"/>
اگر از Fluent NHibernate استفاده مي‌كنيد (و فايل‌هاي hbm.xml به صورت خودكار از كلاس‌هاي شما تهيه خواهند شد)، روش كار به صورت زير است (فراخواني متد LazyLoad روي خاصيت مورد نظر):
public class Post
{
public virtual int Id { set; get; }
public virtual string PostText { set; get; }
}

public class PostMappings : ClassMap<Post>
{
public PostMappings()
{
Id(p => p.Id, "PostId").GeneratedBy.Identity();
Map(p => p.PostText).LazyLoad();
//…
Table("Posts");
}
}
در اين حالت در پشت صحنه در مورد خاصيت PostText چنين نگاشتي تعريف خواهد شد:
<property name="PostText" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" lazy="true" />

سؤال: چه زماني نبايد از اين روش استفاده كرد؟
فرض كنيد در شرايطي ديگر نياز است تا اطلاعات تمام ركوردهاي جدول مذكور به همراه مقدار PostText نمايش داده شود. در اين حالت بسته به تعداد ركوردها، ممكن است هزاران هزار كوئري به ديتابيس ايجاد شود كه مطلوب نيست (به ازاي هربار دسترسي به خاصيت PostText يك كوئري توليد مي‌شود).

البته امكان لغو موقت اين روش تنها در حين استفاده از HQL (يكي ديگر از روش‌هاي دسترسي به داده‌ها در NHibernate) ميسر است. اطلاعات بيشتر: (+)

۱۳۸۹/۱۰/۰۱

NHibernate 3.0 و عدم وابستگي مستقيم به Log4Net


اولين موردي كه پس از دريافت NHibernate 3.0 ممكن است به چشم بخورد، نبود اسمبلي Log4Net است. مطابق درخواست‌هاي كاربران، ارجاع مستقيم به اين كتابخانه حذف شده و با يك اينترفيس عمومي به نام IInternalLogger جايگزين گشته است (قرار گرفته در فضاي نام NHibernate.Logging). به اين صورت مي‌توان از انواع و اقسام كتابخانه‌هاي ثبت وقايع نوشته شده براي دات نت استفاده كرد؛ مانند: log4net، Nlog، EntLib Logging و غيره.
البته لازم به ذكر است كه همان روش قبلي استفاده از Log4Net هنوز هم پشتيباني مي‌شود (بدون نياز به تغيير خاصي در كدهاي خود)، زيرا پياده سازي اينترفيس جديد IInternalLogger براي استفاده از آن به صورت پيش فرض توسط NHibernate انجام شده است.

يك مثال:

كتابخانه‌ي سورس باز Common.Logging واقع شده در سورس فورج، در واقع يك logging abstraction framework است. به اين معنا كه تا به حال كتابخانه‌‌هاي ثبت وقايع مختلف و متعددي براي دات نت فريم ورك نوشته شده است و هر كدام راه و روش و پياده سازي خاص خود را دارند. كتابخانه‌ي Common.Logging لايه‌اي است عمومي بر روي تمام اين كتابخانه‌ها مانند Log4Net، Enterprise Library Logging ، Nlog و غيره كه برنامه‌ي شما را از وابستگي مستقيم به هر كدام از موارد ذكر شده رها مي‌سازد.
اكنون با توجه به وجود اينترفيس IInternalLogger در NHibernate 3.0 ، تنها كافي است اين اينترفيس جهت استفاده از كتابخانه‌ي Common.Logging پياده سازي شود (abstraction اندر abstraction !). البته نيازي نيست اينكار انجام شود، زيرا پيشتر توسط پروژه‌ي NHibernate.Logging در سايت كدپلكس اينكار صورت گرفته است.
بنابراين تنها كاري كه بايد انجام داد اين است كه :
الف) ارجاعاتي را به اسمبلي‌هاي Common.Logging.dll (واقع در سورس فورج) و NHibernate.Logging.CommonLogging.dll (واقع در كدپلكس) به پروژه‌ي خود اضافه كنيد.
ب) ارجاعي را به اسمبلي كتابخانه‌ي ثبت وقايع مورد نظر خود نيز بايد اضافه نمائيد (مثلا NLog).
ج) سپس بايد چند سطر زير را به فايل app.config خود اضافه كنيد:

<appSettings>
<add key="nhibernate-logger"
value="NHibernate.Logging.CommonLogging.CommonLoggingLoggerFactory, Hibernate.Logging.CommonLogging"/>
</appSettings>

NHibernate.Logging.CommonLogging.dll وقايع داخلي NHibernate را با پياده سازي IInternalLogger به Common.Logging.dll منتقل مي‌كند. سپس Common.Logging.dll اين وقايع را به زبان كتابخانه‌ي ثبت وقايع مورد نظر ترجمه خواهد كرد.

اطلاعات بيشتر: (+)

۱۳۸۹/۰۹/۳۰

NHibernate 3.0 و ارائه‌ي جايگزيني جهت ICriteria API


ICriteria API در NHibernate پياده سازي الگوي Query Object است. مشكلي هم كه اين روش دارد استفاده از رشته‌ها جهت ايجاد كوئري‌هاي متفاوت است؛ به عبارتي Type safe نيست. ايرادي هم به آن وارد نيست چون پياده سازي اوليه آن از جاوا صورت گرفته و مباحث Lambda Expressions و Extension Methods هنوز در آن زبان به صورت رسمي ارائه نشده است (در JDK 7 تحت عنوان Closures قرار است اضافه شود). NHibernate 3.0 از ويژگي‌هاي جديد زبان‌هاي دات نتي جهت ارائه‌ي محصور كننده‌اي Type safe حول ICriteria API استاندارد به نام QueryOver API سود جسته است. اين پياده سازي بسيار شبيه به عبارات LINQ است اما نبايد با آن اشتباه گرفته شود زيرا LINQ to NHibernate‌ يك ويژگي ديگر جديد، يكپارچه و استاندارد NHibernate 3.0 به شمار مي‌رود.
براي نمونه در يك ICriteria query متداول، فراخواني‌هاي ذيل متداول است:
.Add(Expression.Eq("Name", "Smith"))
اكنون شما در NHibernate 3.0 مي‌توانيد دستورات فوق را به صورت ذيل وارد نمائيد:
.Where<Person>(p => p.Name == "Smith")

مزيت‌هاي اين روش (strongly-typed fluent API) به شرح زير است:
- خبري از رشته‌ها جهت استفاده از يك خاصيت وجود ندارد. براي مثال در اينجا خاصيت Name كلاس Person تحت كنترل كامپايلر قرار مي‌گيرد و اگر در كلاس Person تغييراتي حاصل شود، براي مثال Name به LName تغيير كند، برنامه ديگر كامپايل نخواهد شد. اما در حالت ICriteria API يا بايد به نتايج حاصل از Unit testing مراجعه كرد يا بايد به نتايج بازخورد كاربران برنامه مانند: "باز برنامه رو تغيير دادي، يكجاي ديگر از كار افتاد!" دقت نمود!
- اگر در حين ويرايش كلاس Person از ابزارهاي Refactoring استفاده شود، تغييرات حاصل به صورت خودكار به تمام برنامه نيز اعمال خواهد شد. بديهي است اين اعمال تغييرات تنها در صورتي ميسر است كه خاصيت مورد نظر به صورت رشته معرفي نگرديده و ارجاعات به اشياء تعريف شده به سادگي قابل parse باشند.
- در اين حالت امكان بررسي نوع خواص تغيير كرده نيز توسط كامپايلر به سادگي ميسر است و اگر ارجاعات تعريف شده به نحو صحيحي از اين نوع جديد استفاده نكنند باز هم برنامه تا رفع اين مشكلات كامپايل نخواهد شد كه اين هم مزيت مهمي در نگهداري ساده‌تر يك برنامه است.
- با بكارگيري Extension methods و پياده سازي Fluent API جديد، مدت زمان يادگيري اين روش نيز به شدت كاهش يافته، زيرا Intellisense موجود در VS.NET بهترين راهنماي استفاده از امكانات فراهم شده است. براي مثال جهت استفاده از ويژگي جديد QueryOver به سادگي مي‌توان پس از ساختن يك session جديد به صورت زير عمل نمود:
IList<Cat> cats = session.QueryOver<Cat>().Where(c => c.Name == "Max").List();
در اينجا اگر متدهاي نمايش داده شده توسط Intellisense را دنبال كنيم ديگر حتي نيازي به مراجعه به مستندات QueryOver در مورد اينكه چه متدها و امكاناتي را فراهم كرده است نيز نخواهد بود.

جهت مشاهده‌ي معرفي كامل آن مي‌توان به مستندات NHibernate 3.0 مراجعه كرد.

۱۳۸۹/۰۹/۲۹

Feedproxy گوگل هم فيلتر شد


چند وقت پيش كلا feeds.feedburner.com به همراه هزاران هزار زير دامنه‌ي آن فيلتر شدند؛ از ديروز هم feedproxy.google.com فيلتر شده و هزاران هزار زير دامنه‌ي آن ديگر به سادگي در دسترس نيستند. مورد اول را كسي زياد جدي نگرفت چون لينك و راه حل ساده‌ي دومي هم دارد، اما اين مورد اخير استفاده از گوگل ريدر را با مشكل مواجه كرده. در هر حال دلخوشي و مهمترين منابع مطالعاتي ما هم همين چيزها است ...


۱۳۸۹/۰۹/۲۸

يكسان سازي ي و ك دريافتي حين استفاده از NHibernate



تصوير فوق، يكي از تصويرهايي است كه شايد از طريق ايميل‌هايي تحت عنوان "فقط در ايران!" به دست شما هم رسيده باشد. تصور كاربر نهايي (كه اين ايميل را با تعجب ارسال كرده) اين است كه در اينجا به او گفته شده مثلا "مرتضي" را جستجو نكنيد و امثال آن. چون براي او تفاوتي بين ي و ى وجود ندارد. همچنين بكار بردن "اقلامي" هم كمي غلط انداز است و بيشتر ذهن را به سمت كلمه سوق مي‌دهد تا حرف.

در ادامه‌ي بحث آلرژي مزمن به وجود انواع "ي" و "ك" در بانك اطلاعاتي (+ و + و +)، اينبار قصد داريم اين اطلاعات را به NHibernate بسط دهيم. شايد يك روش اعمال يك دست سازي "ي" و "ك" اين باشد كه در كل برنامه هر جايي كه قرار است update يا insert ايي صورت گيرد، خواص رشته‌اي را يافته و تغيير دهيم. اين روش "كار مي‌كنه" ولي ايده آل نيست؛ چون حجم كار تكراري در برنامه زياد خواهد شد و نگهداري آن هم مشكل مي‌شود. همچنين امكان فراموش كردن اعمال آن هم وجود دارد.
در NHibernate يك سري EventListener وجود دارند كه كارشان گوش فرا دادن به يك سري رخدادها مانند مثلا update يا insert است. اين رخدادها مي‌توانند پيش يا پس از هرگونه ثبت يا ويرايشي در برنامه صادر شوند. بنابراين بهترين جايي كه جهت اعمال اين نوع مميزي (Auditing) بدون بالا بردن حجم برنامه يا اضافه كردن بيش از حد يك سري كد تكراري در حين كار با NHibernate مي‌توان يافت، روال‌هاي مديريت كننده‌ي همين EventListener ها هستند.

كلاس YeKeAuditorEventListener نهايي با پياده سازي IPreInsertEventListener و IPreUpdateEventListenerبه شكل زير خواهد بود:
using NHibernate.Event;

namespace NHYeKeAuditor
{
public class YeKeAuditorEventListener : IPreInsertEventListener, IPreUpdateEventListener
{
// Represents a pre-insert event, which occurs just prior to performing the
// insert of an entity into the database.
public bool OnPreInsert(PreInsertEvent preInsertEvent)
{
var entity = preInsertEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}

// Represents a pre-update event, which occurs just prior to performing the
// update of an entity in the database.
public bool OnPreUpdate(PreUpdateEvent preUpdateEvent)
{
var entity = preUpdateEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}
}
}
در كدهاي فوق روال‌هاي OnPreInsert و OnPreUpdate پيش از ثبت و ويرايش اطلاعات فراخواني مي‌شوند (همواره و بدون نياز به نگراني از فراموش شدن فراخواني كدهاي مربوطه). اينجا است كه فرصت داريم تا تغييرات مورد نظر خود را جهت يكسان سازي "ي" و "ك" دريافتي اعمال كنيم (كد كلاس CorrectYeKe را در پيوست خواهيد يافت).

تا اينجا فقط تعريف YeKeAuditorEventListener انجام شده است. اما NHibernate چگونه از وجود آن مطلع خواهد شد؟
براي تزريق كلاس YeKeAuditorEventListener به تنظيمات برنامه بايد به شكل زير عمل كرد:
using System;
using System.Linq;
using FluentNHibernate.Cfg;
using NHibernate.Cfg;

namespace NHYeKeAuditor
{
public static class MappingsConfiguration
{
public static FluentConfiguration InjectYeKeAuditorEventListener(this FluentConfiguration fc)
{
return fc.ExposeConfiguration(configListeners());
}

private static Action<Configuration> configListeners()
{
return
c =>
{
var listener = new YeKeAuditorEventListener();
c.EventListeners.PreInsertEventListeners =
c.EventListeners.PreInsertEventListeners
.Concat(new[] { listener })
.ToArray();
c.EventListeners.PreUpdateEventListeners =
c.EventListeners.PreUpdateEventListeners
.Concat(new[] { listener })
.ToArray();
};
}
}
}
به اين معنا كه FluentConfiguration خود را همانند قبل ايجاد كنيد. درست در زمان پايان كار تنها كافي است متد InjectYeKeAuditorEventListener فوق بر روي آن اعمال گردد و بس (يعني پيش از فراخواني BuildSessionFactory).

كدهاي NHYeKeAuditor را از اينجا مي‌توانيد دريافت كنيد.

۱۳۸۹/۰۹/۲۷

WPF4 و ويندوز 7 : به خاطر سپاري ليست آخرين فايل‌هاي گشوده شده توسط برنامه


اگر به برنامه‌هاي جديد نوشته شده براي ويندوز 7 دقت كنيم، از يك سري امكانات مخصوص آن جهت بهبود دسترسي پذيري به قابليت‌هايي كه ارائه مي‌دهند، استفاده شده است. براي مثال برنامه‌ي OneNote مجموعه‌ي آفيس را در نظر بگيريد. اگر بر روي آيكون آن در نوار وظيفه‌ي ويندوز كليك راست كنيم، ليست آخرين فايل‌هاي گشوده شده توسط آن مشخص است و با كليك بر روي هر كدام، به سادگي مي‌توان اين فايل را گشود. يك چنين قابليتي در منوي آغازين ويندوز نيز تعبيه شده است (شكل‌هاي زير):




خبر خوب اينكه براي اضافه كردن اين قابليت به برنامه‌هاي WPF4 نيازي به كد نويسي نيست و اين موارد كه تحت عنوان استفاده از Jump list ويندوز 7 تعريف شده‌اند، با كمي دستكاري فايل App.Xaml برنامه، فعال مي‌گردند:

<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
همين! از اين پس هر فايلي كه توسط برنامه‌ي شما با استفاده از common file dialog boxes باز شود به صورت خودكار به ليست مذكور اضافه مي‌گردد (بديهي است Jump lists جزو ويژگي‌هاي ويندوز 7 است و در ساير سيستم عامل‌ها نديد گرفته خواهد شد).


سؤال: من اينكار را انجام دادم ولي كار نمي‌كنه!؟

پاسخ: بله. كار نمي‌كنه! اين قابليت تنها زماني فعال خواهد شد كه علاوه بر نكته‌ي فوق، پسوند فايل يا فايل‌هايي نيز به برنامه‌ي شما منتسب شده باشد. اين انتساب‌ها مطلب جديدي نيست و در تمام برنامه‌هاي ويندوزي بايد توسط بكارگيري API ويندوز مديريت شود. قطعه كد زير اينكار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;

public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);

try
{
if (IsAssociated(extension)) return;

// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}

// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}

// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}

[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
و مثالي از نحوه‌ي استفاده از آن:
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
لازم به ذكر است كه اين كد در ويندوز 7 فقط با دسترسي مديريتي قابل اجرا است (كليك راست و اجرا به عنوان ادمين) و در ساير حالات با خطاي Access is denied متوقف خواهد شد. به همين جهت بهتر است برنامه‌ي نصاب مورد استفاده اين نوع انتسابات را مديريت كند؛ زيرا اكثر آن‌ها با دسترسي مديريتي است كه مجوز نصب را به كاربر جاري خواهند داد. اگر از فناوري Click once استفاده مي‌كنيد به اين مقاله و اگر براي مثال از NSIS كمك مي‌گيريد به اين مطلب مراجعه نمائيد.


سؤال: من اين كارها را هم انجام دادم. الان به چه صورت از آن‌ استفاده كنم؟

زمانيكه كاربري بر روي يكي از اين فايل‌هاي ذكر شده در ليست آخرين فايل‌هاي گشوده شده توسط برنامه كليك كند، آدرس اين فايل به صورت يك آرگومان به برنامه ارسال خواهد شد. براي مديريت آن در WPF بايد به فايل App.Xaml.cs مراجعه كرده و چند سطر زير را به آن افزود:
    public partial class App
{
public App()
{
this.Startup += appStartup;
}

void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...

در اين كد، e.Args حاوي مسير فايل انتخابي است. براي مثال در اينجا مقدار آن به خاصيت StartupFileName انتساب داده شده است. اين خاصيت در برنامه‌هاي WPF به صورت يك خاصيت عمومي تعريف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلي آن يا هر جاي ديگري) به صورت زير قابل دسترسي است:
var startupFileName = Application.Current.Properties["StartupFileName"];

سؤال: برنامه‌ي من از OpenFileDialog براي گشودن فايل‌ها استفاده نمي‌كند. آيا راه ديگري براي افزودن مسيرهاي باز شده به Jump lists ويندوز 7 وجود دارد؟

پاسخ: بله. همانطور كه مي‌دانيد عناصر XAML با اشياء دات نت تناظر يك به يك دارند. به اين معنا كه JumpList تعريف شده در ابتداي اين مطلب در فايل App.XAML ، دقيقا معادل كلاسي به همين نام در دات نت فريم ورك است (تعريف شده در فضاي نام System.Windows.Shell) و با كد نويسي نيز قابل دسترسي و مديريت است. براي مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
به همين ترتيب،‌ JumpPath ذكر شده در كدهاي فوق، در كدهاي XAML نيز قابل تعريف است:
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>

۱۳۸۹/۰۹/۲۴

پايان پشتيباني از دات نت فريم ورك 3 و سه و نيم


پشتيباني از دات نت فريم ورك‌هاي سه و سه و نيم دقيقا در تاريخ "سه‌شنبه 23 فروردين 1390" پايان خواهد يافت. توصيه مايكروسافت ارتقاء برنامه‌هاي موجود به دات نت فريم ورك سه و نيم، سرويس پك يك و يا دات نت 4 است. طول عمر دات نت فريم ورك سه و نيم، سرويس پك يك به صورت Component محاسبه مي‌شود؛ به اين معنا كه اگر دقت كرده باشيد اين نگارش به صورت پيش فرض جزئي از ويندوز 7 يا ويندوز سرور 2008 است. بنابراين تا زمانيكه اين دو سيستم عامل توسط مايكروسافت پشتيباني مي‌شوند، اجزاي آن‌ها نيز همانند دات نت فريم ورك سه و نيم، سرويس پك يك، پشتيباني خواهند شد.
جدول كامل طول عمر نگارش‌هاي مختلف دات نت فريم ورك را در اينجا مي‌توانيد ملاحظه كنيد.

۱۳۸۹/۰۹/۲۲

چگونه از CodePlex به عنوان مخزني جهت ذخيره سازي كدهاي سايت يا وبلاگ خود استفاده كنيم؟


به شخصه اعتقادي ندارم كه جهت مديريت كار رايگاني كه انجام مي‌شود از امكانات غير رايگان استفاده كرد. تابحال براي ذخيره سازي كدهاي منتشر شده در اين وبلاگ از persiangig تا googlepages مرحوم تا رپيدشير تا ... استفاده كرده‌ام. نه امكان ليست كردن سريع آن‌ها موجود است و نه مشخص است كه چه تعدادي از آن‌ها هنوز وجود خارجي داشته و از سرورهاي ياد شده پاك نشده‌اند. اخيرا تعدادي وبلاگ برنامه نويسي را يافته‌ام كه از سايت CodePlex به عنوان مخزني براي ذخيره سازي كدها و مثال‌هاي منتشر شده در وبلاگ خود استفاده مي‌كنند. اين كار چند مزيت دارد:
- رايگان است (فضا، پهناي باند، اسكريپت و غيره)
- به صورت تضميني تا 10 سال ديگر هم پابرجا است.
- درب آن به روي كاربران ايراني باز است (برخلاف مثلا سايت googlecodes يا رفتار اخير سورس فورج و غيره، سايت CodePlex در اين چندسال رويه ثابتي داشته است)
- امكان مشاهده‌ي ليست تمامي كدهاي منتشر شده‌ موجود است.
- امكان ثبت توضيحات كنار هر كد منتشر شده نيز وجود دارد.
- امكان دريافت يكجاي آن‌ها با توجه به استفاده از ابزارهاي سورس كنترل مهيا است.
- امكان دريافت بهينه‌ي موارد جديد هم براي كاربران وجود دارد. كاربري كه يكبار با استفاده از ابزارهاي سورس كنترل، كدهاي موجود را دريافت كرده، در بار بعدي دريافت اطلاعات، تنها موارد تغيير كرده يا جديد را دريافت خواهد كرد و نه تمام اطلاعات كل مخزن را از ابتدا تا به امروز.
- امكان مشاهده‌ي آمار دريافت‌ها، مراجعات، سايت‌هايي كه به شما لينك داده‌اند و غيره فراهم است.
- امكان دعوت كردن از افراد ديگر نيز جهت به روز رساني مخزن كد تدارك ديده شده است.
- كليه اعضاي CodePlex بدون نياز به عضويت در گروه مخزن كد شما، مي‌توانند جهت تكميل يا اصلاح كار شما patch يا وصله ارسال كنند.
و ...

اما براي استفاده از اين امكانات نياز است حداقل اطلاعاتي را در مورد كار با ابزارهاي سورس كنترل داشت، كه خلاصه‌ي مختصر و مفيد آن‌را در ادامه ملاحظه خواهيد نمود:
0 - دريافت و نصب برنامه‌ي TortoiseSVN
1- ثبت نام در سايت CodePlex
رايگان است.

2- ايجاد يك پروژه‌ي جديد


كه به همراه وارد كردن مشخصات اوليه آن است:


تنها نكته‌ي مهم آن انتخاب سورس كنترل Team foundation server و سپس Subversion است چون مي‌خواهيم با استفاده از TortoiseSVN كار به روز رساني اطلاعات را انجام دهيم.

3- انتخاب مجوز براي پروژه در برگه‌ي License پروژه ايجاد شده

تا مجوزي را براي پروژه انتخاب نكنيد، مجوز ارائه‌ي عمومي آن‌را نخواهيد يافت. در مورد مقايسه‌ي مجوز‌هاي سورس باز لطفا به اين مطلب مراجعه كنيد.

4- checkout كردن سورس كنترل
ابتدا به برگه‌ي source code پروژه مراجعه كرده و بر روي لينك subversion در كنار صفحه كليك كنيد.

در صفحه‌ي باز شده مشخصات اتصال به مخزن كد را جهت به روز رساني آن مشاهده خواهيد نمود.
اكنون جهت استفاده از آن يك پوشه‌ي مشخص را در سيستم خود براي قرار دادن فايل‌ها و ارسال آن به مخزن كد ايجاد كنيد. مثلا به نام SiteRepository . سپس جايي داخل اين پوشه، كليك راست كرده و گزينه‌ي SVN Checkout را انتخاب كنيد:


در صفحه‌ي باز شده آدرس svn مربوط به پروژه خود را وارد نموده و بر روي Ok كليك كنيد:



در صفحه‌ي بعدي بايد نام كاربري و كلمه‌ي عبور مرتبط با حساب كاربري سايت كدپلكس خود را وارد نمائيد. همچنين بهتر است گزينه‌ي به خاطر سپاري آن‌را نيز براي سهولت كار در دفعات بعدي انتخاب كنيد:



به اين صورت يك پوشه‌ي مخفي svn در اينجا تشكيل خواهد شد كه اطلاعات مخزن كد را در خود نگهداري مي‌كند و نبايد آن‌را حذف كرد، تغيير داد، يا جابجا كرد.



5- اضافه كردن فايل‌هاي دلخواه به مخزن كد
براي اضافه كردن كدهاي مورد نظر خود، آن‌ها را به پوشه‌ي SiteRepository فوق كپي كرده و سپس بر روي آن‌ها كليك راست نموده و گزينه‌ي Add مربوط به TortoiseSVN را انتخاب كنيد:



به اين صورت تنها فايل‌هاي مورد نظر جهت اضافه شدن به مخزن كد علامتگذاري خواهند شد (ايجاد پوشه و قرار دادن فايل‌ها درون آ‌ن‌ها نيز به همين ترتيب است):



اكنون براي تكميل فرايند، جايي درون پوشه كليك راست كرده و گزينه‌ي SVN Commit را انتخاب كنيد:



در صفحه‌ي باز شده توضيحاتي را در مورد فايل‌هاي ارسالي وارد كرده و سپس بر روي دكمه‌ي OK كليك نمائيد:



پس از مدتي كار هماهنگ سازي اطلاعات با مخزن كد صورت خواهد گرفت:



همچنين آيكون فايل‌هاي مورد نظر نيز بر روي كامپيوتر شما به صورت زير تغيير خواهند كرد:



6- ارائه نهايي پروژه
فراموش نكنيد كه پس از ايجاد يك پروژه‌ي جديد، انتخاب مجوز و ارسال فايل‌هاي مورد نظر، بايد بر روي دكمه‌ي publish this project در بالاي صفحه كليك كرد. در غيراينصورت پروژه‌ي شما در روز بعد به صورت خودكار از سايت CodePlex حذف مي‌گردد:




براي نمونه مخزن جديد كدهاي وبلاگ جاري را در آدرس زير مي‌توانيد مشاهده كنيد:


در دفعات آتي، تنها تكرار مرحله 5 يعني كپي كردن فايل‌هاي مورد نظر به پوشه‌ي SiteRepository، سپس Add و در نهايت Commit آن‌ها كفايت مي‌كند و نيازي به تكرار ساير مراحل نيست. عمليات هماهنگ سازي با مخزن كد هم بسيار بهينه است و تنها فايل‌هايي كه اخيرا اضافه شده و هنوز ارسال نشده‌اند، Commit خواهند شد.
كاربران نهايي هم يا از طريق اينترفيس تحت وب سايت مي‌توانند از فايل‌هاي شما استفاده كنند و يا روش ديگري هم براي اين منظور وجود دارد (همان Checkout كردن ياد شده و سپس هر بار انتخاب گزينه‌ي SVN update بجاي Commit جهت دريافت فايل‌هاي جديد و نه كل مخزن كد به صورت يكجا).

۱۳۸۹/۰۹/۲۰

خلاصه‌اي در مورد وضعيت فعلي MySQL


MySQL مدتي است كه جزو يكي از محصولات شركت اوراكل محسوب شده و توسعه دهندگان تجاري بايد براي استفاده از آن هزينه كنند. اين هزينه نيز اخيرا افزايش يافته و به حداقل 2000 دلار به ازاي هر سرور رسيده است (+). اين عدد واقعا رقم بالايي براي محصولي محسوب مي‌شود كه بسياري از توسعه دهنده‌ها تصور مي‌كنند رايگان است. استفاده از اين محصول با توجه به مدل تجاري جديد آن فقط در پروژه‌هاي سورس باز رايگان است (بله فقط در پروژه‌هايي كه با مجوز GPL منتشر شوند) و اگر شما يك سيستم تجاري كلاينت سرور را بر اين اساس طراحي كنيد حتما بايد هزينه‌هاي مرتبط را نيز پرداخت نمائيد (+).
توضيحي در مورد GPL و MySQL
MySQL AB offers a commercial license for organizations that do not want to release the source code for their application.
The change from the LGPL to the GPL for the client libraries was made in 2001 during the development of MySQL 4.0 to help MySQL AB more easily differentiate between a proprietary user who should buy a commercial license and a free software user who should use the GPL license.

MySQL با توجه به مجوز GPL آن در شرايط زير رايگان خواهد بود:
- قصد توزيع مجدد آن‌را نداشته باشيد.
- همچنين برنامه‌ي شما نيز به صورت سورس باز تحت مجوز GPL ارائه گردد.
و تنها زماني در مورد MySQL بايد هزينه كنيد كه:
-قصد توزيع مجدد آن‌را داشته باشيد.
-برنامه‌ي شما سورس باز نبوده و قصد نداريد آن‌را تحت مجوز GPL ارائه دهيد. (كه عموما در مورد برنامه‌هاي تجاري به همين صورت است)

نكته‌ي ديگري را كه بايد به آن دقت داشت اين است كه براي واگذاري MySQL به شركت اوراكل،‌ اتحاديه اروپا نيز با توجه به وجود بيش از 50 هزار توسعه دهنده‌ي اروپايي كه از MySQL استفاده مي‌كنند، شركت اوراكل را موظف كرده است تا اين dual licensing (تجاري و سورس باز) را تا سال 2015 حفظ كرده و ادامه دهد (+). به اين معنا كه شركت اوراكل پس از سال 2015 هيچگونه تعهدي به ارائه‌ي نگارش سورس باز اين محصول به هيچ نهاد و يا سازماني ندارد.
البته اين‌ها به معناي پايان دنيا نيست. هم اكنون چهار fork سورس باز از اين محصول وجود دارند (Drizzle ، MariaDB ، OurDelta و Percona Server) ولي تنها آينده است كه ميزان موفقيت، پايداري و تداوم آن‌ها را مشخص خواهد كرد.

۱۳۸۹/۰۹/۱۸

متغيرهاي استاتيك و برنامه‌هاي ASP.NET


هر متغير استاتيك تنها داراي يك مقدار، در يك AppDomain مشخص است (مگر اينكه با ويژگي ThreadStatic مزين شود). هر برنامه‌ي ASP.NET هم AppDomain جداگانه و منحصر به خود را دارا است. بنابراين تعريف يك متغير استاتيك در يك برنامه‌ي ASP.NET به معناي به اشتراك گذاري آن در بين تمامي درخواست‌هاي رسيده به سرور است. بنابراين عموما استفاده از متغيرهاي استاتيك در برنامه‌هاي چند كاربره ASP.NET يك اشتباه بزرگ است و در صورت استفاده از آن بايد منتظر تخريب اطلاعات يا دريافت نتايج غيرمنتظره‌اي باشيد (مگر اينكه واقعا مي‌دانيد داريد چكار مي‌كنيد، براي مثال كش كردن نگاشت‌هاي NHibernate به اين صورت و استفاده از الگوي singleton يا روش‌هاي مشابه كه بايد بين تمام كاربران به يك صورت و يك شكل به اشتراك گذاشه شود و در حين اجراي برنامه تغييري در آن حاصل نمي‌شود). براي مثال اگر كاربر يك، در صفحه‌ي يك، متغير استاتيكي را مقدار دهي كند، كاربر 2 نيز با مقدار به روز شده‌ي كاربر يك كار خواهد كرد كه به طور قطع اين مورد مد نظر شما نيست (چون به احتمال زياد طراحي شما بر اساس كار كاربر در يك Session است و نه يك مقدار براي تمام سشن‌هاي موجود در سايت) و همچنين بايد دقت داشت كه امنيت سيستم نيز در اين حالت زير سؤال است (زيرا در اين حالت تمامي كاربران، صرفنظر از سطوح دسترسي تعريف شده براي آن‌ها، دسترسي به اطلاعاتي خواهند داشت كه نبايد داشته باشند).
نكته‌ي ديگري را هم كه بايد در مورد ASP.NET به خاطر داشت اين است كه ويژگي ThreadStatic نيز در اينجا كمكي نمي‌كند؛ زيرا مطابق طراحي آن از تردها استفاده‌ي مجدد مي‌گردد.به عبارت ديگر در ASP.NET الزامي ندارد كه آغاز يك درخواست جديد حتما به همراه ايجاد يك ترد جديد باشد.
طول عمر اين نوع متغيرها هم تا زماني است كه وب سرور يا برنامه ري استارت شوند. فقط در اين حالت است كه نمونه‌ي موجود تخريب شده و سپس با اجراي مجدد برنامه، بازسازي خواهند شد.
بنابراين متغيرهاي استاتيك در ASP.NET همانند شيء Application عمل مي‌كنند و از آن سريع‌تر هستند زيرا زمانيكه به آن‌ها ارجاع مي‌شود نيازي به جستجو در يك جدول و يافتن آن‌ها نيست (برخلاف شيء Application) و همچنين در اينجا نيازي هم به عمليات تبديل نوع داده‌اي وجود ندارد (برخلاف نوع شيء Application كه به صورت Object تعريف شده است). وجود اشياء Application در ASP.NET فقط به جهت حفظ سازگاري آن با ASP كلاسيك است و توصيه شده است در ASP.NET به دلايلي كه ذكر شد،‌ اگر و تنها اگر نياز به اشيايي در سطح برنامه داشتيد از متغيرهاي استاتيك استفاده كنيد. شيء Cache نيز در ASP.NET همين كاربرد را دارد با اين تفاوت كه مي‌توان براي آن مدت زمان منقضي شدن تعريف كرد يا اينكه وب سرور بسته به حق تقدم و اهميتي كه براي آن تعريف شده است، مجاز به حذف كردن آن در زماني است كه با كمبود منابع مواجه مي‌شود. همچنين بايد دقت داشت كه تنها مكان ذخيره سازي متغيرهاي استاتيك حافظه‌ است اما امكان دخيره سازي كش بر روي فايل سيستم تا بانك اطلاعاتي و غيره نيز مهيا است.

سؤال: آيا تعريف SqlConnection به صورت استاتيك جزو مواردي است كه "مگر واقعا مي‌دانيد داريد چكار مي‌كنيد؟" ؟
پاسخ: خير. در اينجا هم واقعا اين شخص نمي‌داند كه دارد چكار مي‌كند! يعني در مورد سازوكار دروني ADO.NET اطلاعاتي ندارد. باز كردن يك كانكشن در ADO.NET به معناي مراجعه به استخر (pool) كانكشن‌ها و بازكردن يكي از آن‌ها و در مقابل، بستن يك كانكشن هم به معناي علامتگذاري يك كانكشن به صورت غيرفعال است و آماده سازي آن براي استفاده در درخواست بعدي. به معناي ديگر اين عمليات سربار آنچناني ندارد كه بخواهيد آن‌را استاتيك تعريف كنيد.
همچنين مورد ديگري را هم كه اين برنامه نويس نمي‌داند اين است كه متغيرهاي استاتيك thread safe نيستند. به عبارتي حين استفاده از آن‌ها در يك برنامه‌ي چندكاربره‌ي ASP.NET حتما بايد مكانيزم‌هاي قفل‌گذاري بر روي اين نوع متغيرها و اشياء اعمال شود (كه اين هم خود يك سربار اضافي است در مقياس چند 10 يا چند 100 كاربر همزمان). اين مشكلات همزماني به چه معنا است؟ فرض كنيد كاربر يك، شيء استاتيك SqlConnection ايي را باز كرده است و با آن مشغول كوئري گرفتن است. كاربر 2 نيز همزمان شروع به استفاده از اين كانكشن باز در حال استفاده مي‌كند (SqlConnection استاتيك يعني استفاده‌ي تمام كاربران فقط و فقط از يك كانكشن باز شده)، نتيجه اين خواهد بود كه براي مثال پيغام خطايي را دريافت مي‌كند مانند: فيلد مورد نظر در جدول موجود نيست! چرا؟ چون روي شيء استاتيك SqlConnection تعريف شده قفل گذاري صورت نگرفته است و در حين استفاده از آن هر كاربري در سايت نيز همان را استفاده خواهد كرد يا از آن بدتر ممكن است يك كاربر زودتر از كاربر ديگري آن‌را ببندد! كاربر سوم در وسط كار با پيغام غيرمعتبر بودن كانكشن مواجه مي‌شود، يا اينكه به صورت پيش فرض يك datareader را بيشتر نمي‌توان بر روي يك كانكشن باز شده اعمال كرد. كاربر 4 مشغول خواندن اطلاعات است، كاربر 5 ، پيغام غيرمعتبر بودن كوئري را دريافت مي‌كند.

۱۳۸۹/۰۹/۱۶

VMWare 7 و هنگ‌هاي پي در پي


من براي نصب نگارش‌هاي مختلف VS.NET از VMWare استفاده مي‌كنم. به اين صورت تهيه بك آپ از يك يا چند فايل نهايي آن بسيار ساده خواهد بود و همچنين كل مجموعه قابل حمل مي‌شود و به علاوه تداخل نگارش‌هاي مختلف ويژوال استوديو را هم نخواهم داشت؛ اما ...
اگر از VMWare 7 استفاده مي‌كنيد و اجراي اوليه آن كمي طول مي‌كشد يا هر از 10 تا 15 دقيقه يكبار اين برنامه در حالت كما فرو مي‌رود، مشكل از روشن بودن بررسي به روز رساني‌هاي آن از اينترنت است كه در لاگ فايل آن هم قابل مشاهده مي‌باشد:

CDS error: Failed to finish active transfer for https://softwareupdate.vmware.com/cds/index.xml: CDS_HTTP_HOST_RESOLVE_ERROR
براي خاموش كردن بررسي به روز رساني‌هاي آن به منوي Edit->Preferences->updates مراجعه كرده و تيك‌هاي مربوطه را برداريد.
روش ديگر انجام اينكار ويرايش فايل config.ini آن مي‌باشد: (و بهتر است ويرايش گردد)
installerDefaults.autoSoftwareUpdateEnabled = "no"
installerDefaults.componentDownloadEnabled = "no"
installerDefaults.dataCollectionEnabled = "no"
فايل ياد شده در مسير زير قرار دارد:
C:\Documents and Settings\All Users\Application Data\VMware\VMware Workstation\config.ini

البته از شركت VMWare انتظار بيشتري از اين مي‌رفت ولي خوب ... اين فقط يك ضعف شديد برنامه نويسي است. بررسي synchronous بجاي asynchronous به روز رساني‌ها، طوري كه هر 10 تا 15 دقيقه يكبار عملا كل برنامه به خاطر اين موضوع از كار مي‌ايستد.

۱۳۸۹/۰۹/۱۵

پياده سازي اسكرام با شيرپوينت


كتاب‌هاي زيادي در مورد شيرپوينت نوشته شده، اما اين يكي متفاوت است. در طي فصول مختلف اين كتاب، نحوه‌ي ايجاد يك سايت مديريت پروژه به همراه كليه فرم‌ها، گردش‌هاي كاري و گزارشات مرتبط به صورت قدم به قدم، با تصاوير و توضيحات لازم بيان شده است.


ليست فصول مختلف اين كتاب به شرح زير است :
Chapter 1: Introduction
Chapter 2: Collecting Requirements
Chapter 3: Processing Incoming E-mail
Chapter 4: Managing Requirements
Chapter 5: Supporting Discussions
Chapter 6: User Stories
Chapter 7: Project Backlog
Chapter 8: Iteration Backlog
Chapter 9: Burndown Charts

Chapter 10: Getting Organized
Chapter 11: Creating Test Cases
Chapter 12: Reporting Defects
Chapter 13: Testing Metrics
Chapter 14: Workflow Tasks
Chapter 15: State Machine Workflows
Chapter 16: Creating Custom Forms

براي نمونه هدف از فصل user stories آن رسيدن به فرمي شبيه به فرم زير و به گردش انداختن آن بدون حتي يك سطر برنامه نويسي است:




در حاشيه!
كلا يكي از اهداف مهم شيرپوينت بيكار كردن برنامه نويس‌هاي ASP.NET و سپردن كار آن‌ها به business analyst ها است و مايكروسافت در اين زمينه بسيار موفق عمل كرده است! (البته اين را هم داخل پرانتز عرض كنم كه براي راه اندازي و نگهداري شيرپوينت حتما نياز به يك PHD از مايكروسافت خواهيد داشت. اگر باور نداريد فقط يكبار چندماهي آزمايش كنيد! به همين دليل است كه هنوز برنامه نويس‌هاي ASP.NET منقرض نشده‌اند!)


۱۳۸۹/۰۹/۱۳

معرفي برنامه‌ي Subtitle Tools


اين روزها زيرنويس‌هاي فارسي فيلم‌هاي روز دنيا را راحت مي‌شود در اينترنت يافت، اما مشكلات زيادي هم به همراه اين نوع فايل‌ها وجود دارند:
- گاها با فيلم دريافت شده هماهنگ نيستند.
- عموما با فرمت windows-1256 تهيه مي‌شوند كه براي استفاده از آن‌ها در سيستم‌هاي مختلف بهتر است به UTF8 تبديل شوند.
- اكثر برنامه‌هاي موجود براي كار با زير نويس‌ها و ويرايش آن‌ها، دركي از يونيكد ندارند.
- عموما نياز است جهت استفاده از آن‌ها در يك جمع، تعدادي از سطور آن‌ها را با حفظ شماره بندي فايل، حذف كرد!
و ...

به همين جهت نياز به يك برنامه‌ي جمع و جور جهت كار با زير نويس‌ها داشتم كه نتيجه‌ي آن تهيه‌ي برنامه‌ي زير شد:

الف) تغيير encoding فايل دريافتي به UTF-8
هنگام گشودن اكثر فايل‌هاي زير نويس فارسي با تصوير زير روبرو خواهيد شد:



براي تبديل آن به فرمت يونيكد تنها كافي است بر روي دكمه‌ي To UTF-8 كليك كنيد. در اين حالت نتيجه به صورت زير خواهد بود:



ب) هماهنگ سازي زير نويس با فيلم دريافتي
ممكن است ديالوگ‌هاي زير نويس دريافتي شما به اندازه‌ي مدت زمان خاصي، از ديالوگ‌هاي فيلم در حال پخش، عقب‌تر يا جلوتر باشند. در اين حالت ابتدا بايد اين مدت زمان را با گوش فرا دادن به ابتداي فيلم و مقايسه‌ي آن با اولين شماره‌ي زيرنويس، پيدا كنيد. مثلا پس از بررسي متوجه شده‌ايد كه به كليه زمان‌هاي زيرنويس جاري بايد 7 ثانيه اضافه گردد. اينكار را مطابق تصوير ذيل مي‌توان انجام داد:



ج) يكي كردن زمان‌هاي دو زير نويس با هم
گاهي از اوقات مشكلات زماني يك زيرنويس وخيم‌تر از مورد ب است. به اين معنا كه شماره‌هاي مختلف آن، زمان‌هاي كاملا بي‌ربطي داشته و به صورت هماهنگ، قابل افزايش يا كاهش نيستند. در اين حالت ابتدا زير نويس مورد نظر را در برنامه باز كنيد. سپس يك زير نويس خوب انگليسي هماهنگ با فيلم را نيز پيدا كنيد و از طريق دكمه‌ي Merge times به برنامه معرفي نمائيد. در اين حالت به صورت خودكار زمان‌هاي صحيح از زير نويس دوم به زير نويس اول منتقل خواهند شد.

د) يكي كردن دو فايل زير نويس با هم
ممكن است فيلم دريافتي تنها از يك فايل تشكيل شده باشد، اما شما فقط توانسته‌ايد زير نويس مرتبط با نگارش دو سي دي اين فيلم را پيدا كنيد. بنابراين نياز است تا اين دو فايل زير نويس يكي شوند با اين شرط كه زمان‌هاي سي دي دوم از انتهاي سي دي اول در فايل نهايي يكي شده‌ي توليدي شروع گردد.
براي اين منظور ابتدا فايل زيرنويس سي دي اول را در برنامه باز كرده و سپس به گزينه‌ي join files در برنامه مراجعه كنيد. در اينجا ابتدا مسير فايل زيرنويس سي دي 2 را مشخص نمائيد. برنامه سعي خواهد كرد تا زمان آغاز قسمت دوم را بر اساس آخرين زمان سي دي اول و اولين زمان سي دي دوم حدس بزند و نمايش دهد. يا مي‌توانيد اين مقدار پيش فرض را پذيرفته و بر روي دكمه‌ي join كليك نمائيد و يا امكان تغيير دستي آن هم ميسر است.



ه) حذف و يا ويرايش رديف‌ها
به دلايل نامشخصي اشخاصي كه سعي در ترجمه‌ي زيرنويس‌ها مي‌كنند عموما علاقمندند كه به متن اصلي وفادار بمانند. به همين دليل نياز به جرح و تعديل زيرنويس‌هاي تهيه شده براي نمايش در يك جمع خانوادگي وجود دارد. گزينه‌ي جستجوي موجود در بالاي رديف‌هاي زيرنويس باز شده در برنامه، امكان نمايش رديف‌هايي را كه حاوي متن وارد شده است، به صورت خودكار دارد.



در اين حالت اگر نياز است سطري حذف شود، آن‌را انتخاب نموده و بر روي دكمه‌ي Delete row كليك نمائيد. در اين حالت علاوه بر حذف رديف، كليه شماره‌هاي موجود در زير نويس نيز به صورت خودكار مجددا توليد و مرتب خواهند شد. (كليك راست بر روي هر رديف نيز اين گزينه‌ را نمايش مي‌دهد)
يا اگر مشغول به ويرايش متني شديد، پس از ويرايش، كليك كردن بر روي دكمه‌ي Save را فراموش نكنيد (در حالت حذف نيازي به اينكار نيست).

ز) پيدا كردن زيرنويس يك فايل بر اساس امضاي ديجيتال آن
سايت opensubtitles.org يك API را جهت پيدا كردن زيرنويسي مطابق با هش يك فايل ويديويي ارائه داده است، كه در برنامه‌ي جاري، كلاينتي براي آن تهيه شده است:



فقط كافي است فايل ويديويي خود را در اين قسمت انتخاب نمائيد. برنامه هش فايل را محاسبه كرده و سپس با كمك سرويس XML-RPC سايت opensubtitles.org سعي در يافتن زيرنويس هماهنگ با آن خواهد كرد. در اينجا ديگر مهم نيست نام فايل انتخابي چيست؛ امضاي ديجيتال آن مهم است. براي دريافت موارد مورد نظر، ابتدا آن‌ها را تيك زده و سپس بر روي دكمه‌ي دريافت كليك كنيد. كليك راست بر روي رديف مورد نظر نيز اين امكان دريافت را لحاظ كرده است.
همچنين قسمتي هم براي آپلود زيرنويس به اين سايت پيش بيني شده است (لطفا مصرف كننده‌ي صرف يا به قولي ليچر نباشيد!)



در اينجا انتخاب فايل ويديويي، فايل زيرنويس هماهنگ با آن و همچنين زبان زير نويس الزامي است. از فايل ويديويي جهت محاسبه‌ي هش آن براي يافتن ساده‌تر زيرنويس‌ها در دفعات آتي استفاده مي‌گردد.

پيشنياز استفاده از اين برنامه، نصب دات نت فريم ورك 4 است كه اگر از ويندوز 7 استفاده مي‌كنيد، جزو به روز رساني‌هاي اختياري آن است و در حالت كلي نسخه‌ي كامل 32 بيتي و 64 بيتي آن از اين آدرس قابل دريافت است.


۱۳۸۹/۰۹/۱۰

انجمن سايت LLBLGEN سورس باز شد


LLBLGEN يكي از ORM هاي تجاري بسيار با كيفيت دات نت است و علاوه بر اينكه هويت مستقلي دارد، امكان توليد كدهاي مخصوص Entity framework و NHibernate را هم دارا است.
اخيرا اين شركت تصميم گرفته است كه سيستم پشتيباني مشتريان خودش را كه نمونه‌اي از آن‌را در اينجا مي‌توانيد ملاحظه كنيد، سورس باز كند.
حداقل پيشنيازهاي نصب اين انجمن به شرح زير است:
- ASP.NET 2.0+
- SQL Server 2000 or higher / CE Desktop 3.5
- NET 3.5+
لطفا جهت دريافت آن به اين آدرس و جهت ملاحظه‌ي قابليت‌هاي آن به اين آدرس مراجعه نمائيد.

۱۳۸۹/۰۹/۰۴

ويديوهاي آموزشي اسكرام


تعدادي ويديوي آموزشي رايگان مربوط به اسكرام را كه از يوتيوب جمع آوري شده‌اند، مي‌توانيد از يكي از لينك‌هاي زير دريافت كنيد (تمام لينك‌ها ختم به يك فايل هستند):

+ ، + ، + ، + ، + ، +

اين مجموعه شامل موارد زير است:



۱۳۸۹/۰۹/۰۲

راه‌هاي تامين Product backlog در تيم‌هاي مايكروسافت


شايد مهم‌ترين رخداد وبلاگ‌هاي مرتبط با برنامه نويسي ايراني در نيمه دوم سال 89، انتشار كتابچه اسكرام و XP ساده شده به زبان فارسي باشد. يكي از فصول اين كتابچه، به روش‌هاي تهيه Product backlog اختصاص دارد كه جزو قسمت‌هاي اوليه پروسه اسكرام است و مي‌شود به آن يك to-do list الويت بندي شده هم گفت. تيم‌هاي مايكروسافت هم به نظر كمابيش بر همين اساس مديريت مي‌شوند. در ادامه ليستي از سايت‌هايي را مشاهده خواهيد كرد كه اين تيم‌هاي گوناگون درون مايكروسافت از آن‌ها جهت تامين product backlog خود استفاده مي‌كنند؛ كاربران (كه در اينجا همان برنامه نويس‌ها هستند) با مراجعه به اين سايت‌ها نيازهاي خود را عنوان كرده و همچنين با وجود امكانات راي دهي، امكان تهيه ليست‌هايي اولويت بندي شده هم وجود دارد:




و ...


تيم‌هاي خارج از مايكروسافت هم از اين ايده استفاده مي‌كنند؛ مانند:

۱۳۸۹/۰۸/۳۰

تغييرات دسترسي به كدها در دات نت 4


دو پروژه‌ي سورس باز XML RPC و Log4Net براي اجرا شدن در برنامه‌هاي دات نت 4 نياز به اندكي تغيير در هر دو برنامه‌ي فراخوان و اسمبلي‌هاي آن‌ها دارند كه در ادامه توضيحات مربوطه ارائه خواهند شد.

اگر يك پروژه‌ي جديد دات نت 4 را آغاز كنيد و سپس ارجاعي را به يكي از اسمبلي‌هاي ذكر شده اضافه نمائيد، اولين خطايي را كه حين استفاده مشاهده خواهيد نمود، مورد زير است:
Could not resolve assembly "System.Web".
The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client".
Please remove references to assemblies not in the targeted framework or consider retargeting your project.
علت هم اينجا است كه در تنظيمات پروژه‌ها‌ي جديد مبتني بر دات نت 4، پيش فرض Target framework انتخابي توسط VS.NET 2010 از نوع Client profile است؛ كه صرفا جهت كاهش حجم دات نت فريم ورك مورد نياز اين نوع برنامه‌ها طراحي شده است. در اين پروفايل ساده شده، اسمبلي System.Web وجود ندارد. بنابراين جهت استفاده از كتابخانه‌هاي XML RPC و يا Log4Net نياز است تا در خواص پروژه، Target framework را بر روي دات نت فريم ورك 4 كامل قرار داد تا خطاي فوق برطرف شود.

خطاي دومي كه حين كار با كتابخانه‌هاي XML RPC و يا Log4Net در يك برنامه‌ي دات نت 4 حتما با آن مواجه خواهيد شد در ادامه ذكر گرديده است:
Inheritance security rules violated while overriding member:
GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext),
Security accessibility of the overriding method must match the security accessibility of the method being overriden.
متد ISerializable.GetObjectData با ويژگي SecurityCritical در دات نت فريم ورك مزين شده است. با تغييرات امنيتي صورت گرفته در دات نت 4، متدي كه اين متد را تحريف مي‌كند نيز بايد با همان سطح دسترسي متد virtual اصلي معرفي گردد و گرنه برنامه اجرا نخواهد شد. البته اين مشكل ما نيست؛ مشكل سازندگان كتابخانه‌هاي ذكر شده است! ولي خوب تا اين لحظه براي مثال كتابخانه XML RPC براي دات نت 4 به روز نشده است ولي سورس كامل آن در دسترس است.
براي رفع اين مشكل ابتدا سورس اين كتابخانه‌ها را دريافت كرده و سپس در فايل AssemblyInfo.cs آن‌ها يك سطر زير را حذف نموده و پروژه را مجددا كامپايل كنيد:
[assembly: AllowPartiallyTrustedCallers]
علت وجود اين ويژگي‌ در كتابخانه‌هاي ذكر شده اين است كه بتوان از آن‌ها در محيط‌هاي اصطلاحا partially trusted (براي مثال هر برنامه‌اي كه در internet zone يا intranet zone اجرا مي‌شود) استفاده كرد. در دات نت 4 با تغييرات انجام شده معناي AllowPartiallyTrustedCallers به security-transparency تغيير كرده است. بنابراين با قيد آن يا بايد هر جايي كه متد GetObjectData ذكر شده در اين كتابخانه‌ها تحريف مي‌شود، ويژگي SecurityCritical را صريحا اعمال كرد يا اينكه مي‌توان AllowPartiallyTrustedCallers را حذف كرده و وظيفه‌ي انجام آن‌را به CLR محول نمود.


براي مطالعه بيشتر:
Using Libraries from Partially Trusted Code
Security Changes in the .NET Framework 4
TypeLoadException based on Security-Transparent Code, Level 2
Making log4net run on .NET 4.0

۱۳۸۹/۰۸/۲۷

مديريت كار تيمي با SQL Server


پس از انتشار جزوه‌ي SVN در حدود دو سال قبل، ايميل در اين مورد زياد داشتم. يكي از سؤالات هم اين بود كه: "چگونه از SVN جهت مديريت نگارش‌هاي مختلف يك بانك اطلاعاتي اس كيوال سرور در يك تيم استفاده كنيم؟ (منظور مديريت schema است)" و من هم پاسخ مناسبي براي اين مورد نداشتم چون كلاينت‌هاي SVN حداقل با Management studio يكپارچه نمي‌شود (بر خلاف ابزارهاي موجود براي VS.NET مانند VisualSVN ، AnkhSVN و غيره). صد البته مي‌شود از آن همانند اعمال نگارش به يك فايل Text معمولي مانند فايل‌هاي SQL استفاده كرد، اما خوب ...

و خبر خوب اينكه شركت معظم RedGate چند روز قبل يك كتاب رايگان را در اين مورد منتشر كرده است:



سرفصل‌هاي اين كتاب
Chapter 1: Writing Readable SQL
Chapter 2: Documenting your Database
Chapter 3: Change Management and Source Control
Chapter 4: Managing Deployments
Chapter 5: Testing Databases
Chapter 6: Reusing T-SQL Code
Chapter 7: Maintaining a Code Library
Chapter 8: Exploring your Database Schema
Chapter 9: Searching DDL and Build Scripts
Chapter 10: Automating CRUD
Chapter 11: SQL Refactoring

دريافت

۱۳۸۹/۰۸/۲۲

نحوه‌ي صحيح فراخواني SQL Aggregate Functions حين استفاده از LINQ - قسمت دوم


در قسمت قبل در مورد حالتي كه كوئري انجام شده نتيجه‌اي را بر نگردانده است، بحث شد. در اين قسمت يكي از شايع‌ترين مشكلات حين كار با تابع Sum بررسي خواهد شد.

ابتدا جدول ساده Transactions را با دو فيلد Id و Amount مطابق تصوير زير در نظر بگيريد:


تعدادي ركورد در اين جدول ثبت شده‌اند. اكنون مي‌خواهيم جمع آن‌ها را محاسبه كنيم:


همانطور كه ملاحظه مي‌نمائيد اين عمليات ميسر نيست، زيرا حاصل نهايي فراتر از بازه‌ي تعريف شده‌ي Int32 است.
براي رفع اين مشكل بايد Amount را تبديل به BigInt (براي مثال مرتبط با نگارش‌هاي مختلف SQL Server) كرد. مطابق توضيحات قسمت قبل، اين عمليات casting بايد به lambda expression تعريف شده اعمال گردد، زيرا خروجي Sum بر مبناي آن تعيين مي‌گردد.



در اين حالت خروجي SQL آن نيز به صورت زير در خواهد آمد:



هر چند اين مباحث ساده به نظر مي‌رسند ولي در صورت عدم رعايت سبب سرخ و سفيد شدن در هنگام مقتضي خواهند گرديد.

۱۳۸۹/۰۸/۱۹

مديريت ساده‌تر امور Async (غير همزمان) در نسخه‌ي بعدي زبان‌هاي دات نتي


چندي قبل مطلبي را در اين سايت در مورد معرفي الگويي كه توسط آن مي‌توان اعمال غير همزمان را به صورت پي در پي انجام داد، مطالعه كرديد:

و بحث اصلي مطالب فوق هم اين است:
"در برنامه نويسي متداول هميشه عادت داريم كه اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممكن است ابتدا C انجام شود، سپس A و بعد B يا هر حالت ديگري صرفنظر از تقدم و تاخر آن‌ها در حين معرفي متدهاي مرتبط در يك قطعه كد. همچنين ميزان خوانايي اين نوع كدنويسي نيز مطلوب نيست...."

خبر خوش آن است كه پشتيباني از اين نوع مدل پي در پي برنامه نويسي در نگارش‌هاي بعدي سي شارپ و VB.NET اضافه شده است.



ليستي از مقالات منتشر شده در اين مورد را در ادامه ملاحظه خواهيد كرد:

علاوه بر آن يك سري ويديوي مرتبط با اين بحث نيز منتشر شده است:

۱۳۸۹/۰۸/۱۸

آدرس‌ها و ابزارهايي جهت سهولت دريافت ويديوهاي PDC 2010


اخيرا دو برنامه جهت دريافت ساده‌تر فايل‌هاي PDC 2010 با سيلورلايت تهيه شده‌اند كه بر اساس قابليت اجراي خارج از مرورگر آن (OOB=Out Of Browser) طراحي و پياده سازي شده‌اند:
برنامه‌ي آقاي Mike Taulty سورس باز بوده و بر اساس الگوي MVVM پياده سازي شده است.


علاوه بر آن يك ليست ديگر نيز در اين زمينه وجود دارد:

۱۳۸۹/۰۸/۱۳

نحوه‌ي صحيح فراخواني SQL Aggregate Functions حين استفاده از LINQ


SQL Aggregate Functions كه مد نظر شما هستند مانند Min ، Max ، Sum و امثال آن. بحث LINQ هم زمانيكه از الگوي Repository استفاده شود مستقل از نوع ORM مورد نظر خواهد شد؛ بنابراين در اينجا مقصود از LINQ مي‌تواند LINQ to SQL ، LINQ to Entities ، LINQ to NHibernate و كلا هر نوع ORM ديگري با پشتيباني از LINQ باشد.
صورت مساله هم اين است: آيا نوشتن عبارت LINQ ايي به شكل زير صحيح است؟
decimal amount = respository.Transactions
.Where(t=>t.TransactionDate>new DateTime(2010,10,13))
.Sum(t=>t.Amount);
پاسخ: خير!
توضيحات:
عبارت LINQ فوق در نهايت به شكل زير ترجمه خواهد شد:
-- Region Parameters
-- @p0: DateTime [2010/10/13 12:00:00 ق.ظ]
-- EndRegion
SELECT SUM([t0].[Amount]) AS [value]
FROM [Transactions] AS [t0]
WHERE [t0].[TransactionDate] > @p0
و اتفاقا در اين سيستم پس از تاريخ 2010/10/13 هيچ تراكنشي ثبت نشده است؛ بنابراين خروجي اين كوئري null خواهد بود و نه صفر. همينجا است كه يكي از استثناهاي زير صادر شده و ادامه‌ي برنامه با مشكل مواجه خواهد شد:
- System.InvalidOperationException: The cast to value type 'decimal' failed because the materialized value is null.
- InvalidOperationException: The null value cannot be assigned to a member with type decimal which is a non-nullable value type.

مشكل هم از اينجا ناشي مي‌شود كه متغييري از نوع deciaml يا int و امثال آن، مقدار دريافتي نال را نمي‌پذيرند. براي رفع اين مشكل بايد عبارت LINQ فوق به صورت زير بازنويسي شود (و اهميتي هم ندارد كه Sum است يا Max يا Avg و غيره؛ در مورد بكارگيري تمام SQL Aggregate Functions در يك عبارت LINQ ، اين مورد بايد لحاظ گردد):
decimal amount = respository.Transactions
.Where(t=>t.TransactionDate>new DateTime(2010,10,13))
.Sum(t=>(decimal?)t.Amount)??0;

دقيقا به همين علت است كه در دات نت، nullable types تعريف شده‌اند. امكان ذخيره سازي null‌ در يك متغير براي مثال از نوع decimal وجود ندارد اما نوع decimal? (و يا Nullable<decimal> به بياني ديگر) اين قابليت را دارد.
شايد بگوئيد كه در اينجا با تغيير تعريف متغير به decimal? amount مشكل حل مي‌شود، اما خير. تعريف extension method مربوط به sum به صورت زير است:

public static TResult Sum<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector)

در اين تعريف به TResult دقت نمائيد؛ هم بيانگر نوع خروجي نهايي متد و هم مشخص سازنده‌ي نوع پارامتري است كه خروجي Lambda Expression را تشكيل مي‌دهد. به اين معنا كه سي شارپ، TResult را از lambda expression دريافت كرده و خروجي Sum را بر همان مبنا و نوع تشكيل مي‌دهد. بنابراين براي دريافت خروجي nullable بايد TResult ايي nullable را همانند مثال فوق ايجاد كنيم.

خلاصه بحث:
اگر در كدهاي LINQ خود كه با بانك اطلاعاتي سر و كار دارند از معادل‌هاي SQL Aggregate Functions استفاده كرده‌ايد، آن‌ها را يافته و نكته‌ي nullable TResult فوق را به آن‌ها اعمال كنيد؛ در غير اينصورت منتظر باشيد تا روزي برنامه شما به سادگي كرش كند.


۱۳۸۹/۰۸/۱۰

استفاده از اسمبلي‌هاي دات نت 2 در يك پروژه دات نت 4


تنظيمات برنامه BloggerToChm را به دات نت 4 تغيير دادم و بدون مشكل كامپايل شد. اما حين اجرا بلافاصله با خطاي زير برنامه اجرا نمي‌شد:

Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.
مشكل هم از اسمبلي‌هاي مرتبط با SQLite است كه هنوز براي دات نت 4 كامپايل نشده‌اند. براي رفع اين مشكل بايد تغيير زير را (تنظيم گزينه useLegacyV2RuntimeActivationPolicy) به فايل app.config برنامه اضافه كرد:
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
براي برنامه‌هاي ASP.NET نيز به همين صورت است. در آنجا اين تغييرات بايد به Web.Config اضافه شوند.

۱۳۸۹/۰۸/۰۷

تفاوت بين IQueryable و IEnumerable در حين كار با ORMs


متد زير را كه يكي از اشتباهات رايج حين استفاده از LINQ خصوصا جهت Binding اطلاعات است، در نظر بگيريد:
IQueryable<Customer> GetCustomers()

اين متد در حقيقت هيچ چيزي را Get نمي‌كند! نام اصلي آن GetQueryableCustomers و يا GetQueryObjectForCustomersاست.
IQueryable قلب LINQ است و تنها بيانگر يك عبارت (expression) از ركوردهايي مي‌باشد كه مد نظر شما است و نه بيشتر.
IQueryable<Customer> youngCustomers = repo.GetCustomers().Where(m => m.Age < 15);
براي مثال زمانيكه يك IQueryable را همانند مثال فوق فيلتر مي‌كنيد نيز هنوز چيزي از بانك اطلاعاتي يا منبع داده‌اي دريافت نشده است. هنوز هيچ اتفاقي رخ نداده است و هنوز رفت و برگشتي به منبع داده‌اي صورت نگرفته است.
به آن بايد به شكل يك expression builder نگاه كرد و نه ليستي از اشياء فيلتر شده‌ي ما. به اين مفهوم، deferred execution (اجراي به تاخير افتاده) نيز گفته مي‌شود (بايد دقت داشت كه IQueryable هم يك نوع IEnumerable است به علاوه expression trees كه مهم‌ترين وجه تمايز آن نيز مي‌باشد).
براي مثال در عبارت زير تنها در زمانيكه متد ToList فراخواني مي‌شود، كل عبارت LINQ ساخته شده، به عبارت SQL متناظر با آن ترجمه شده، اطلاعات از ديتابيس اخذ گرديده و حاصل به صورت يك ليست بازگشت داده مي‌شود:
IList<Competitor> competitorRecords =  competitorRepository
.Competitors
.Where(m => !m.Deleted)
.OrderBy(m => m.countryId)
.ToList(); //فقط اينجا است كه اس كيوال نهايي توليد مي‌شود

در مورد IEnumerable ها چطور؟
IEnumerable<Product> products = repository.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
دو سطر فوق به اين معنا است:
لطفا ابتدا به بانك اطلاعاتي رجوع كن و تمام ركوردهاي محصولات موجود را بازگشت بده. سپس بر روي اين حجم بالاي اطلاعات، محصولاتي را كه قيمت بالاي 25 دارند، فيلتر كن.

اگر همين دو سطر را با IQueryable بازنويسي كنيم چطور؟
 IQueryable<Product> products = repository.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
در سطر اول تنها يك عبارت LINQ ساخته شده است و بس. در سطر دوم نيز به همين صورت. در طي اين دو سطر حتي يك رفت و برگشت به بانك اطلاعاتي صورت نخواهد گرفت. در ادامه اگر اين اطلاعات به نحوي Select شوند (يا ToList فراخواني شود، يا در طي يك حلقه براي مثال Iteration ايي روي اين حاصل صورت گيرد يا موارد مشابه ديگر)، آنگاه كوئري SQL متناظر با عبارت LINQ فوق ساخته شده و بر روي بانك اطلاعاتي اجرا خواهد شد.
بديهي است اين روش منابع كمتري را نسبت به حالتي كه تمام اطلاعات ابتدا دريافت شده و سپس فيلتر مي‌شوند، مصرف مي‌كند (حالت بازگشت تمام اطلاعات ممكن است شامل 20000 ركورد باشد، اما حالت دوم شايد فقط 5 ركورد را بازگشت دهد).

سؤال: پس IQueryable بسيار عالي است و از اين پس كلا از IEnumerable ها ديگر نبايد استفاده كرد؟
خير! توصيه اكيد طراحان اين است كه لطفا تا حد امكان متدهايي كه IQueryable بازگشت مي‌دهند ايجاد نكنيد! IQueryable يعني اينكه اين نقطه‌ي آغازين كوئري در اختيار شما، بعد برو هر كاري كه دوست داشتي با آن در طي لايه‌هاي مختلف انجام بده و هر زمانيكه دوست داشتي از آن يك خروجي تهيه كن. خروجي IQueryable به معناي مشخص نبودن زمان اجراي نهايي كوئري و همچنين مبهم بودن نحوه‌ي استفاده از آن است. به همين جهت متدهايي را طراحي كنيد كه IEnumerable بازگشت مي‌دهند اما در بدنه‌ي آن‌ها به نحو صحيح و مطلوبي از IQueryable استفاده شده است. به اين صورت حد و مرز يك متد كاملا مشخص مي‌شود. متدي كه واقعا همان فيلتر كردن محصولات را انجام مي‌دهد، همان 5 ركورد را بازگشت خواهد داد؛ اما با استفاده از يك ليست يا يك IEnumerable و نه يك IQueryable كه پس از فراخواني متد نيز به هر نحو دلخواهي قابل تغيير است.

۱۳۸۹/۰۸/۰۵

Count يا Any


با وجود امكانات مهياي توسط LINQ ، يك سري از عادات متداول حين كار با گروهي از اشياء بايد كنار گذاشته شوند؛ براي مثال چگونگي بررسي اين مطلب كه آيا شيء IEnumerable ما حاوي عنصري هست يا خير.
روش متداول انجام اينكار استفاده از متد Count است. چون اين متد پيش از تدارك امكانات LINQ نيز وجود داشته، بنابراين اولين موردي كه جهت بررسي آن به ذهن خطور مي‌كند، استفاده از متد Count مي‌باشد؛ براي مثال:
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Count() > 0)
// do something...
}
اين روش بهينه نيست زيرا كار متد Count بررسي تك تك عناصر شيء IEnumerable و سپس بازگرداندن تعداد آن‌ها است. اين مورد خصوصا در حالت‌هاي كار با بانك اطلاعاتي و تنظيمات lazy-loading آن و يا تعداد بالاي عناصر يك ليست، بسيار هزينه‌بر خواهد شد.
ولي در اينجا هدف ما اين است كه آيا شيء IEnumerable داراي حداقل يك عنصر است يا خير؟ بنابراين بجاي استفاده از متد Count بهتر است از يكي از extension methods فراهم شده توسط LINQ به نام Any استفاده شود.
كار متد Any ، پس از بررسي اولين عنصر يك مجموعه، خاتمه خواهد يافت و بديهي است كه نسبت به متد Count بسيار سريعتر و كم هزينه‌تر خواهد بود. علاوه بر آن حين كار با بانك‌هاي اطلاعاتي براي مثال توسط LINQ to Entities ، در SQL نهايي توليدي به EXISTS ترجمه خواهد شد.
void Method(IEnumerable<Status> statuses)
{
if (statuses != null && statuses.Any())
// do something...
}
خلاصه‌ي بحث:
از اين پس حين استفاده از انواع و اقسام ليست‌ها، آرايه‌ها، IEnumerable ها و امثال آن‌ها، جهت بررسي خالي بودن يا نبودن آن‌ها تنها از متد Any فراهم شده توسط LINQ استفاده نمائيد.
if (myArray != null && myArray.Any())
// do something...

۱۳۸۹/۰۸/۰۱

معرفي يك ابزار گزارشگيري رايگان مخصوص WPF


تا صحبت از گزارشگيري به ميان بيايد احتمالا معرفي ابزارهاي تجاري مانند Reporting services ، كريستال ريپورت، stimulsoft.com ، fast-report.com و امثال آن درصدر ليست توصيه كنندگان و مشاوران قرار خواهند داشت. اما خوب براي ايجاد يك گزارشگيري ساده حتما نيازي نيست تا به اين نوع ابزارهاي تجاري مراجعه كرد. ابزار رايگان و سورس باز جالبي هم در اين باره جهت پروژه‌هاي WPF در دسترس است:



در ادامه در طي يك مثال قصد داريم از اين كتابخانه استفاده كنيم:

1) تنظيم وابستگي‌ها
پس از دريافت كتابخانه فوق، ارجاعات زير بايد به پروژه شما اضافه شوند:
CodeReason.Reports.dll (از پروژه فوق) و ReachFramework.dll (جزو اسمبلي‌هاي استاندارد دات نت است)

2) تهيه منبع داده‌ گزارش
كتابخانه‌ي فوق به صورت پيش فرض با DataTable‌ كار مي‌كند. بنابراين كوئري‌هاي شما يا بايد خروجي DataTable داشته باشد يا بايد از يك سري extension methods براي تبديل IEnumerable به DataTable استفاده كرد (در پروژه پيوست شده در پايان مطلب، اين موارد موجود است).
براي مثال فرض كنيد مي‌خواهيم ركوردهايي را از نوع كلاس Product زير در گزارش نمايش دهيم:

namespace WpfRptTests.Model
{
public class Product
{
public string Name { set; get; }
public int Price { set; get; }
}
}
3) تعريف گزارش
الف) اضافه كردن فايل تشكيل دهنده ساختار و ظاهر گزارش
گزارش‌‌هاي اين كتابخانه مبتني است بر اشياء FlowDocument استاندارد WPF . بنابراين از منوي پروژه گزينه‌ي Add new item در قسمت WPF آن يك FlowDocument جديد را به پروژه اضافه كنيد ( بايد دقت داشت كه Build action اين فايل بايد به Content تنظيم گردد). ساختار ابتدايي اين FlowDocument به صورت زير خواهد بود كه به آن FlowDirection و FontFamily مناسب جهت گزارشات فارسي اضافه شده است. همچنين فضاي نام مربوط به كتابخانه‌ي گزارشگيري CodeReason.Reports نيز بايد اضافه گردد.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FlowDirection="RightToLeft" FontFamily="Tahoma"
xmlns:xrd="clr-namespace:CodeReason.Reports.Document;assembly=CodeReason.Reports"
PageHeight="29.7cm" PageWidth="21cm" ColumnWidth="21cm">

</FlowDocument>

مواردي كه در ادامه ذكر خواهند شد محتواي اين گزارش را تشكيل مي‌دهند:
ب) مشخص سازي خواص گزارش

<xrd:ReportProperties>
<xrd:ReportProperties.ReportName>SimpleReport</xrd:ReportProperties.ReportName>
<xrd:ReportProperties.ReportTitle>گزارش از محصولات</xrd:ReportProperties.ReportTitle>
</xrd:ReportProperties>
در اينجا ReportName و ReportTitle بايد مقدار دهي شوند (دو dependency property كه در كتابخانه‌ي CodeReason.Reports تعريف شده‌اند)

ج) مشخص سازي Page Header و Page Footer
اگر مي‌خواهيد عباراتي در بالا و پايين تمام صفحات گزارش تكرار شوند مي‌توان از SectionReportHeader و SectionReportFooter اين كتابخانه به صورت زير استفاده كرد:
    <xrd:SectionReportHeader PageHeaderHeight="2" Padding="10,10,10,0" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportHeader>

<xrd:SectionReportFooter PageFooterHeight="2" Padding="10,0,10,10" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
نام كاربر:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
صفحه
<xrd:InlineContextValue PropertyName="PageNumber" FontWeight="Bold" /> از
<xrd:InlineContextValue PropertyName="PageCount" FontWeight="Bold" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportFooter>

دو نكته در اينجا حائز اهميت هستند: xrd:InlineDocumentValue و xrd:InlineContextValue
InlineDocumentValue را مي‌توان در كد‌هاي برنامه به صورت سفارشي اضافه كرد. بنابراين هر جايي كه نياز بود مقدار ثابتي از طريق كد نويسي به گزارش تزريق و اضافه شود مي‌توان از InlineDocumentValue استفاده كرد. براي مثال در كدهاي ViewModel برنامه كه در ادامه ذكر خواهد شد دو مقدار PrintDate و RptBy به صورت زير تعريف و مقدار دهي شده‌اند:
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحيد");
براي مشاهده مقادير مجاز مربوط به InlineContextValue به فايل ReportContextValueType.cs سورس كتابخانه مراجعه كنيد كه شامل PageNumber, PageCount, ReportName, ReportTitle است و توسط CodeReason.Reports به صورت پويا تنظيم خواهد شد.

د) مشخص سازي ساختار توليدي گزارش

<Section Padding="80,10,40,10" FontSize="12">
<Paragraph FontSize="24" TextAlignment="Center" FontWeight="Bold">
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
<Paragraph TextAlignment="Center">
گزارش از ليست محصولات در تاريخ:
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
توسط:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
<xrd:SectionDataGroup DataGroupName="ItemList">
<Table CellSpacing="0" BorderBrush="Black" BorderThickness="0.02cm">
<Table.Resources>
<!-- Style for header/footer rows. -->
<Style x:Key="headerFooterRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Background" Value="LightGray"/>
</Style>

<!-- Style for data rows. -->
<Style x:Key="dataRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontSize" Value="12"/>
</Style>

<!-- Style for data cells. -->
<Style TargetType="{x:Type TableCell}">
<Setter Property="Padding" Value="0.1cm"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="0.01cm"/>
</Style>
</Table.Resources>

<Table.Columns>
<TableColumn Width="0.8*" />
<TableColumn Width="0.2*" />
</Table.Columns>
<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>نام محصول</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>قيمت</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

<TableRowGroup Style="{StaticResource dataRowStyle}">
<xrd:TableRowForDataTable TableName="Product">
<TableCell>
<Paragraph>
<xrd:InlineTableCellValue PropertyName="Name" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<xrd:InlineTableCellValue PropertyName="Price" AggregateGroup="Group1" />
</Paragraph>
</TableCell>
</xrd:TableRowForDataTable>
</TableRowGroup>

<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Right">
<Bold>جمع كل</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" />
</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

</Table>

<Paragraph TextAlignment="Center" Margin="5">
در اين گزارش
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Count"
EmptyValue="هيچ"
FontWeight="Bold" /> محصول با جمع كل قيمت
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" /> وجود دارند.
</Paragraph>
</xrd:SectionDataGroup>
</Section>
براي اينكه بتوان اين قسمت‌ها را بهتر توضيح داد، نياز است تا تصاوير مربوط به خروجي اين گزارش نيز ارائه شوند:




در ابتدا توسط دو پاراگراف، عنوان گزارش و يك سطر زير آن نمايش داده شده‌اند. بديهي است هر نوع شيء و فرمت مجاز در FlowDocument را مي‌توان در اين قسمت نيز قرار داد.
سپس يك SectionDataGroup جهت نمايش ليست آيتم‌ها اضافه شده و داخل آن يك جدول كه بيانگر ساختار جدول نمايش ركوردهاي گزارش مي‌باشد، ايجاد گرديده است.
سه TableRowGroup در اين جدول تعريف شده‌اند.
TableRowGroup هاي اولي و آخري دو سطر اول و آخر جدول گزارش را مشخص مي‌كنند (سطر عناوين ستون‌ها در ابتدا و سطر جمع كل در پايان گزارش)
از TableRowGroup مياني براي نمايش ركوردهاي مرتبط با نام جدول مورد گزارشگيري استفاده شده است. توسط TableRowForDataTable آن نام اين جدول بايد مشخص شود كه در اينجا همان نام كلاس مدل برنامه است. به كمك InlineTableCellValue، خاصيت‌هايي از اين كلاس را كه نياز است در گزارش حضور داشته باشند، ذكر خواهيم كرد. نكته‌ي مهم آن AggregateGroup ذكر شده است. توسط آن مي‌توان اعمال جمع، محاسبه تعداد، حداقل و حداكثر و امثال آن‌را كه در فايل InlineAggregateValue.cs سورس كتابخانه ذكر شده‌اند، به فيلدهاي مورد نظر اعمال كرد. براي مثال مي‌خواهيم جمع كل قيمت را در پايان گزارش نمايش دهيم به همين جهت نياز بود تا يك AggregateGroup را براي اين منظور تعريف كنيم.
از اين AggregateGroup در سومين TableRowGroup تعريف شده به كمك xrd:InlineAggregateValue جهت نمايش جمع نهايي استفاده شده است.
همچنين اگر نياز بود در پايان گزارش اطلاعات بيشتري نيز نمايش داده شود به سادگي مي‌توان با تعريف يك پاراگراف جديد، اطلاعات مورد نظر را نمايش داد.

4) نمايش گزارش تهيه شده
نمايش اين گزارش بسيار ساده است. View برنامه به صورت زير خواهد بود:
<Window x:Class="WpfRptTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeReason.Reports.Controls;assembly=CodeReason.Reports"
xmlns:vm="clr-namespace:WpfRptTests.ViewModel"
Title="MainWindow" WindowState="Maximized" Height="350" Width="525">
<Window.Resources>
<vm:ProductViewModel x:Key="vmProductViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmProductViewModel}}">
<c:BusyDecorator IsBusyIndicatorHidden="{Binding RptGuiModel.IsBusyIndicatorHidden}">
<DocumentViewer Document="{Binding RptGuiModel.Document}" />
</c:BusyDecorator>
</Grid>
</Window>

تعريف ابتدايي RptGuiModel به صورت زير است (جهت مشخص سازي مقادير IsBusyIndicatorHidden و Document در حين بايندينگ اطلاعات):

using System.ComponentModel;
using System.Windows.Documents;

namespace WpfRptTests.Model
{
public class RptGuiModel
{
public IDocumentPaginatorSource Document { get; set; }
public bool IsBusyIndicatorHidden { get; set; }
}
}
و اين View اطلاعات خود را از ViewModel زير دريافت خواهد نمود:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using CodeReason.Reports;
using WpfRptTests.Helper;
using WpfRptTests.Model;

namespace WpfRptTests.ViewModel
{
public class ProductViewModel
{
#region Constructors (1)

public ProductViewModel()
{
RptGuiModel = new RptGuiModel();
if (Stat.IsInDesignMode) return;
//انجام عمليات نمايش گزارش در يك ترد ديگر جهت قفل نشدن ترد اصلي برنامه
showReportAsync();
}

#endregion Constructors

#region Properties (1)

public RptGuiModel RptGuiModel { set; get; }

#endregion Properties

#region Methods (3)

// Private Methods (3)

private static List<Product> getProducts()
{
var products = new List<Product>();
for (var i = 0; i < 100; i++)
products.Add(new Product { Name = string.Format("Product{0}", i), Price = i });

return products;
}

private void showReport()
{
try
{
//Show BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = false;

var reportDocument =
new ReportDocument
{
XamlData = File.ReadAllText(@"Report\SimpleReport.xaml"),
XamlImagePath = Path.Combine(Environment.CurrentDirectory, @"Report\")
};

var data = new ReportData();

// تعريف متغيرهاي دلخواه و مقدار دهي آن‌ها
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحيد");

// استفاده از يك سري اطلاعات آزمايشي به عنوان منبع داده
data.DataTables.Add(getProducts().ToDataTable());

var xps = reportDocument.CreateXpsDocument(data);
//انقياد آن به صورت غير همزمان در ترد اصلي برنامه
DispatcherHelper.DispatchAction(
() => RptGuiModel.Document = xps.GetFixedDocumentSequence()
);
}
catch (Exception ex)
{
//وجود اين مورد ضروري است زيرا بروز استثناء در يك ترد به معناي خاتمه آني برنامه است
//todo: log errors
}
finally
{
//Hide BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = true;
}
}

private void showReportAsync()
{
var thread = new Thread(showReport);
thread.SetApartmentState(ApartmentState.STA); //for DocumentViewer
thread.Start();
}

#endregion Methods
}
}

توضيحات:
براي اينكه حين نمايش گزارش، ترد اصلي برنامه قفل نشود، از ترد استفاده شد و استفاده ترد به همراه DocumentViewer كمي نكته دار است:
- ترد تعريف شده بايد از نوع STA باشد كه در متد showReportAsync مشخص شده است.
- حين باينديگ Document توليد شده توسط كتابخانه‌ي گزارشگيري به خاصيت Document كنترل، حتما بايد كل عمليات در ترد اصلي برنامه صورت گيرد كه سورس كلاس DispatcherHelper را در فايل پيوست خواهيد يافت.

كل عمليات اين ViewModel در متد showReport رخ مي‌دهد، ابتدا فايل گزارش بارگذاري مي‌شود، سپس متغيرهاي سفارشي مورد نظر تعريف و مقدار دهي خواهند شد. در ادامه يك سري داده آزمايشي توليد و به DataTables گزارش ساز اضافه مي‌شوند. در پايان XPS Document متناظر آن توليد شده و به كنترل نمايشي برنامه بايند خواهد شد.

دريافت سورس اين مثال