۱۳۸۹/۰۸/۰۷

تفاوت بين 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 متناظر آن توليد شده و به كنترل نمايشي برنامه بايند خواهد شد.

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

۱۳۸۹/۰۷/۲۶

پايان پروژه ASP.NET Ajax Control Toolkit !


بله! همانطور كه حدس زده مي‌شد بالاخره مايكروسافت تكليف خودش را با كتابخانه‌هاي Ajax ايي توليد شده در طي اين چند سال مشخص كرد و از اين پس انتخاب اصلي جهت توليد برنامه‌هاي ASP.NET مبتني بر Ajax ، تنها jQuery است.
اصل مطلب رو مي‌تونيد اينجا مطالعه كنيد:



خلاصه آن:
  • ASP.NET AJAX در آينده نيز كاملا پشتيباني مي‌شود، اما شهروند درجه يك محسوب نخواهد شد؛ مانند استفاده از ScriptManager و UpdatePanel
  • Ajax Control Toolkit كه اكنون به صورت سورس باز در سايت CodePlex نيز قابل دريافت است، منسوخ شده در نظر گرفته شده و تنها در آينده شايد هر از چندگاهي به رفع باگ‌هاي گزارش شده در آن پرداخته شود (اما ديگر به صورت فعال توسعه داده نخواهد شد).
  • Microsoft Ajax Library / ASP.NET Ajax Library هر چند تشابه اسمي با ASP.NET Ajax دارند اما جزئي از كتابخانه‌ي AJAX Control Toolkit بوده و اين مورد نيز از اين پس منسوخ شده در نظر گرفته خواهد شد (مانند استفاده از DataView يا Sys.require ).

۱۳۸۹/۰۷/۲۲

چك ليست نصب SQL Server


عموما هنگام نصب SQL Server ، پيش و پس از آن، بهتر است موارد زير جهت بالا بردن كيفيت و كارآيي سرور، رعايت شوند:

1- پيش فرض‌هاي نصب SQL Server در مورد محل قرارگيري فايل‌هاي ديتا و لاگ و غيره صحيح نيست. هر كدام بايد در يك درايو مجزا مسير دهي شوند براي مثال:
Data drive D:
Transaction Log drive E:
TempDB drive F:
Backup drive G:
اين مورد TempDB را كساني كه با SharePoint كار كرده باشند به خوبي علتش را درك خواهند كرد. پيش فرض نصب افراد تازه كار، نصب SQL Server و تمام مخلفات آن در همان درايو ويندوز است (يعني همان چندبار كليك بر روي دكمه‌ي Next براي نصب). SharePoint هم به نحو مطلوبي تمام كارهايش مبتني بر transactions ، استفاده از جداول موقتي و ذخيره فايل‌هاي حجيم در بانك اطلاعاتي است. يعني استفاده‌ي كامل از TempDB . نتيجه؟ پس از مراجعه به درايو ويندوز مشاهده خواهيد كرد كه فقط چند مگ فضاي خالي باقي مانده! حالا اينجا است كه بدو اين مقاله و اون مقاله رو بخون كه چطور TempDB را بايد از درايو C به جاي ديگري منتقل كرد. چيزي كه همان زمان نصب اوليه SQL Server بايد در مورد آن فكر مي‌شد و نه الان كه سيستم از كار افتاده.
همچنين وجود اين مسيرهاي مشخص و پيش فرض و آگاهي از سطوح دسترسي مورد نياز آن‌ها، از سر دردهاي بعدي جلوگيري خواهد كرد. براي مثال : انتقال فايل‌هاي ديتابيس اس كيوال سرور 2008

2- پس از رعايت مورد 1 ، نوبت به تنظيمات آنتي ويروس نصب شده روي سرور است. اين پوشه‌هاي ويژه را كه جهت فايل‌هاي ديتا و لاگ و غيره بر روي درايوهاي مختلف معرفي كرده‌ايد يا خواهيد نمود، بايد از تنظيمات آنتي ويروس شما Exclude شوند. همچنين در حالت كلي فايل‌هايي با پسوندهاي LDF/MDF/NDF بايد جزو فايل‌هاي صرفنظر شونده از ديد آنتي ويروس شما معرفي گردند.
اين مورد علاوه بر بالا بردن كارآيي SQL Server ، در حين Boot سيستم نيز تاثير گذار است. گاها ديده شده است كه آنتي ويروس‌ها اين فايل‌هاي حجيم را در حين راه اندازي اوليه سيستم، پيش از SQL Server ، جهت بررسي گشوده و به علت حجم بالاي آن‌ها اين قفل‌ها تا مدتي رها نخواهند شد. در نتيجه آغاز سرويس SQL Server را با مشكلات جدي مواجه خواهند كرد كه عموما عيب يابي آن كار ساده‌اي نيست.

3- پيش فرض ميزان حافظه‌ي مصرفي SQL Server صحيح نيست. اين مورد بايد دقيقا بلافاصله پس از پايان عمليات نصب اوليه اصلاح شود. براي مطالعه بيشتر: تنظيمات پيشنهادي حداكثر حافظه‌ي مصرفي اس كيوال سرور

4- آيا مطمئن هستيد كه از تمام امكانات نگارش جديد SQL Server ايي كه نصب كرده‌ايد در حال استفاده مي‌باشيد؟
براي مطالعه بيشتر: تنظيم درجه سازگاري يك ديتابيس اس كيوال سرور

5- بهتر است فشرده سازي خودكار بك آپ‌ها در SQL Server 2008 فعال شوند.
براي مطالعه بيشتر: +

6- از paging بيش از حد اطلاعات، از حافظه‌ي فيزيكي سرور به virtual memory و انتقال آن به سخت ديسك سيستم جلوگيري كنيد. براي اين منظور:
در قسمت Run ويندوز تاپيك كنيد : GPEDIT.MSC و پس از اجراي آن با مراجعه به Group policy editor ظاهر شده به مسير زير مراجعه كنيد:
windows settings -> security settings -> local policies -> user rights assignment -> lock pages in memory
در اينجا به يوزر اكانت سرويس SQL Server دسترسي lock pages in memory را بدهيد.
علاوه بر آن در همين قسمت (user rights assignment) گزينه‌ي "Perform Volume Maintenance tasks" را نيز يافته و دسترسي لازم را به يوزر اكانت سرويس SQL Server بدهيد.

7- به روز رساني اطلاعات آماري SQL Server را به حالت غيرهمزمان تنظيم كنيد.
اگر مطالب مرتبط با SQL Server اين سايت را مرور كرده باشيد حتما با يك سري DMV كه دقيقا به شما خواهند گفت بر اساس اطلاعات آماري جمع شده براي مثال بهتر است روي چه فيلدهايي Index درست كنيد، آشنا شده‌ايد. حالت پيش فرض به روز رساني اين اطلاعات آماري، synchronous است يا همزمان. به اين معنا كه تا اطلاعات آماري يك كوئري ذخيره نشود، حاصل كوئري به كاربر بازگشت داده نخواهد شد كه اين امر مي‌تواند بر روي كارآيي سيستم تاثير گذار باشد. اما امكان تنظيم آن به حالت غير همزمان نيز مطابق كوئري‌هاي زير وجود دارد (اين مورد از SQL Server 2005 به بعد اضافه شده است):

ALTER DATABASE dbName SET AUTO_UPDATE_STATISTICS ON
ALTER DATABASE dbName SET AUTO_UPDATE_STATISTICS_ASYNC ON

8- نصب آخرين سرويس پك موجود فراموش نشود. براي مثال اين سايت آمار تمام به روز رساني‌ها را نگهداري مي‌كند.

9- حتما رويه‌اي را براي تهيه بك آپ‌هاي خودكار پيش بيني كنيد. براي مثال : +

10- ميزان فضاي خالي باقيمانده درايوهاي سرور را مونيتور كنيد. اطلاعات بيشتر: +

11- با نصب سرور جديد و تنظيم collation آن به فارسي، به نكات "يافتن تداخلات Collations در SQL Server" دقت داشته باشيد.

۱۳۸۹/۰۷/۲۱

خلاصه‌اي از مبحث نمايش اطلاعات hierarchical در WPF


در اين مطلب خلاصه‌اي را در مورد نحوه‌ي نمايش اطلاعات hierarchical (سلسله مراتبي، درختي) در WPF به همراه يك سري لينك مرتبط ملاحظه خواهيد نمود.

كلاس زير را در نظر بگيريد:
using System.Collections.Generic;

namespace WpfTests.Hierarchy.Raw.Model
{
public class Person
{
private readonly List<Person> _children = new List<Person>();
public IList<Person> Children
{
get { return _children; }
}

public string Name { get; set; }
}
}
و همچنين يك ObservableCollection ساخته شده از آن‌را با مقدار دهي اوليه:
using System.Collections.ObjectModel;

namespace WpfTests.Hierarchy.Raw.Model
{
public class People : ObservableCollection<Person>
{
public People()
{
this.Add(
new Person
{
Name = "P1",
Children =
{
new Person
{
Name="P2",
Children=
{
new Person
{
Name="P3",
Children=
{
new Person
{
Name="P4",
}
}
}
}
}
}
}
);
}
}
}
قصد داريم اين اطلاعات را در يك TreeView نمايش دهيم.
روش صحيح Binding اين نوع اطلاعات در WPF استفاده از HierarchicalDataTemplate است به صورت زير :
<TreeView ItemsSource="{Binding People}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>


يك سري منبع آموزشي براي آشنايي بيشتر با HierarchicalDataTemplate
Hierarchical Databinding in WPF
Binding WPF Treeview and Objects
A TreeView, a HierarchicalDataTemplate, and a 2D collection
Non-recursive WPF TreeView controls

همچنين هنگام كار با بانك‌هاي اطلاعاتي:
- يك Extension method عالي قابل استفاده در LINQ to SQL و همچنين Entity framework به نام AsHierarchy
- مثالي ديگر از كاربرد LINQ to SQL براي اين منظور
- و يا مثالي از ADO.NET و DataSets و مثالي ديگر

۱۳۸۹/۰۷/۱۵

معرفي Microsoft.Data.dll يا WebMatrix.Data.dll


مايكروسافت اخيرا علاوه بر تكميل ORM هاي خود مانند LINQ to SQL و همچنين Entity framework ، لايه ديگري را نيز بر روي ADO.NET جهت كساني كه به هر دليلي دوست ندارند با ORMs كار كنند و از نوشتن كوئري‌هاي مستقيم SQL لذت مي‌برند،‌ ارائه داده است كه Microsoft.Data library نام دارد و از قابليت‌هاي جديد زبان سي شارپ مانند واژه‌ كليدي dynamic استفاده مي‌كند.

در ادامه قصد داريم جهت بررسي توانايي‌هاي اين كتابخانه از بانك اطلاعاتي معروف Northwind استفاده كنيم. اين بانك اطلاعاتي را از اينجا مي‌توانيد دريافت كنيد.

مراحل استفاده از Microsoft.Data library:
الف) اين اسمبلي جديد به همراه پروژه WebMatrix ارائه شده است. بنابراين ابتدا بايد آن‌را دريافت كنيد: +
لازم به ذكر است كه اين كتابخانه اخيرا به WebMatrix.Data.dll تغيير نام يافته است. (اگر وب را جستجو كنيد فقط به Microsoft.Data.dll اشاره شده است)

ب) پس از نصب، ارجاعي را از اسمبلي WebMatrix.Data.dll به پروژه خود اضافه نمائيد. اين اسمبلي در صفحه‌ي Add References ظاهر نمي‌شود و بايد كامپيوتر خود را براي يافتن آن جستجو كنيد كه عموما در آدرس زير قرار دارد:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\WebMatrix.Data.dll

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

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="systemData:defaultProvider" value="System.Data.SqlClient" />
</appSettings>
<connectionStrings>
<add name="Northwind"
connectionString="Data Source=(local);Integrated Security = true;Initial Catalog=Northwind"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>

اين تعاريف در فايل web.config و يا app.config برنامه وب يا ويندوزي شما قرار خواهند گرفت.

د) نحوه‌ي تعريف كوئري‌ها و دريافت اطلاعات
using System;
using WebMatrix.Data;

namespace TestMicrosoftDataLibrary
{
class Program
{
static void Main(string[] args)
{
getProducts();

Console.Read();
Console.WriteLine("Press a key ...");
}

private static void getProducts()
{
using (var db = Database.Open("Northwind"))
{
foreach (var product in db.Query("select * from products where UnitsInStock < @0", 20))
{
Console.WriteLine(product.ProductName + " " + product.UnitsInStock);
}
}
}
}
}
پس از افزودن ارجاعي به اسمبلي WebMatrix.Data و مشخص سازي رشته‌ي اتصالي به بانك اطلاعاتي، استفاده از آن جهت دريافت اطلاعات كوئري‌ها همانند چند سطر ساده‌ي فوق خواهد بود كه از امكانات dynamic زبان سي شارپ 4 استفاده مي‌كند؛ به اين معنا كه product.ProductName و product.UnitsInStock در زمان اجرا مورد ارزيابي قرار خواهند گرفت.
همچنين نكته‌ي مهم ديگر آن نحوه‌ي تعريف پارامتر در آن است (همان 0@ ذكر شده) كه نسبت به ADO.NET كلاسيك به شدت ساده شده‌است (و نوشتن كوئري‌هاي امن و SQL Injection safe را تسهيل مي‌كند).
در اينجا Database.Open كار گشودن name ذكر شده در فايل كانفيگ برنامه را انجام خواهد داد. اگر بخواهيد اين تعاريف را در كدهاي خود قرار دهيد (كه اصلا توصيه نمي‌شود)، مي‌توان از متد Database.OpenConnectionString استفاده نمود.

يا مثالي ديگر: استفاده از LINQ حين تعريف كوئري‌ها:
private static void getCustomerFax()
{
using (var db = Database.Open("Northwind"))
{
var product = db.Query("SELECT * FROM [Customers] WHERE City=@0", "Paris").FirstOrDefault();
if (product != null)
Console.WriteLine(product.Fax);
else
Console.WriteLine("not found.");
}
}

ه) اجراي كوئري‌ها بر روي بانك اطلاعاتي
private static void ExecQuery()
{
using (var db = Database.Open("Northwind"))
{
int affectedRecords = db.Execute("UPDATE [Customers] SET fax = fax + '*' WHERE City = @0", "Paris");
Console.WriteLine("Affected records: {0}", affectedRecords);
}
}

با استفاده از متد Execute آن مي‌توان كوئري‌هاي دلخواه خود را بر روي بانك اطلاعاتي اجرا كرد. خروجي آن تعداد ركورد تغيير كرده است.

و) نحوه‌ي اجراي يك رويه ذخيره شده و نمايش خروجي آن
private static void ExecSPShowResult()
{
using (var db = Database.Open("Northwind"))
{
var customer = db.Query("exec CustOrderHist @0", "ALFKI").FirstOrDefault();
if (customer != null)
{
Console.WriteLine(customer.ProductName);
}
}
}
در اين مثال رويه ذخيره شده CustOrderHist در بانك اطلاعاتي Northwind اجرا گرديده و سپس اولين خروجي آن نمايش داده شده است.

ز) اجراي يك تابع و نمايش خروجي آن
private static void useFuncs()
{
using (var db = Database.Open("Northwind"))
{
var query = db.Query("SELECT dbo.FN_GET_CATEGORY_TREE(@0) as Rec1", 3);
foreach(var tree in query)
{
Console.WriteLine(tree.Rec1);
}
}
}
در اينجا تابع FN_GET_CATEGORY_TREE موجود در بانك اطلاعاتي Northwind انتخاب گرديده و سپس خروجي آن به كمك يك نام مستعار (براي مثال Rec1) نمايش داده شده است.

سؤال : آيا WebMatrix.Data.dll بهتر است يا استفاده از ORMs ؟

در اينجا چون از قابليت‌هاي دايناميك زبان سي شارپ 4 استفاده مي‌شود، كامپايلر دركي از اشياء خروجي و خواص آن‌ها براي مثال tree.Rec1 (در مثال آخر) ندارد و تنها در زمان اجرا است كه مشخص مي‌شود آيا يك چنين ستوني در خروجي كوئري وجود داشته است يا خير. اما حين استفاده از ORMs اين طور نيست و Schema يك بانك اطلاعاتي پيشتر از طريق نگاشت‌هاي جداول به اشياء دات نتي، به كامپايلر معرفي مي‌شوند و همين امر سبب مي‌شود تا اگر ساختار بانك اطلاعاتي تغيير كرد، پيش از اجراي برنامه و در حين كامپايل بتوان مشكلات را دقيقا مشاهده نمود و سپس برطرف كرد.
ولي در كل استفاده از اين كتابخانه نسبت به ADO.NET كلاسيك بسيار ساده‌تر بوده، مي‌توان اشياء و خواص آن‌ها را مطابق نام جداول و فيلدهاي بانك اطلاعاتي تعريف كرد و همچنين تعريف پارامترها و برنامه نويسي امن نيز در آن بسيار ساده‌تر شده است.

براي مطالعه بيشتر:
Introduction to Microsoft.Data.dll

۱۳۸۹/۰۷/۱۲

دريافت خلاصه‌ي وبلاگ تا 12 مهرماه 1389


اگر علاقمند باشيد كه مطالب سايت جاري را (تا 12 مهرماه 1389) به صورت آفلاين مطالعه كنيد،‌ لطفا فايل زير را دريافت نمائيد.
دريافت خلاصه‌ي وبلاگ و يا +




۱۳۸۹/۰۷/۱۱

يكسان سازي "ي" و "ك" دريافتي در حين استفاده از Entity framework


در مورد يكسان سازي ي و ك در حين استفاده از WCF RIA Services پيشتر مطلبي را در اين سايت خوانده بوديد. جهت تكميل اين بحث، بسط اين روش به Entity framework به صورت زير خواهد بود:

using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Reflection;

namespace EfExt
{
public static class CorrectYeKe
{
public static void ApplyCorrectYeKe(this ObjectContext ctx)
{
if (ctx == null)
return;

//پيدا كردن موجوديت‌هاي تغيير كرده
var changedEntities = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified
);

foreach (var entity in changedEntities)
{
if (entity.Entity == null) continue;

//يافتن خواص قابل تنظيم و رشته‌اي اين موجوديت‌ها
var propertyInfos = entity.Entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(string));

var pr = new PropertyReflector();

//اعمال يكپارچگي نهايي
foreach (var propertyInfo in propertyInfos)
{
var propName = propertyInfo.Name;
var val = pr.GetValue(entity.Entity, propName);
if (val != null)
{
pr.SetValue(
entity.Entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}
}

ابتدا موجوديت‌هاي تغيير كرده يافت خواهند شد (اگر از self tracking entities استفاده مي‌كنيد استفاده از Context.DetectChanges پيش از فراخواني اين متد ضروري خواهد بود)، سپس در اين ليست در مورد تك تك اشياء، خواص رشته‌اي كه readonly نيستند يافت شده و ي و ك آن‌ها يك دست مي‌شوند.
محل اعمال آن هم بايد پيش از فراخواني Context.SaveChanges باشد.

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