استفاده از CLR Integration در SQL Server 2005 (قسمت سوم)

ارسال شده توسط administrator
27. مي 2010 16:42

اگر فرض کنیم که متد GetStrings() تحت عنوان یک TVF با همان نام ثبت شده است، آنگاه مثلا زیر، قطعه ای از T-SQL از یک stored procedure است که از این TVF برای بیرون کشیدن (extract) line items ، در شکل جدولی استفاده می کند.

CREATE PROCEDURE Insert_Order @cust_id int, @lineitems nvarchar(8000)
AS
BEGIN
...
INSERT LineItems
SELECT * FROM dbo.GetStrings(@lineitems)
...
END

کدام را استفاده کنیم؟

تصمیم در مورد اینکه یک stored procedure را در ترکیب با SqlPipe- چه غیر مستقیم در T-SQL و چه مستقیم د ر یک CLR routine- یا یک table-valued function استفاده کنیم، بستگی به فاکتورهای زیادی دارد که باید مد نظر قرار گیرند، که شامل composability requirements، منبع داده ها، نیاز به اثرات جانبی، و typing requirements برای خروجی است. به ترتیب هر کدام از آنها را مورد بحث قرار می دهیم.

Composability Requirements

شاید استفاده دوباره یا تغییر (manipulation) خروجی بدست آمده درون یک TVF یا یک stored procedure، مطلوب باشد. Table-valued functions از نقطه نظر composability، پرکاربردتر هستند، مثلاً نوع بازگشت (return type) یک TVF، یک rowset نسبی است که در هر جاییکه چنین ساختاری مجاز است، قابل استفاده است. مخصوصاً، دربخش FROM در عبارت SELECT، قابل استفاده است، و چنین خروجیی می توانند از composability of SELECT در sub-queryها، عبارات INSERT...SELECT، جدولهای مشتق شده، و جدول عبارات استفاده کنند.

از سوی دیگر، ، می توان stored procedureها را فقط به عنوان بخشی از ترکیب INSERT...EXEC، از درون T-SQL language، compose کرد، که به خروجی بدست آمده اجازه ذخیره شدن در یک جدول دایم یا موقت را می دهد. عملیات INSERT، یک کپی واقعی از داده ها را ارائه می کند که احتمالاً روی performance تاثیر گذار خواهد بود.

اگر composability و استفاده مجدد از خروجی درون سرور یک نیاز باشند، TVFها جایگزینهای بهتری هستند. اگر خروجی بدست آمده فقط نیاز به stream شدن به client یا middle-tier داشته باشند، هر دو روش منطقی است.

منبع داده ها (Source of the Data)

منبع داده هایی که بازگردانده می شود، فاکتور مهم دیگری در انتخاب بین پیاده سازی بر اساس T-SQL یا CLR است. خروجی را میتوان هم از طریق خواندن source در local instance با استفاده از ADO.NET provider یا از طریق یک source خارج از SQL Server، بدست آورد. در منابع خارجی، پیاده سازی بر پایه CLR، انتخاب بهتری از T-SQL است، به علت داشتن سهولتی که logic accessing the external data، توسط آن انجام می شود.

در صورت بدست آوردن خروجی بر اساس یک query که با استفاده از ADO.NET provider روی local instance اجرا شده، یک stored procedure، معمولاً یک query را اجرا می کند، از طریق خروجی تکرار می شود، و بعضی از عملیات ها را روی rowها، قبل از بازگرداندن از طریق یک SqlPipe، انجام می دهد.

با یک TVF ، برنامه نویسان می توانند یک شی data reader را خوانده و در یک کلکسیون در حافظه بارگذاری کنند. اما، SQL Server 2005، به requestها اجازه نمی دهد تا زمانی که یک table-valued function باز می گردد، pending بمانند؛ هر query که از طریق ADO.NET provider اجرا شده است، باید کاملاً اجرا شوند و خروجی باید به طور کامل قبل از اینکه function body بتواند بازگردد، consume شوند. اگر عبارت بازگشت (return statement)، هنگامی که SqlDataReader operationها در ADO.NET provider، معلق (pending) است، اجرا شود، یک error ممکن است روی دهد. این بدین معناست که در اکثر مواردی که داده ها از local database instance بازگردانده می شوند، نمی توان داده ها را از طریق یک CLR TVF، stream کرد. اگر فاکتورهای دیگر از قبیل composability، مستلزم نوشته شدن به عنوان یک TVF باشد، شاید نوشتن آن در T-SQL تنها آپشن باشد. در غیر این صورت، استفاده از یک stored procedure مدیریت شده که از SqlPipe استفاده می کند، ممکن است.

در مواردی که در آنها خروجی قرار است از درون یک stored procedure بر اساس داده ها از local instance، بدست آید، استفاده از APIهای SendResults فقط در مواردی معنا دارد که خروجی نیاز به مقداری اصلاح یا تغییر رویه ای (procedural modification or manipulation) دارد.

عملیاتهایی (operation) با اثرات جانبی (Side-Effect)

بطور کلی، operationهایی که دارای side-effect هستند- operationهایی که حالت database، از قبیل عبارات DML، یا transaction operationها، را تغییر می دهند- از functionهایی (مثلاً functionهای table-valued) که تعریف کاربر است، منع می شوند؛ اما شاید این operationها مطلوب باشند. مثلاً ممکن است کسی بخواهد یک SAVEPOINT transaction را set کند، یا یک operation اجرا کند، و در صورت روی دادن error، آن را به SAVEPOINT، rollback کند.

با فرض اینکه functionهایی که کاربر تعریف می کند، منع می شوند، چنین سناریویی فقط از طریق یک stored procedure قابل پیاده سازی است، و خروجی باید از طریق SqlPipe بازگردانده شوند. اما به یاد داشته باشید، به operationهایی با side-effect، اجازه اجرا شدن از طریق ADO.NET provider، زمانیکه SqlPipe مشغول فرستادن خروجی است، داده نمی شود. این operationها فقط قبل از اینکه خروجی شروع یا بعد از اینکه تکمیل شوند، مجاز هستند.

Typing و تعداد خروجی

توصیف خروجی بدست آمده از طریق یک CLR stored procedure توسط SqlPipe، با خروجی بدست آمده از یک CLR TVF متفاوت، و با همتایانشان در T-SQL یکسان هستند.

از سوی دیگر، یک stored procedure declaration، هیچ عبارتی در مورد خروجی بدست آمده – یا حتی آیا آنها را بدست می آورد- نمی سازد. ممکن است آسان بنظر آید، و گرچه مسلماً انعطاف (flexibility) بیشتری ایجاد می کند، ولی باید در نوشتن applicationهایی که stored procedureها را اجرا می کنند دقت بیشتری کرد، زیرا ممکن است است به طور دینامیکی شکل خروجی را دوباره تعریف کند. اما، اگر لازم باشد که schema برای خروجی در میان فراخوانی ها ()invocation متغیر باشند، باید از یک stored procedure استفاده کرد، زیرا فقط SqlPipe این انعطاف را در اختیار می گذارد.

در حقیقت، نوع داده های ضعیف خروجی ها تولید شده از طریق sqlpipe درون Stored Procedureها، فراتر از Schemaی یک خروجی واحد است که احتمال بازگشت چندین خروجی متغیر را دربر می گیرد. هم typeها و هم تعداد خروجی را می توان توسط stored procedure به طور دینامیکی تعیین کرد.

خلاصه

جدول زیر خلاصه ای از راهنماییهایی در مورد چگونگی تعیین اینکه آیا یک application ویژه باید در T-SQL یا CLR نوشته شود، و یا اینکه آیا یک stored procedure یا یک table-valued function باید استفاده شود، است.

 

خیر بله موقعیت
Procedure یا TVF TVF آیا Composability نیاز است؟

(accessing only local data) T-SQL TVF یا procedure

CLR TVF یا CLR procedure

منبع خارجی داه ها (در مقابل (accessing only local data
Procedure یا TVF Procedure آیا side-effect نیاز است؟
Procedure Procedure یا TVF results schema ثابت؟
Procedure یا TVF Procedure بیش از یک دسته خروجی
T-SQL TVF CLR TVF آیا قادر به stream کردن خروجی است؟

در اکثرجاهای این بخش، ارسال خروجی از طریق SqlPipe، به شدت با procedureها پیوند خورده است. گرچه SqlPipe و امکان بازگرداندن خروجی در بدنه CLR triggerها در دسترس هستند، اما این عمل به شدت نهی می شود، زیرا ممکن است به خروجی غیر منتظره ای برای issuing Data Manipulation Language یا عبارات Data Definition Language statements با triggerهایی که روی اشیاء هدف تعریف شده، منجر شود.

اجرای Aggregationهای سفارشی روی داده ها

سناریوهایی وجود دارند که ممکن است aggregation نیاز به اجرا شدن روی data داشته باشد، که شامل اجرای محاسبات آماری از قبیل یافتن میانگین ها، انحراف از معیار (standard deviation) و غیره است. اگر aggregation function مورد نظر، در داخل ساخته نشود، راههای زیادی برای اضافه کردن (add) functionality در SQL Server 2005 وجود دارد:

· نوشتن aggregation به عنوان یک aggregation تعریف شده توسط کاربر (UDA).

· نوشتن aggregation با استفاده از یک CLR stored procedure.

· استفاده از یک cursor جانبی سرور (server-side) در T-SQL.

بیایید این سه راه را در بافت (context) یک aggregation function ساده که محصول یک دسته از valueهای معین را محاسبه می کند، امتحان کنیم.

مثال: یک Product که به عنوان یک aggregate function تعریف شده توسط کاربر، پیاده سازی شده

در اینجا کدی برای این task آورده شده که به عنوان یک aggregate تعریف شده توسط کاربر نوشته شده. Logic محاسبه این محصول، وقتی که وارد می شود، در متد Accumulate() وجود دارد. متد Merge()، تعریف می کند اگر این دو aggregate ادغام شوند، چه اتفاقی می افتد.

در Visual Basic .NET:

Imports System
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server

<Serializable()> _
<SqlUserDefinedAggregate(Format.Native)> _
Public Structure ProductAgg

Private product As SqlInt32

Public Sub Init()
product = 1
End Sub

Public Sub Accumulate(ByVal value As SqlInt32)
product = product * value
End Sub

Public Sub Merge(ByVal group As ProductAgg)
product = product * group.product
End Sub

Public Function Terminate() As SqlInt32
Return product
End Function

End Structure

در C#:

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(Format.Native)]
public struct ProductAgg
{
private SqlInt32 product;

public void Init()
{
product = 1;
}

public void Accumulate(SqlInt32 value)
{
product = product * value;
}

public void Merge(ProductAgg group)
{
product = product * group.product;
}

public SqlInt32 Terminate()
{
return product;
}
}
 

بعد از اینکه این type، با SQL Server ساخته و ثبت شد، می توان آن را فقط به عنوان یک built-in aggregate از T-SQL استفاده کرد:

SELECT dbo.ProductAgg(intcol) 
FROM tbl
GROUP BY col
 

مثال: Product، به عنوان یک Stored Procedure مدیریت شده

یک stored procedure را می توان ایجاد کرد که روی data تکرار می شود تا محاسبه (computation) را اجرا کند. این تکرار با استفاده از کلاس SqlDataReader بدست می آید، همانطور که در پایین نشان داده شده.

در Visual Basic .NET:

 
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlTypes
Imports System.Data.SqlClient

Partial Public Class StoredProcedures

<SqlProcedure()> _
Public Shared Sub VBProductProc(ByRef value As SqlInt32)

' The empty product is 1
value = 1

Using conn As New SqlConnection("context connection = true")
conn.Open()
Dim cmd As SqlCommand = New SqlCommand()
cmd.Connection = conn
cmd.CommandText = "SELECT intcolumn FROM tbl"
Dim r As SqlDataReader = cmd.ExecuteReader()
Using r
Do While r.Read()
value = value * r.GetSqlInt32(0)
Loop
End Using
conn.Close()
End Using
End Sub

End Class

در C#:


using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Data.SqlClient;
public partial class StoredProcedures
{
[SqlProcedure]
public static void ProductProc(out SqlInt32 value)
{
// Ensure that we write to value.
// Empty product is 1.
value = 1;

using (SqlConnection conn =
new SqlConnection("context connection = true"))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT intcolumn FROM tbl";
SqlDataReader r = cmd.ExecuteReader();
using (r)
{
while (r.Read()) //skip to the next row
{
value *= r.GetSqlInt32(0);
}
}
}
}
}

می توان این را با استفاده از عبارت T-SQL EXEC فرا خواند:

 
 
EXEC Product @p OUTPU

مثال: Product به عنوان یک T-SQL Stored Procedure که از یک Cursor استفاده می کند

نهایتاً، می توان یک T-SQL stored procedure ایجاد کرد که یک query را اجرا و محاسبه را با استفاده از یک T-SQL cursor انجام می دهد تا data را تکرار کند.

T-SQL:

create procedure TSQL_ProductProc (@product int output)
as
begin
declare @sales int
declare c insensitive cursor for select intcolumn from tbl

set @product = 1

open c
fetch next from c into @sales

while @@FETCH_STATUS = 0
begin
set @product = @product * @sales
fetch next from c into @sales
end

close c
deallocate c
end
go

خلاصه

تصمیم گیری در مورد استفاده از یک UDA یا یکی از راه حلهای دیگر برای بدست آوردن خروجی به چند عامل بستگی دارد، که شامل composability requirements، مشخصه های aggregation algorithm، و نیاز به side-effect می شود.

در واقع، یک UDA، یک شی مستقل است که از هر T-SQL query، قابل استفاده است، عموماً در همان مکانی که یک system aggregate استفاده می شود. هیچ تصوری در مورد یک query که روی آن عمل می کند وجود ندارد. مثلاً، می توان آن را در view definitionها (اما نه در indexed viewها) و در sub-queryهای ترازویی (scalar) به شمار آورد.

می توان UDAها را قبل از کلمه ORDER BY در یک query ارزیابی کرد، پس هیچ تضمینی برای ترتیبی که valueها به aggregation function ارائه می شوند وجود ندارد. بنابراین، اگر aggregation algorithm باید valueها را به ترتیب خاصی consume کند، نمی توان از یک UDA استفاده کرد. یک UDA نیز دقیقاً valueهای کل یک گروه را consume می کند و یک value واحد باز می گرداند. اگر مشکل حل نشد، آنگاه باید از یک تکنیک دیگر استفاده کرد.

همچنین یک UDA، هیچ data access را اجرا نمی کند و هیچ side-effect ندارد؛ اگر هر یک از اینها لازم باشند، آنگاه باید از یک stored procedure استفاده کرد.

گرچه UDAها دارای محدودیتهایی هستند، اما احتمال دارد بهترین performance را از آپشنهای ارائه شده انجام دهند، پس aggregation عموماً باید از طریق یک UDA اجرا شود، مگر اینکه الزامات (requirement) دیگر مانع آن شوند.

Typeهای تعریف شده توسط کاربر

حالا به یکی از قدرتمندترین ویژگیهای SQL Server 2005 می رسیم که اغلب مورد سوء تفاهم قرار می گیرد. با Typeهای تعریف شده توسط کاربر (UDTها)، می توان scalar type system را در database، گسترش داد. و این، فراتر از تنها تعریف کردن alias برای یک system type است که در نسخه قبلی SQL Server دردسترس بود. تعریف یک UDT، به سادگی نوشتن یک کلاس در کد مدیریت شده، ایجاد یک اسمبلی، و سپس (register) ثبت کردن type در SQL Server با استفاده از عبارت CREATE TYPE است. مثال زیر، اسکلت یک کد است که شکل ساختاری یک UDT را نشان می دهد.

در Visual Basic .NET:

<Serializable()> _
<Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native)> _
Public Structure SimpleType
Implements INullable

Public Overrides Function ToString() As String
...
End Function

Public ReadOnly Property IsNull() As Boolean Implements _
INullable.IsNull
...
End Property

Public Shared ReadOnly Property Null() As SimpleType
...
End Property

Public Shared Function Parse(ByVal s As SqlString) As SimpleType
...
End Function

End Structure

در C#:

 
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native)]
public struct SimpleUdt : INullable
{
public override string ToString() { ... }
public bool IsNull { ... }
public static SimpleUdt Null
{
get { ... }
}
public static SimpleUdt Parse(SqlString s) { ... }
}

می توان این را در T-SQL بارگذاری (load) و استفاده کرد:

CREATE TYPE simpleudt FROM [myassembly].[SimpleUdt]
CREATE TABLE t (mycolumn simpleudt)

کی یک UDT ایجاد کنیم؟

UDTها در SQL Server 2005، یک مکانیزم توسعه پذیر object-relational نیستند؛ بلکه راهی برای گسترش scalar type system در database هستند. scalar type system شامل typeهای ستونی (columnar) است که با SQL Server (int، nvarchar، uniqueidentifier، و غیره) منتقل و استفاده می شوند. با UDTها، می توان یک typeجدید تعریف کرد که میتوان به جای یک built-in scalar type استفاده کرد. اگر type، یک value اتمی است که برای مدل شدن به عنوان یک ستون مناسب است، یک UDT ایجاد کنید.

کاندیدهای خوب جهت پیاده سازی یک UDT، شامل تاریخ custom و typeهای time data در تقویم های مختلف و typeهای currency data است. یک UDT، یک کلاس واحد است که در معرض همه رفتارهای (behavior) دردسترس روی type است و داده های بنیادی ذخیره شده توسط type را خلاصه می کند: کل data access از programmatic interface در UDT استفاده می کنند. اغلب می توان از functionality موجود در .NET framework — از قبیل بین المللی کردن (internationalization) یا calendar functionality — استفاده کرد تا functionality را که به سختی فراهم می شود، فراهم کرد.

کی یک UDT ایجاد نکنیم؟

از یک UDT نباید برای مدل سازی اشیای business از قبیل کارمندان، قراردادها، یا مشتری ها استفاده کرد. SQL Server با یک UDT به عنوان یک واحد (unit) رفتار می کند که به آن opaque است. بعضی از مسائل با UDTهای پیچیده، شامل محدودیت 8 کیلو بایتی برای typeها، محدودیتهای indexing، و این واقعیت است که کل value باید هنگامی که هر value در UDT، update می شود، update شود.

فاکتورهایی که هنگام طراحی یک UDT باید در نظر گرفت

از آنجاییکه UDTها ستونی هستند، می توان index ها را، روی کل valueهای UDT تعریف کرد؛ همانطور که referential integrity constraint، از قبیل unique بودن، را می توان. همچنین می توان از UDTها در مقایسه و مرتب کردن سناریوها استفاده کرد.

مقایسه Valueهای UDT، بوسیله مقایسه binary representation of the type انجام می شود. اگر از Format.Native به عنوان مکانیزم پایداری (persistence mechanism) استفاده شود، آنگاه یک persisted form با استفاده از همان ترتیب field، به عنوان آنچه که در type تعریف می شود، ایجاد می شود؛ پس باید دقت کرد که آنها در جای صحیح قرار می گیرند.

غیر از مقایسه، هر عملیاتی روی یک UDT، نیازمند این است که UDT value، de-serialize شود و یک متد، فراخوانده شود. این الگو (pattern) موجب تحمیل بار اضافی روی server می شود، که باید هنگام تصمیم گیری در مورد اینکه آیا آن را به عنوان یک UDT، مدلسازی کنیم یا نکنیم، مد نظر قرار بگیرد. UDTها زمانی که یک type ،که نیاز به model شدن دارد، رفتارهای پیچیده ای دارد، مفیدتر هستند. اگرtype نسبتاً ساده باشد، آنگاه شاید بهتر باشد از ساختار UDT پرهیز کنیم.

نهایتاً، می توان از متدهای static یک UDT به عنوان یک مکانیزم بسته بندی (packaging) راحت جهت ذخیره سازی library تابعهای مربوط استفاده کرد. متدهای static را می توان از T-SQL با استفاده از syntaz زیر فراخوانی کرد:

select Type::Function(@arg1)

مثال: تاریخ های غیر غربی

می خواهیم valueهای زمان و تاریخ را با استفاده از تقویم "ام القرا" که با تقویم گریگوری که SQL Server datetime data type از آن استفاده می کند، ذخیره کنیم. می خواهیم این data type همان رفتارهای پایه ای را داشته باشد، بویژه string conversion، قابلیت بازیابی اجزای data، و محاسبات تاریخ و غیره.

مثال زیر از یک type تعریف شده توسط کاربر، یک پیاده سازی ساده از این data type است، و از UmAlQuraCalendar type استفاده می کند، که در نسخه 2.0 .NET Framework جدید است. متوجه شدن این مثال کمک زیادی به فراهم کردن متذهای لازم می کند.

UDT ام القرا در C#

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Globalization;

[Serializable]
[SqlUserDefinedType(Format.Native, IsByteOrdered = true)]
public struct UmAlQuraDateTime : INullable
{

/*
* Private state.
*/

private long dtTicks;
private bool isNull;

// Calendar object used for all calendar-specific operations
private static readonly UmAlQuraCalendar s_calendar =
new UmAlQuraCalendar();

// For correct formatting we need to provie a culture code for
// a country that uses the Um Al Qura calendar: Saudi Arabia.
private static readonly CultureInfo ci =
new CultureInfo("ar-SA", false);


/*
* Null-Handling
*/

// get a null instance
public static UmAlQuraDateTime Null
{
get
{
UmAlQuraDateTime dt = new UmAlQuraDateTime();
dt.isNull = true;
return dt;
}
}

public bool IsNull
{
get
{
return this.isNull;
}
}

/*
* Constructors
*/

public UmAlQuraDateTime(long ticks)
{
isNull = false;
dtTicks = ticks;
}

public UmAlQuraDateTime(DateTime time) : this(time.Ticks)
{
}

/*
* Factory routines.
*/

public static UmAlQuraDateTime Parse(SqlString s)
{
if (s.IsNull) return Null;
DateTime t = DateTime.Parse(s.Value);
return new UmAlQuraDateTime(t);
}

public static UmAlQuraDateTime ParseArabic(SqlString s)
{
if (s.IsNull) return Null;
DateTime t = DateTime.Parse(s.Value, ci);
return new UmAlQuraDateTime(t);
}

public static UmAlQuraDateTime FromSqlDateTime(SqlDateTime d)
{
if (d.IsNull) return Null;
return new UmAlQuraDateTime(d.Value);
}

public static UmAlQuraDateTime Now
{
get
{
return new UmAlQuraDateTime(DateTime.Now);
}
}

/*
* Conversion Routines
*/

public DateTime DateTime
{
get { return new DateTime(this.dtTicks); }
}

public SqlDateTime ToSqlDateTime()
{
return new SqlDateTime(this.DateTime);
}

public override String ToString()
{
return this.DateTime.ToString(ci);
}

public String ToStringUsingFormat(String format)
{
return this.DateTime.ToString(format, ci);
}

/*
* Methods for getting date parts.
*/

public int Year
{
get
{
return s_calendar.GetYear(this.DateTime);
}
}

public int Month
{
get
{
return s_calendar.GetMonth(this.DateTime);
}
}

public int Day
{
get
{
return s_calendar.GetDayOfMonth(this.DateTime);
}
}

/*
* Date arithmetic methods.
*/

public UmAlQuraDateTime AddYears(int years)
{
return new
UmAlQuraDateTime(s_calendar.AddYears(this.DateTime, years));
}

public UmAlQuraDateTime AddDays(int days)
{
return new
UmAlQuraDateTime(s_calendar.AddDays(this.DateTime, days));
}

public double DiffDays(UmAlQuraDateTime other)
{
TimeSpan diff = DateTime.Subtract(other.DateTime);
return diff.Days;
}
}

وقتی این type در SQL Server بارگذاری می شود، می توان این type از طریق T-SQL استفاده کرد. در زیر چند مثال T-SQL آورده شده اند که از این UDT و خروجیی که بدست می آورند، استفاده می کنند.

ابتدا یک تاریخ ام القرا را parse و سپس آن را در دو فرمت با معادلهای غربی چاپ می کنیم:

 

DECLARE @d UmAlQuraDateTime
SET @d = UmAlQuraDateTime::ParseArabic('01/02/1400')
PRINT @d.ToString()
PRINT @d.ToStringUsingFormat('F')
PRINT @d.ToSqlDateTime()

نتیجه زیر حاصل می آید:

 

clip_image001


 

می توانیم تاریخ های غربی را نیز به ام القرا تبدیل کنیم:

DECLARE @n DateTime
SET @n = 'March 20, 2005'
DECLARE @d UmAlQuraDateTime
SET @d = UmAlQuraDateTime::FromSqlDateTime(@n)
PRINT @n
PRINT @d.ToString()

نتیجه:

clip_image001[8]

نهایتاً، بااین type می توانیم جداولی را با ستون، ایجاد و اصلاح کنیم:

 
CREATE TABLE dates (
western DateTime,
umalqura UmAlQuraDateTime
)

INSERT INTO dates(western) VALUES ('June 1, 2005')
INSERT INTO dates(western) VALUES ('July 1, 2005')

UPDATE dates
SET umalqura = UmAlQuraDateTime::FromSqlDateTime(dates.western)

SELECT western, umalqura.ToString() as umalqura FROM dates

این جدول، نتیجه نهایی است:

 
 
clip_image001[10]
 
 
 

نتیجه گیری

این مقاله، راهنماییها، سناریوهای استفاده واقعی، و نمونه هایی را با استفاده از ویژگیهای CLR integration در SQL Server 2005، ارائه کرده است. برنامه نویسان و معماران (Database application developers and architects) باید از این مقاله جهت ترکیب با documentation در ویژگیهای دیگر SQL Server 2005، از قبیل Transact-SQL، XML، و Service Broker استفاده کنند.

 
 

استفاده از CLR Integration در SQL Server 2005 (قسمت دوم)

ارسال شده توسط administrator
27. مي 2010 15:36

حل کردن مشکلات رایج در Database Programming

بخش قبلی، برنامه نویسی بر پایه CLR را در سطحی بالاتر که راجع به T-SQL، extended stored procedures XPها(، و کدی در middle-tier است، قرار داد. در این بخش، ما نگاهی به دسته ای از taskها و الگوهایی (pattern) که یک database application developer ممکن است با آن مواجه شود، خواهم انداخت، و چگونگی استفاده (یا عدم استفاده) از CLR integration جهت حل کردنشان را مورد بحث قرار خواهیم داد. ما چندین code example در اختیار قرار می دهیم، هم در C# و هم در Visual Basic .NET.

اعتبار دهی داده ها با استفاده از NET Framework Library.

CLR integration در SQL Server 2005 به کاربران اجازه می دهد از عملکرد غنی (rich functionality) که توسط class libraryهای .NET Framework فراهم شده، برای حل مشکلات database programmingشان استفاده کنند.

یک مثال، استفاده از expressionهای مرتب است، جهت فراهم کردن الگوهای پیچیده تر نسبت به آنچه که با استفاده از اپراتور LIKE در T-SQL دردسترس است. کد زیر را که چیزی جز یک wrapper ساده اطراف کلاس RegEx در System.Text.RegularExpressions namespace نیست، ملاحظه فرمایید.

در Visual Basic .NET:

Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Text.RegularExpressions

Partial Public Class Validation

<SqlFunction(IsDeterministic:=True, IsPrecise:=True)> _
Public Shared Function RegExMatch(ByVal pattern As String, _
ByVal matchString As String) As Boolean

Dim r1 As Regex = New Regex(pattern.TrimEnd(Nothing))
Return r1.Match(matchString.TrimEnd(Nothing)).Success
End Function

<SqlFunction(IsDeterministic:=True, IsPrecise:=True)> _
Public Shared Function ExtractAreaCode(ByVal matchString As String)_
As SqlString

Dim r1 As Regex = New Regex("\((?<ac>[1-9][0-9][0-9])\)")
Dim m As Match = r1.Match(matchString)

If m.Success Then
Return m.Value.Substring(1, 3)
Else
Return SqlString.Null
End If

End Function
End Class

 

در C#:

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;

public partial class Validation
{
[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static bool RegExMatch(string pattern, string matchString)
{
Regex r1 = new Regex(pattern.TrimEnd(null));
return r1.Match(matchString.TrimEnd(null)).Success;
}

[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static SqlString ExtractAreaCode(string matchString)
{

Regex r1 = new Regex("\\((?<ac>[1-9][0-9][0-9])\\)");
Match m = r1.Match(matchString);
if (m.Success)
return m.Value.Substring(1, 3);
else return SqlString.Null;
}
}

 

بیایید فرض کنیم متدهای RegExMatch() و ExtractAreaCode()، به عنوان تابع های (function) تعریف شده توسط کاربر، در database با آپشن RETURNS NULL ON NULL INPUT ثبت شده اند، به طوری که function، هنگامی که هر یک از ورودی هایش (input) بی اثر هستند، بی اثر باز می گردد. این، به ما اجازه می دهد از نوشتن هر special null handling code درون function بپرهیزیم.

حالا می توانیم constraintهایی روی ستونهای جدولی که از کد بالا برای اعتبار دهی آدرسهایی ایمیل و شماره تلفنها استفاده می کند، به ترتیب زیر تعریف کنیم:

 
CREATE TABLE contacts 
(
firstName nvarchar(30),
lastName nvarchar(30),
emailAddress nvarchar(30) CHECK
(dbo.RegExMatch('[a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.)+(com|org|edu)',
emailAddress) = 1),
usPhoneNo nvarchar(30) CHECK
(dbo.RegExMatch(
'\([1-9][0-9][0-9]\) [0-9][0-9][0-9]\-[0-9][0-9][0-9][0-9]',
usPhoneNo)=1),
areaCode AS dbo.ExtractAreaCode(UsPhoneNo) PERSISTED
)

 

به یاد داشته باشید که ستون areaCode، یک ستون دایم و محاسبه شده است، که area code را از ستون usPhoneNo با استفاده از تابع ExtractAreaCode() بیرون می کشد. ستون areaCode ممکن است index شود، و queryها را در جدولی که توسط area code دنبال مخاطبین می گردد، تسهیل می کند.

بطور کلی، این مثال نشان می دهد چگونه می توان از libraryهای .NET Framework برای افزایش T-SQL built-in function library با تابع های مفید که در T-SQL به سختی بیان می شوند، استفاده کرد.

بدست آوردن خروجی

نیاز به بدست آوردن خروجی از یک database object (از قبیل یک stored procedure یا یک view) که درون سرور اجرا می شود، یکی از رایج ترین taskهای database programming است. اگر بتوان خروجی را با استفاده از یک query واحد ایجاد کرد، آنگاه می توان این کار را تنها با استفاده از یک view یا یک inline table-valued function انجام داد. اما، اگر برای ایجاد خروجی، نیاز به عبارات چند گانه و procedural logic باشد، آنگاه دو آپشن وجود دارد: stored procedureها و table-valued functionها. در حالیکه SQL Server 2000 دارای table-valued functionها است، اما می توان آنها را فقط در T-SQL نوشت. با CLR integration در SQL Server 2005، می توان این functionها را با استفاده از یک زبان مدیریت شده (managed language) نیز نوشت. در این بخش، ما نگاهی به چگونگی نوشتن stored procedureها و table-valued functionها با استفاده از CLR می اندازیم.

برگرداندن خروجی نسبی (relational results) از T-SQL، چه به عنوان return value یک table-valued function، چه از طریق "caller's pipe" مستقیم و همیشه حاضر، درون یک stored procedure ممکن است: از هرجایی در یک stored procedure –بدون در نظر گرفتن nesting در execution level– یک عبارت SELECT اجرا شده، خروجی را به caller باز می گرداند.

اگر دقیق تر بگوییم، این درست است که عبارات SELECT، مقدار دهی به متغیررا اجرا نمی کنند. عبارات FETCH، READTEXT، PRINT، و RAISERROR، نیز خروجی را مستقیماً به caller بازمیگردانند.

به یاد داشته باشید که " caller" بدرستی تعریف نشده است؛ بستگی به فراخوانی بافت (invocation context) در stored procedure دارد.

اگر یک stored procedure از هر client data access API(ا زقبیل ODBC، OLEDB، یا SQLClient) فراخوانده شود، caller، API واقعی و هر abstraction (مثلاً، hstmt، IRowset، یا SqlDataReader) که برای ارائه خروجی مهیا می کند، است. بطور کلی، این بدین معناست که خروجی بدست آمده از درون یک stored procedure، به API فراخواننده بازگردانده خواهد شد، و همه frameهای T-SQL روی stack را کنار می گذارد، مانند مثال زیر:

 class="csharpcode">CREATE PROC proc1 AS
SELECT col1 FROM dbo.table1;
CREATE PROC proc2 AS
EXEC proc1;

هنگام اجرای رویه proc2، خروجی بدست آمده از proc1، به caller of proc2 می روند. فقط یک راه هست که در آن proc2 می تواند خروجی بدست آمده را capture کند: از طریق stream کردن خروجی به disk، با استفاده از INSERT یا EXEC، به یک جدول دایم یا موقت، یا یک جدول متغیر.

CREATE PROC proc2 AS
DECLARE @t TABLE(col1 INT);
INSERT @t (col1) EXEC proc1;
-- do something with results

Stored procedureهای SQL Server 2005 CLR، نوع جدیدی از caller را معرفی می کنند. وقتی یک query با استفاده از ADO.NET provider از درون یک frame مدیریت شده اجرا می شود، خروجی از طریق شی SqlDataReader قابل دسترسی می شوند و می توانند درون stored procedure مصرف شوند.

در Visual Basic .NET:


...
Using conn As New SqlConnection("context connection = true")
conn.Open()
Dim cmd As SqlCommand = new SqlCommand( _
"SELECT col1 FROM dbo.table1", conn)
Dim reader As SqlDataReader = cmd.ExecuteReader()

Do While reader.Read()
' Do something with current row
Loop
End Using
...

در C#:

 
...
using (SqlConnection conn= new SqlConnection("contect connection = true"))
{
...
SqlCommand cmd = new SqlCommand(
"SELECT col1 FROM dbo.table1", conn);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
// do something with current row
}
...
}
...

مسئله ای که باقی می ماند، این است که یک managed routine چگونه خروجیش را به caller باز می گرداند. این کار در stored procedureها و table-valued functionها به طور متفاوتی انجام می شود. یک stored procedure از یک مثال static که از کلاس SqlContext قابل دسترسی است، جهت بازگرداندن data استفاده می کند، در حالیکه یک table-valued function، یک interface را پیاده سازی می کند که به SQL Server اجازه بازیابی خروجی را می دهد. هر دوی اینها در قسمت بعدی مورد بحث قرار می گیرند.

Stored Procedureهای CLR و SqlPipe

در میان متدهای دردسترس در کلاس SqlPipe، ساده ترینشان، ExecuteAndSend() است که یک command object را یک پارامترورودی در نظر می گیرد. این متد، command را اجرا می کند، اما به جای اینکه خروجی برای managed frame قابل دسترس کند، خروجی را به فراخواننده stored procedure ارسال می کند. این، معادل قرار دادن یک عبارت درون یک T-SQL stored procedure است، و در حالیکه ناخوشایندتر است، همراه با معادل T-SQL، روی par است.

یک stored procedure ساده برای اجرای یک SELECT در T-SQL:

CREATE PROC proc1 AS
SELECT col1 FROM dbo.table1;

معادلش در C#:

...
[Microsoft.SqlServer.Server.SqlProcedure]
public static void proc1()
{
using (SqlConnection conn =
new SqlConnection("context connection = true"))
{
conn.Open();
SqlCommand cmd = new SqlCommand(
"SELECT col1 FROM dbo.table1", conn);
SqlContext.Pipe.ExecuteAndSend(cmd);
conn.Close();
}
}
...

و در Visual Basic .NET:

...
<Microsoft.SqlServer.Server.SqlProcedure()> _
Public Shared Sub VBproc1()
Using conn As New SqlConnection("context connection=true")
conn.Open()
Dim cmd As SqlCommand = new SqlCommand( _
"SELECT col1 FROM dbo.table1", conn)
SqlContext.Pipe.ExecuteAndSend(cmd)
conn.Close()
End Using
End Sub
...

SqlPipe.ExecuteAndSend()، در سناریوهایی که داده های در شرف بازگشت، مستقیماً توسط یک query اجرا شده بدست می آیند، خوب جواب می دهد. اما، ممکن است مواردی وجود داشته باشد که در آنها، تغییر (manipulation) داده ها قبل از ارسال آنها، یا ارسال داده هایی که از sourceهای خارج از local SQL Server instance بدست آمده، مطلوب باشد.

SqlPipe، گروهی از متدهایی را که با هم کار می کنند تا applicationها را قادر به باز گرداندن خروجی اختیاری به caller، (SendResultsStart()،SendResultsRow()، و SendResultsEnd()) کنند، در اختیار می گذارد. در اکثر موارد، این APIها، شبیه APIهای srv_describe و srv_sendrow هستند که برای extended stored procedureها دردسترس هستند.

SendResultsStart()، یک SqlDataRecord را یک پارامتر ارسالی (argument) در نظر می گیرد و شروع یک نتیجه جدید را نشان می دهد. این API، اطلاعات metadata را از record object می خواند و آن را به caller ارسال می کند. متعاقباً، می توان Rowها را با فراخواندن SendResultsRow()، یکبار برای هر row که در شرف ارسال است، باز گرداند. بعد از اینکه همه rowهای دلخواه ارسال شدند، فراخوانی SendResultsEnd() جهت نشان دادن پایان خروجی، لازم است.

مثال: بازگرداندن یک RSS Feed در یک CLR Stored Procedure

C# code fragment زیر، بخشی از یک stored procedure تشان می دهد که یک XML document را از Web می خواند- یک Really Simple Syndication (RSS) feed از MSDN- و کلاسهای System.Xml برای parse کردن آن استفاده می کند، و اطلاعات را به شکل نسبی باز می گرداند. به یاد داشته باشید که کد باید در یک EXTERNAL_ACCESS یا UNSAFE assembly نمایش داده شود، زیرا permissionهای Code Access Security (CAS) که برای دسترسی به اینترنت ضروری هستند، فقط در این دسته ازpermission ها قابل دسترس هستند.

 
...
using (SqlConnection conn =
new SqlConnection("context connection = true"))
{
// Retrieve the RSS feed
XPathDocument doc = new
PathDocument("http://msdn.microsoft.com/sql/rss.xml");
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator i = nav.Select("//item");

// create metadata for four columns
// three of them are string types and one of the is a datetime
SqlMetaData[] rss_results = new SqlMetaData[4];
rss_results[0] = new SqlMetaData("Title", SqlDbType.NVarChar, 250);
rss_results[1] = new SqlMetaData("Publication Date",
SqlDbType.DateTime);
rss_results[2] = new SqlMetaData("Description",
SqlDbType.NVarChar, 2000);
rss_results[3] = new SqlMetaData("Link", SqlDbType.NVarChar, 1000);

// construct the record which holds metadata and data buffers
SqlDataRecord record = new SqlDataRecord(rss_results);

// cache a SqlPipe instance to avoid repeated calls to
// SqlContext.GetPipe()
SqlPipe sqlpipe = SqlContext.Pipe;

// send the metadata, do not send the values in the data record
sqlpipe.SendResultsStart(record);

// for each xml node returned, extract four pieces
// of information and send back each item as a row
while (i.MoveNext())
{

record.SetString(0, (string)

i.Current.Evaluate("string(title[1]/text())"));
record.SetDateTime(1, DateTime.Parse((string)

i.Current.Evaluate("string(pubDate[1]/text())")));
record.SetString(2, (string)

i.Current.Evaluate("string(description[1]/text())"));
record.SetString(3, (string)

i.Current.Evaluate("string(link[1]/text())"));
sqlpipe.SendResultsRow(record);
}

// signal end of results
sqlpipe.SendResultsEnd();
}
...

به خاطر داشته باشید که بین callها به SendResultsStart() و SendResultsEnd()، عبارت SqlPipe در یک حالت busy تنظیم می شود، وفراخوانی هر متد Send، غیر از SendResultsRow()، منجر به یک error خواهد شد. SendingResults property، در حالیکه SqlPipe در حالت busy است، true تنظیم می شود.

Functionsهای Table-Valued

CLR integration، از functionهای table-valued (TVF) که در managed languageها نوشته شده اند، نیز ساپورت می کند. مانند معادل T-SQL، CLR TVFها نیز ابتداً برای بازگرداندن خروجی جدولی استفاده می شدند. واضح ترین تفاوت این است که T-SQL table-valued function، خروجی را موقتاً در یک جدول ذخیره می کنند، در حالیکه CLR TVF، قابلیت stream کردن خروجی بدست آمده را دارد، بدین معنی که این خروجی نباید قبل از بازگشت از function، materialize شوند.

مثال: یک Table-Valued Function برای بازیابی یک RSS Feed

در اینجا، ما RSS retrieval recast را به عنوان یک table-valued function در C# نشان می دهیم. به تطابق بین SqlFunction annotation در متد RSS_TVF() و امضاء (signature) متد FillTVFRow()، دقت کنید.

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Xml.XPath;
using System.Collections;

public partial class UserDefinedFunctions
{
[SqlFunction(FillRowMethodName = "FillTVFRow",
TableDefinition = "Title nvarchar(250), " +
"PublicationDate datetime, " +
"Description nvarchar(2000), " +
"Link nvarchar(1000)")
]
public static IEnumerable RSS_TVF()
{
return new RssReader();
}

public static void FillTVFRow(object row, out SqlString str,
out SqlDateTime date, out SqlString desc, out SqlString link)
{
// split each object array
object[] rowarr = (object[])row;
str = (SqlString)(rowarr[0]);
date = (SqlDateTime)(rowarr[1]);
desc = (SqlString)(rowarr[2]);
link = (SqlString)(rowarr[3]);
}
}

public class RssReader : IEnumerable
{
XPathDocument doc;
XPathNavigator nav;

// Construct helper class, initializing metadata for the results
// reading from the RSS feed, creating the iterator
public RssReader(string site)
{
// Retrieve the RSS feed
//doc = new XPathDocument("http://msdn.microsoft.com/sql/rss.xml");
doc = new XPathDocument(site);
nav = doc.CreateNavigator();
}

public IEnumerator GetEnumerator()
{
return new RSSEnumerator(this);
}

private class RSSEnumerator : IEnumerator
{
XPathNodeIterator i;
Object[] current;
RssReader reader;

public RSSEnumerator(RssReader reader)
{
this.reader = reader;
Reset();
}

public void Reset()
{
i = reader.nav.Select("//item");
}

public bool MoveNext()
{
if (i.MoveNext())
{
current = new Object[4];
current[0] = new SqlString((string)
i.Current.Evaluate("string(title[1]/text())"));
current[1] = new SqlDateTime(DateTime.Parse((string)
i.Current.Evaluate("string(pubDate[1]/text())")));
current[2] = new SqlString((string)
i.Current.Evaluate("string(description[1]/text())"));
current[3] = new SqlString((string)
i.Current.Evaluate("string(link[1]/text())"));
return true;
}
else return false;

}

public Object Current
{
get
{
return current;
}
}
}
}

یک query ساده برای consume کردن خروجی از این جدول، اینگونه خواهد بود:

SELECT *
FROM RSS_TVF()

طبیعتاً، queryهای قویتر را می توان روی شکل TVF این داده، بیان کرد. با فرض اینکه ما یک تابع CanonicalURL() داریم که نسخه كانونيك یک URL را باز می گرداند، داده هایی از یک RSS feed را می توان با استفاده از URLهای کانونیک به آسانی بازگرداند:

select title, publicationDate, description, dbo.CanonicalURL(link) 
from dbo.RSS_TVF()
order by publicationDate

توجه داشته باشید که در مثال بالا، ما از قابلیتهای stream کردن TVF استفاده نمی کنیم، زیرا ما کل RSS feed را consume می کنیم، یک navigator در بالا درست می کنیم، و سپس آیتمهای مجزا را هنگامی که MoveNext() فراخوانده می شود، تکرار می کند. اما، consume کردن خروجی از یک Web source و تکرارXML تولید شده با یک XmlReader، با استفاده از streaming API ممکن است. قابل ذکر است که با فرض وجود تفاوت مدل اجرا بین table-valued functions در CLR و T-SQ، تفاوتی بزرگ ممکن است مشاهده شود، مخصوصاً در سناریوهایی که در آنها stream کردن خروجی ممکن است.

مثال: crack کردن Scalarها در Rowها

معمولاً در برنامه نیاز داریم که چندین پارامتر ورودی را به صورت همزمان ارسال کنیم. مثلاً، امکان دارد یک order processing system نیاز به یک stored procedure داشته باشد که یک order را در جدول orderها قرار می دهد. امکان دارد یک پارامتر ورودی مطلوب یک stored procedure، در order، line-items باشد؛ اما، این موضوع با این محدودیت روبرو می شود که T-SQL از پارامترهای چندگانه ساپورت نمی کند، و فاقد collectionها و arrayها است.

یک راه حل،encode کردن collection به عنوان یک scalar value است – مثلاً به عنوان یک nvarchar یا xml- و ارسال آن به عنوان یک پارامتر ورودی به stored procedure. Stored procedure می تواند از table-valued function که scalar input را می گیرد و آن را تبدیل به دسته ای از rowها می کند، استفاده می کند، و سپس می تواند در یک جدول line-items یا تغییر یافته (manipulated) قرار گیرد.

در حالیکه table-valued function را می توان در T-SQL نوشت، اما اگر در CLR نوشته شود، performance بهتری خواهد داشت. زیرا می تواند از string manipulation functionها در System.Text namespace استفاده بهینه ببرد، همچنین انجامش هم بسیار ساده است.

مثال زیر، پیاده سازی یک table-valued function را نشان می دهد که یک input string را که توسط یک نقطه ویرگول جدا شده را می گیرد و قطعه ها را به شکل دسته ای از rowها باز می گرداند.

در Visual Basic .NET:

Imports System.Collections
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server

Partial Public Class UserDefinedFunctions

' This needs to return an IEnumerable, but since an array
' does, in this case we do not need to define a new class
' that implements it: we can simply return the array.
<SqlFunction(FillRowMethodName:="FillRow", _
TableDefinition:="value nvarchar(60)")> _
Public Shared Function GetStrings(ByVal str As SqlString) _
As IEnumerable

Return str.Value.Split(";"c)
End Function

' This method does the decoding of the row object. Since the
' row is already a string, this method is trivial. Note that
' this method is pointed to by the annotation on the
' GetString method.
Public Shared Sub FillRow(ByVal row As Object, _
ByRef str As String)

str = CType(row, String)
End Sub

End Class

در C#:

using System.Collections;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
/*
* This needs to return an IEnumerable, but since an array
* does, in this case we do not need to define a new class
* that implements it: we can simply return the array.
*/
[SqlFunction(FillRowMethodName="FillRow",
TableDefinition="value nvarchar(60)")]
public static IEnumerable GetStrings(SqlString str)
{
return str.Value.Split(';');
}

/*
* This method does the decoding of the row object. Since the
* row is already a string, this method is trivial. Note that
* this method is pointed to by the annotation on the
* GetString method.
*/
public static void FillRow(object row, out string str)
{
str = (string)row;
}
}

استفاده از CLR Integration در SQL Server 2005 (قسمت اول)

ارسال شده توسط administrator
27. مي 2010 15:02

خلاصه: این مقاله ویژگیهای جدید CLR integration در SQL Server 2005، را توصیف می کند و توضیح می دهد که چگونه یک database application developer و architectها می توانند از آنها جهت نوشتن رویه ها(procedure)، functionها، و triggerهایی که توسط کاربر تعریف شده اند، استفاده کنند. و همچنین typeها و aggregateهای جدیدی را تعریف می کنند.

ما، برنامه نویسی بر پایه CLR را با مدلهای برنامه نویسی از قبیل Transact-SQL و extended stored procedures، را که در SQL Server ساپورت می شوند، مقایسه ، و نقاط قوت و ضعف هر تکنیک را برجسته می کنیم، و لیستی از رهنمودهای سطح بالا در مورد چگونگی انتخاب alternativeهای در دسترس برای برنامه نویسی را در اختیار می گذاریم. همچنین، گروهی از نمونه کدهایی را که خصوصیات CLR integration را نشان میدهند، نیز در اختیار می گذاریم.

مقدمه

Microsoft SQL Server 2005 به طور چشمگیری مدل database programming را با میزبانی از Microsoft .NET Framework 2.0 Common Language Runtime (CLR) بهبود می بخشد. این موضوع، برنامه نویسان را قادر به نوشتن رویه ها (procedure)، functionها، و triggerها در هر زبان CLR، مخصوصاً Microsoft Visual C# .NET, Microsoft Visual Basic و Microsoft Visual C++ می کند. همچنین به برنامه نویسان اجازه می دهد database را با typeها وaggregateهای جدید، گسترش دهند.

این مقاله چگونگی استفاده بهینه از این تکنولوژی را از نگاه یک database application developer توضیح می دهد، و تکنیکهای CLR integration را با programming language support موجود در SQL Server، Transact-SQL (T-SQL) و stored procedure های گسترش یافته، مقایسه می کند. این مقاله، مرجعی برای propertyها نیست، اطلاعات مربوط به مرجع را می توان در SQL Server 2005 Books Online پیدا کرد. مثالهای این مقاله، CTP آوریل سال 2005 از SQL Server 2005 را در نظر می گیرد.

مخاطبین این مقاله شامل database application developerها، architectها و administratorها می شود. این مقاله، شامل آشنایی موثر با .NET Framework-based programming و database programming می شود.

مرور CLR Integration

آنچه در ادامه می آید، مروریست مختصر بر عملکرد SQL Server که توسط CLR integration فعال شده، و اینکه Visual Studio 2005، چگونه این خصوصیات را ساپورت می کند.

نصب (deployment) دستی

ثبت و اجرای کدهای مدیریت شده در database شامل مراحل زیر می شود:

1. برنامه نویس، یک برنامه مدیریت شده را به عنوان مجموعه ای از تعاریف class می نویسد. Routinesهای SQL Server، stored procedureها، functionها، به عنوان متدهای static (یا shared در Microsoft Visual Basic .NET) یک کلاس نوشته می شوند. Type و aggregateهایی که کاربر تعریف کرده به عنوان کلاسهای کلی نوشته می شوند. برنامه نویس کد را compile و یک assembly ایجاد می کند.

2. Assembly به یک SQL Server database، یعنی جاییکه در کاتالوگهای سیستم، با استفاده از عبارات DDL، ذخیره می شود، آپلود می شود.

3. سپس Objectهای Transact-SQL (T-SQL) از قبیل routineها، typeها و aggregateها ایجاد می شوند و به entry pointهایی که قبلاً آپلود شده اند، محدود می شوند. این روند، با استفاده از عبارات CREATE PROCEDURE/FUNCTION/TRIGGER/TYPE/AGGREGATE به دست می آید.

4. بعد از اینکه routineها ایجاد می شوند، applicationها می توانند آنها را مثل routineهای T-SQL مورد استفاده قرار دهند. مثلاً، می توان CLR functionها را از queryهای T-SQL، و CLR procedureها را از یک client application یا از یک T-SQL batch فرا خواند، گویی procedureهای T-SQL هستند.

ساختن، نصب (deployment)، و debug کردن با استفاده از Visual Studio

Visual Studio 2005 از development، deployment و debug کردن کدهای مدیریت شده در SQL Server 2005 ساپورت می کند. یک SQL Server Project جدید، code templateهایی را که شروع نوشتن کدها برای routineها، typeها و aggregateهای CLR-based database را برای برنامه نویسان آسان می کند، در اختیار می گذارد. همچنین این پروژه به برنامه نویسان اجازه می دهد مرجعهایی (reference) را به assemblyهای دیگری در database اضافه کنند.

زمانیکه یک SQL Server Project ساخته می شود، به یک assembly، compile می شود. Deploy کردن پروژه، assembly binary را در SQL Server database که مربوط به پروژه است، آپلود میکند. همچنین عملیات deploy،routine ها، typeها و aggregateهایی را ایجاد می کند که در database assembly که برپایه attributeهای دلخواه (SqlProcedure, SqlFunction, SqlTrigger) موجود در کد است، تعریف می شوند. همچنین deploy، کدهای منبع و سمبولهای debugging (فایلهای .pdb) را که مربوط به اسمبلی هستد، آپلود می کند.

از آنجاییکه debugging، بخش مهمی از تجربه یک برنامه نویس برای هر platform است، SQL Server 2005 و Visual Studio 2005، database programmerهایی را با چنین قابلیتهایی در اختیار می گذارند. یک ویژگی کلیدی در debug کردن اشیاء در SQL Server 2005، سهولت نصب و استفاده است. اتصال به کامپیوترهایی که SQL Server را run می کنند، ممکن است مانند فرایندهایی که تحت یک سیستم عامل قدیمی اجرا می شوند، debug شوند. نوع اتصال به سروری که client، دارد، عملکرد debugger را تحت تاثیر قرار نمی دهد. هم Tabular Data Stream (TDS) و هم HTTP connectionها ممکن است debug شوند. به علاوه، debug کردن، در همه زبانها، کاملاً یکپارچه عمل می کند و به کاربر می دهد از T-SQL به متدهای CLR یا بالعکس، دست یابد.

CLR و Alternativeهایش

در ارزبابی استفاده از CLR integration، یک برنامه نویس باید آنرا با optionهای دردسترس دیگر مقایسه کند. هدف ما در اینجا، فراهم کردن پایه ای برای این مقایسه است، طوری که بتوان آنرا مقابل تکنیکهای برنامه نویسی کنونی از قبیل: Transact-SQL, extended stored procedures و code in the middle-tier، قرارداد. در این قسمت، ما روی routineهایی که کابر تعریف می کند، تمرکز خواهیم کرد .

CLR در برابر Transact-SQL

Transact-SQL (T-SQL) یک زبان برنامه نویسی ذاتی (native) است که توسط SQL Server ساپورت می شود. مثل اکثر نسخه های SQL، شامل ویژگیهای تغییر داده ها (data manipulation features) و ویژگیهای تعریف داده ها (data definition features) می شود. data manipulation features را می توان به دو گروه کلی تقسیم بندی کرد:

یک declarative query language (تشکیل شده از عبارات SELECT/INSERT/UPDATE/DELETE) و یک procedural language (WHILE, assignment, triggers, cursors و غیره). اگر بخواهیم کلی بگوییم، CLR support در SQL Server، جایزگزینی را برای بخش روندی T-SQL (procedural portion) در اختیار می گذارد.

حتی بدون CLR support نیز، مهم است تشخیص دهیم که database applicationها باید تا آنجا که ممکن است از declarative query language استفاده کنند. این قسمت از زبان، قادر به استفاده از قدرت query processor است که می تواند به نحو احسن، operation bulkها را اجرا و بهینه کند. Database applicationها باید فقط از procedural programming برای بیان منطق (logic) که در query language قابل بیان (express) نیست، استفاده کند.

تمامی اینها، با CLR support در SQL Server، نیز صدق می کند: از CLR نباید برای نوشتن procedural code که با استفاده از ویژگیهای زبان T-SQL، قابل نوشتن هستند، استفاده کرد. برنامه نویسان باید آگاه باشند که تعدادی enhancementهای مهم T-SQL query language در SQL Server 2005 وجود دارند که قدرت T-SQL query language را افزایش می دهند، و باید مطمئن شوند که از همه مزایای آنها قبل از نوشتن procedural code بهره می برند، چه در CLR و چه در جای دیگر. بعضی از این ویژگیهای اضافه شده عبارتند از:

· قابلیت نوشتن Queryهای بازگشتی جهت traverse کردن سلسله مراتب (hierarchy) بازگشتی در یک جدول.

· Functionهای تحلیلی (analytical) جدید از قبیل RANK و ROW_NUMBER که اجازه درجه بندی ردیفها (RANKING ROW) را در لیست خروجی می دهد.

· اپراتورهای نسبی (relational) جدید از قبیل EXCEPT, INTERSECT, APPLY, PIVOT و UNPIVOT.

 

برنامه نویسان باید به CLR به عنوان جایگزینی کارآمد برای منطق (logic) که در query language قابل بیان نیست، نگاه کنند.

بیایید نگاهی به به برخی از سناریوهایی که در آنها برنامه نویسی بر پایه CLR میتواند قدرت T-SQL query language را تکمیل کند بیاندازیم. اغلب، ما نیاز به قراردادن procedural logic درون یک Query که میتوان function نامید، داریم. این، شامل موقعیتهای زیر می شود:

· اجرای محاسبات پیچیده (که باید با استفاده از procedural logic بیان شوند) روی valueهای ذخیره شده در جداول database. این کار مستلزم ارسال خروجی این محاسبات به client، یا استفاده از آنها برای فیلتر کردن دسته ای از ردیفها (row) که به client ارسال می شوند، است؛ مانند مثال زیر:

SELECT <complex-calculation>(<column-name>,...)
FROM <table>
WHERE <complex-calculation>(<column-name>,...) = ...

· استفاده از procedural logic جهت ارزیابی خروجیی که در قسمت FROM در یک عبارت SELECT یا DML، query می شوند. (DML مخفف Data Manipulation Languaga، به معنای زبان تغییر داده ها به دستورات INSERT، UPDATE، و DELETE است)

 

SQL Server 2000، (Function) تابع های T-SQL را معرفی کرد که این سناریو هارا فعال می کند. با SQL Server 2005، این تابع ها، با استفاده از زبانهای CLR، راحتتر نوشته می شوند. زیرا برنامه نویسان می توانند از libraryهای بسیار گسترده در .NET Framework API، بهترین استفاده را ببرند. به علاوه، زبانهای برنامه نویسی CLR، ساختارهای قوی data از قبیل (arrayها، listها، و غیره) رادر اختیار می گذارد که در T-SQL وجود ندارد، و می توانند به علت داشتن مدلهای متفاوت execution در CLR و T-SQL، بهتر اجرا شوند.

بطور کلی، تابعها کاندیدهای خوبی برای نوشته شدن، با استفاده از CLR هستند؛ زیرا نیاز به دسترسی به database از درون یک تابع به ندرت پیش می آید؛ valueهای database، معمولاً تحت عنوان پارامتر ارسالی (argument) ارسال می شوند که در taskهای محاسبه ای، بهتر از T-SQL هستند.

Data Access از CLR

حالا نگاهی به CLR به عنوان یک option برای نوشتن routineهایی که data access اجرا میکند، می اندازیم، هم از منظر مدل برنامه نویسی (programming model) وهم ازمنظر اجرا (performance).

در T-SQL، عبارات query language از قبیل SELECT، INSERT، UPDATE و DELETE، فقط در procedural code قرا می گیرند. ار سوی دیگر، کد مدیریت شده، ازADO.NET data access provider برای SQL Server (SqlClient) استفاده می کنند. در این روش، همه عبارات query language توسط stringهای دینامیک که به متدها و خصوصیات (propert) در ADO.NET API تحت عنوان پارامتر ارسالی (argument) ارسال می شوند.

به خاطر این تفاوت، data access code نوشته شده که از CLR استقاده می کند، می تواند از T-SQL طولانی تر باشد. مهمتر اینکه، از آنجاییکه عبارات SQL در stringهای دینامیک رمزگذاری (encode) می شوند، تا وقتی که اجرا شوند، compile و اعتباردهی (validate) نمی شوند ؛ که هم روی debug کردن کد و هم روی اجرایش (performance) تاثیر می گذارد. اما، مدل برنامه نویسیdatabase با ADO.NET بسیار شبیه مدلی است که در client یا middle-tierها استفاده شده، که هم move کردن کد بین tierها و هم استفاده از مهارتهای موجود را آسانتر می کند.

یه یاد داشته باشید که مدلهای برنامه نویسی بر پایه T-SQL- و هم بر پایه CLR، از یک SQL query language استفاده می کنند؛ تنها procedural portionها فرق دارند.

همانطور که قبلاً هم ذکر شد، کد مدیریت شده، با در نظر گرفتن اکثر محاسباث رویه ای (procedural)، از مزیت "اجرا شدن حتمی" در مقایسه با T-SQL بهره می برد؛ اما برای data-access،T-SQL معمولاً بهتر عمل می کند.

بنابراین، یک قانون رایج خوب این است که "کد حساس به محاسبه و منطق" (computation- and logic-intensive)، انتخاب بهتری برای پیاده سازی در CLR نسبت به کد حساس به data-access است.

بیایید نگاهی به بعضی از الگوهای معمولی در data-access programming بیاندازیم، که توضیح می دهد T-SQL و برنامه نویسی مدیریت شده (managed programming)، که از integrated CLR و ADO.NET استفاده می کند، چگونه در این سناریوها اجرا می شوند.

ارسال خروجی به Client

سناریوی اول ما، مستلزم ارسال دسته ای از ردیفها (row) به Client بدون consume کردن آنها در سرور است. مثلاً، هیچ ردیفی خارج از routine، navigate نمی شود. با T-SQL، وارد کردن یک عبارت SELECT در T-SQL procedure، به اندازه ارسال ردیفهای ایجاد شده توسط SELECT به cleint، موثر است. با کدهای مدیریت شده، شی SqlPipe، برای ارسال خروجی به client استفاده می شود. T-SQL و ADO.NET این سناریو را تقریباً به یک صورت اجرا می کنند.

Submit کردن عبارات SQL

Submit کردن عبارات SQLاز CLR مستلزم traverse کردن لایه های اضافی کد، به منظور switch بین کد مدیریت شده و کد SQL، است. به همین دلیل، T-SQL دارای مزیت performance، هنگام صدور (issue) یک SQL query است. توجه داشته باشید که بعد از submit شدن این عبارت در query processor، فرقی در performance که بر پایه منبع عبارت است، وجود ندارد؛ (چه در T-SQL، چه در کد مدیریت شده). اگر query پیچیده باشد و ارزیابی اش (evaluate) طول بکشد، آنگاه تفاوتها در performance بین T-SQL و کد مدیریت شده، کم اهمیت خواهد بود. به طور خلاصه، queryهای ساده، overhead لایه های اضافی کد می تواند performance یک procedure مدیریت شده را تحت تاثیر قرار دهد.

احتمال دارد stored procedureهای معمولی و حساس به data-access، درگیر submit کردن یک سری (sequence) از عبارات SQL شود. اگر عبارات SQL ساده باشند و زیاد طول نکشند تا اجرا شوند، آنگاه فراخواندن overhead از کد مدیریت شده می تواند بر زمان اجرا تسلط پیدا کند؛ چنین procedureهایی نوشتن در T-SQL را بهتر اجرا می کنند.

Forward-Only, Read-Only Row Navigation (خواندن رکوردها، فقط رو به جلو و فقط خواندنی)

در T-SQL، forward-only, read-only navigation با استفاده از یک cursor پیاده می شود. در کد CLR، این کار با یک SqlDataReader پیاده می شود. معمولاً، مقداری پردازش برای هر قطعه از DATA انجام می شود. اگر این موضوع را نادیده بگیریم، T-SQL یک مزیت دارد، زیرا Row Navigation که از CLR استفاده می کند، کمی آهسته تر از T-SQL است. اما، از آنجاییکه CLR در هر پردازشی که روی data انجام می شود، به طور برجسته ای اجرای (performance) بهتری نسبت به T-SQL دارد، CLR performance، هنگامی که مقدار پردازش افزایش می یابد، جای اجرای T-SQL را می گیرد.

به علاوه، شخص هنگام استفاده از cursorهای T-SQL، باید از پتانسیل امکانات اضافی (latency) آگاه باشد. گرچه بعضی از queryها، الزاماً مقداری latency نشان می دهند، زیرا باید خروجی میانی (intermediate) را ظاهر کنند، cursorهای STATIC و KEYSET همیشه خروجی نهایی را در یک جدول موقت قبل از بدست آوردن هر نتیجه دیگری، ظاهر می کنند. یک cursor می تواند یا مستقیماً با STATIC با KEYSET، تعریف (declare) شود و یا غیر مستقیماً ، به علت برخی ویژگیهای query و data، به یک تبدیل شود. CLR SqlDataReader همیشه خروجی را هنگامی که در دسترس هستند و با پرهیز از این latency تولید می کند.

Row-Navigation با updateها

اگر این مشکل مستلزم update کردن ردیفها (row) برپایه موقعیت کنونی cursor باشد، آنگاه هیچ مقایسه performance مرتبطی وجود نخواهد داشت. این عملکرد در ADO.NET ساپورت نمی شود و باید از طریق cursorهای قابل update در T-SQL انجام شود. اما به خاطر داشته باشید که معمولاً استفاده از عبارات UPDATE برای update کردن ردیفهای خالی بهتر است، و modificationهایی را که بر پایه cursor هستند، برای زمانیکه نمی توان با declarative SQL بیان کرد، ذخیره می کند.

خلاصه

در زیر، خلاصه ای از راهنماییهایی که دیده ایم می توانند در انتخاب CLR یا T-SQL استفاده شوند، آورده شده است:

· هر گاه ممکن است، از عبارات SELECT، INSERT، UPDATE، و DELETE استفاده کنید. پردازش رویه ای (procedural) و پردازش بر پایه row باید فقط هنگامی که logic با استفاده از declarative language قابل بیان نیست، استفاده شود.

· اگر رویه (procedure) ،فقط یک wrapper برای فرمانهای declarative T-SQL است، باید در T-SQL نوشته شود.

· اگر رویه (procedure)، ابتداً مستلزم forward-only, read-only row navigation از طریق یک دسته از خروجی با همان پردازش هر row باشد، احتمالاً استفاده از CLR، بازده بیشتری دارد.

· اگر رویه (procedure)، هم مستلزم data access و هم محاسبه باشد، تقسیم کردن کد رویه ای (procedural code) را در CLR portion که یک T-SQL procedure را جهت اجرای data access یا یک T-SQL procedure را که CLR را جهت اجرای محاسبه فرا می خواند، در نظر بگیرید. راه حل دیگر، استفاده از یک T-SQL batch واحد است که شامل یک سری query است که یک بار از managed code جهت کاهش تعداد round tripهای submit کردن عبارات T-SQL اجرا شده است.

قسمت های بعدی بیشتر به بحث درباره زمان و چگونگی استفاده صحیح از T-SQL و CLR هنگام کار با خروجی می پردازد.

CLR در مقابل XPها

در نسخه های قبلی SQL Server، extended stored procedureها (XPها) تنها جایگزین T-SQL بودند که نوشتن کد سمت سرور (server-side code) به آن سبک بسیار سخت بود. CLR integration، یک جایگزین قویتر برای XPها در اختیار می گذارد. به علاوه، با CLR integration، خیلی از stored procedureها بهتر بیان می شوند و به آنها، با استفاده از query language، اجازه فراخوانده شدن و تغییر (manipulation) می دهد.

بعضی از مزیتهای استفاده از CLR procedureها نسبت به XPها عبارتند از:

· کنترل دانه ای (Granular control): administratorهای SQL Server کنترل کمی روی کارهایی که XPها می تواند یا نمی تواند انجام دهند، دارند. با استفاده از مدل Code Access Security، یک SQL Server administrator می تواند یکی از سه permission bucketهای SAFE، EXTERNAL_ACCESS، یا UNSAFE، را جهت بکار گیری درجات مختلف کنترل عملیاتهایی که کد مدیریت شده اجازه دارد اجرا کند، تعیین کند.

· قابلیت اعتماد (Reliability): کد مدیریت شده، مخصوصاً در permissionهای SAFE و EXTERNAL_ACCESS، مدل برنامه نویسی قابل اعتماد تری را نسبت به XPها در اختیار می گذارد. کدهای مدیریت شدهِ قابل تایید، تضمین می کنند که دسترسی به اشیا از طریق interfaceهایی که احتمال اینکه برنامه به memory bufferهایی که متعلق به SQL Server است، دسترسی پیدا کند یا آنها را مختل کند، کاهش می دهد.

· Data access: با XPها، باید اتصالی مستقیم (loop-back connection) به database ، جهت دسترسی به SQL Server database محلی (local) ایجاد شود. به علاوه، این loop-back connection باید مستقیماً به transaction context های جلسه اصلی (original session) محدود شود تا تضمین کند XP در transaction که در آن فراخوانده می شود، شرکت دارد. کد مدیریت شده CLR می تواند با استفاده از یک مدل برنامه نویسی طبیعی و کارآمدارتر که از connection و transaction context حاضر بیشترین بهره را ببرد، به داده های محلی (local data) دسترسی پیدا کند.

· Data Typeهای اضافی: APIهای مدیریت شده از data typeهای جدید (از قبیل XML، (n)varchar(max)، و varbinary(max)) که در SQL Server 2005 معرفی شدند، ساپورت می کند؛ در حالیکه ODS APIها جهت ساپورت از این typeهای جدید، گسترش نیافته اند.

· قابلیت صعود پذیری (Scalability): APIهایی که در معرض منابعی (resource) از قبیل حافظه، threadها، و همسان سازی (synchronization) هستند، در بالای SQL Server resource manager پیاده می شوند، و به SQL Server اجازه مدیریت این منابع برای کد CLR را می دهد. در مقابل، SQL Server هیچ دید یا کنترلی روی منبع استفاده از یک XP ندارد. اگر یک XP، زمان و حافظه زیادی از CPU را بگیرد، راهی برای شناسایی یا کنترل آن از درون SQL Server وجود ندارد. با کد CLR، SQL Server می تواند پی ببرد که یک thread تعیین شده، به مدت طولانی بازگشتی نداشته و task را مجبور به بازگشت می کند، طوریکه بتوان کار دیگری را برنامه ریزی کرد. در نتیجه، استفاده از کد مدیریت شده، قابلیت صعود (scalability) و قوت (robustness) بهتری را در اختیار می گذارد.

همانطور که در بالا اشاره شد، برای data access و ارسال خروجی به client CLR، routineها بهتر از XPها عمل می کنند. برای کدی که مستلزم data access یا ارسال خروجی نیست، مقایسه اجرای XPها (performance) و کد مدیریت شده، مانند مقایسه کد مدیریت شده با native code است. معمولاً، کد کنترل شده نمی تواند بر اجرای native code در این سناریوها فایق آید. علاوه بر این، یک cost اضافی نیز در زمان انتقال از کد مدیریت شده به کد native، هنگام run شدن درون SQL Server وجود دارد، زیرا SQL Server نیاز به انجام book-keeping روی تنظیمات مخصوص thread هنگام انتقال به native code و بالعکس دارد. در نتیجه، XPها می توانند اجرای (running) کد مدیریت شده را درون SQL Server برای مواردی که انتقالهای (transition) مکرر بین کد مدیریت شده و کد native وجود دارد، بهتر انجام دهد.

در اکثر procedureها، مزیتهای کد مدیریت شده می تواند procedureهای CLR را جایگزین جذابتری نسبت به XPها کند. در مواردی که performance، ابتداً توسط پردازش حساس به محاسبه (computationally-intensive processing) و انتقالات مکرر بین کد مدیریت شده و کد native تعیین شده، مزایای CLR باید در مقابل مزایای اجرای خام XPها (raw performance) سنجیده شود.

کد در Middle Tier

گزینه دیگر برنامه نویسان، قرار دادن logicشان بیرون از database است. این کار به آنها اجازه نوشتن کدشان را در زبانهای انتخابیشان می دهد. با در اختیار گذاشتن یک مدل برنامه نویسی غنی در database، CLR integration به برنامه نویسان امکان منتقل کردن چنین logic را به database می دهد. البته، این بدین معنا نیست که همه یا اکثر کدها باید به database منتقل شوند.

انتقال logic به database tier می تواند مقدار داده ای را که روی network جاری است کاهش دهد، اما باری اضافی روی resourceهای باارزش سی پی یوی سرور می گذارد. این موضوع باید قبل از تصمیم گیری درباره قراردادن کد برای یک application، با دقت مد نظر قرار گیرد. ملاحظات زیر می تواند database را تبدیل به code location ارجح کند:

· اعتبار دهی داده ها (Data validation): نگه داشتن data validation logic در database، امکان بهتر خلاصه کردن این logic را با داده ها می دهد، و از تکثیر شدن validation logic در data touch pointها، از قبیل back-end processing، bulk upload و update کردن داده ها از middle tier و غیره جلوگیری می کند.

· کاهش ترافیک شبکه (Network traffic reduction): ممکن است قراردادن logic در database برای taskهای پردازش داده ها که مستلزم پردازش مقدار زیادی داده است، هنگام تولید درصد کمی از آن داده ها، مناسب باشد. نمونه های معمولی شامل applicationهای تحلیل داده ها از قبیل demand forecasting (پیش بینی تقاضا) و scheduling production (زمانبندی تولید) بر اساس forecast demandها و غیره باشد.

البته این ملاحظات بدون CLR integration هم مهم هستند؛ CLR integration فقط به تضمین اینکه انتخاب زبان برنامه نویسی در تعیین موقعیت مناسب کد دخالت نمی کند، کمک می کند.

مثال: زمانبندی تولید (Production Scheduling)

Production scheduling یک کار عادی در کارخانجات صنعتی است. در سطحی بالا، این کار مستلزم ارائه طرحی برای زمان تولید محصولات به منظور برآورده کردن تقاضا و در عین حال، به حداقل رساندن هزینه کل تولید و انبار کردن محصولات است. الگوریتمهای متعددی وجود دارند که پیش بینی تقاضا، هزینه های نگهداری انبار کالا، و هزینه های راه اندازی خط تولید را به عنوان ورودی، و استراتژی تولید را خروجی در نظر می گیرند.

با فرض اینکه پیش بینی تقاضاهای آینده در جدولی در SQL Server ذخیره می شوند، پیاده سازی چنین الگوریتمی دارای مشخصه های زیر است:

1. مقدار زیادی داده (پیش بینی تقاضا) را ورودی در نظر می گیرد.

2. نتیجه کمی را تولید می کند، مثلاً تعداد واحدهایی که باید در یک زمان یا زمانهای مشخص تولید شوند.

3. مستلزم محاسبات زیاد جهت مشتق کردن خروجی از ورودی است.

پیاده سازی چنین الگوریتمی در middle tier اختیاری است، اما انتقال داده های تقاضا از database، هزینه زیادی دربر دارد. انجام آن در T-SQL به عنوان یک stored procedure نیز شدنی است، اما عدم وجود data typeهای پیچیده پیاده سازی را مشکل می کند، و performance در T-SQL، به علت کمیت و پیچیدگی محاسبات لازم، مسئله ساز خواهد بود. البته، مشخصه های اجرا (performance characteristics) بسته به مقدار واقعی داده ها و پیچیدگی الگوریتم، متنوع خواهد بود.

جهت تایید تطابق CLR integration با چنین سناریویی، ما یک الگوریتم برنامه ریزی تولید—الگوریتم Wagner-Whitin — را در نظر گرفتیم و آنرا با استفاده از هم CLR و هم T-SQL پیاده کنیم. همانطور که انتظار می رفت، CLR integration پیاده سازی خیلی بهتری نسبت به T-SQL داشت. پیاده سازی در C# راحتتر بود، زیرا این الگوریتم از arrayهای تک و چند بعدی که در T-SQL وجود ندارند استفاده می کند. روی هم رفته، نسخه CLR چندین orders of magnitude را بهتر از T-SQL پیاده می کند.

بیایید شکل database ساده زیر را که لیست محصولاتی را که تولید می شوند را نگه می دارد، در نظر بگیریم.

جدول t_products:

شرح نوع نام ستون
مشخصات اولیه یک محصول int Pid
نام محصول nvarchar(256) pName
هزینه ذخیره این محصول در هر دوره int inventoryCost
هزینه راه اندازی خط تولید برای شروع تولید محصول int startupCost

به علاوه، جدول زیر پیش بینی تقاضا برای هر محصول در هر روز را ذخیره می کند. ما فرض می کنیم که ستون pid یک کلید خارجی در جدول t_products است.

جدول t_salesForecast:

شرح نوع نام ستون
مشخصات محصول int Pid
روزی که تقاضای برای آن پیش بینی می شود nvarchar(256) demandDate
پیش بینی تقاضا برای محصول معین شده int demandQty
   

ما یک stored procedure ایجاد کردیم که علاوه بر داده های محصول، پارامترهایی را در نظر می گیرد که دامنه تاریخ هایی را که باید برنامه ریزی تولید ارائه کرد، بیان می کند.

این stored procedure، یک rowset را با schema در جدول زیر باز می گرداند.

شرح نوع نام ستون
نام محصول nvarchar(256) product
روز تولید datetime product
تعداد محصولاتی که قرار است تولید شوند int quantity
   

نسخه C# این کد، در زیر آورده شده تا نوع ستاریویی را که می تواند از CLR integration سود ببرد نشان دهد:

using System; 
using System.Data;
using System.Collections;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

public partial class ProductionScheduler
{
const int MAXNAME = 256; // The maximum name size

[Microsoft.SqlServer.Server.SqlProcedure] // Flag as a SQL procedure
public static void Schedule(SqlDateTime start, SqlDateTime end)
{
// Guarantee that we have a valid connection while we run
using (SqlConnection conn =
new SqlConnection("context connection=true"))
{
conn.Open(); // open the connection
SqlPipe pipe = SqlContext.Pipe; // get the pipe

// Find all the products in the database with any demand
// whatsoever along with data about their production costs.
// Make sure they are ordered.
ArrayList items = new ArrayList();
SqlCommand cmd = new SqlCommand(
" SELECT DISTINCT tp.pid, pname, startupCost,"
" inventoryCost" +
" FROM t_products tp" +
" JOIN t_salesForecast ts" +
" ON tp.pid = ts.pid" +
" ORDER BY pid",
conn);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
items.Add(new Item(
reader.GetInt32(0), reader.GetSqlChars(1),
reader.GetInt32(2), reader.GetInt32(3)));
}
reader.Close();

// Now get all the production schedule information, ordered
// by PID and demand date
"SELECT pid, demandDate, demandQty" +
" FROM t_salesForecast" +
" WHERE demandDate >= @start" +
" AND demandDate <= @end" +
" ORDER BY pid, demandDate",
conn);
cmd.Parameters.AddWithValue("@start", start);
cmd.Parameters.AddWithValue("@end", end);
reader = cmd.ExecuteReader();

// Read each section of schedule information into the items.
reader.Read();
for (int i = 0; (i < items.Count) && (!reader.IsClosed); i++)
{
((Item)(items[i])).readData(reader);
}

// ensure the reader is closed
if (!reader.IsClosed) reader.Close();


foreach (Item item in items)
{
// Compute the schedule and report it
item.ComputeSchedule();
item.OutputSchedule(pipe);
}
}
}

class Item
{
// Information about the product we are scheduling. These will
// be pulled from the database.
private int pid;
private SqlChars name;
private int startCost;
private int holdCost;

// Store how many dates we have.
private int size = 0;

// The dates on which we have demand. These are guaranteed to
// be unique by the database, and we will load them in order.
private ArrayList dates = new ArrayList();
// Store what the demand was on each date.
private ArrayList quantities = new ArrayList();

// Our schedule, which we have not yet computed.
int[] schedule = null;

// Set up the metadata for the return
SqlMetaData[] metadata = new SqlMetaData[] {
new SqlMetaData("product", SqlDbType.NVarChar, MAXNAME),
new SqlMetaData("period", SqlDbType.DateTime),
new SqlMetaData("quantity", SqlDbType.Int)
};

public Item(int pid, SqlChars name, int startCost, int holdCost)
{
this.pid = pid;
this.name = name;
this.startCost = startCost;
this.holdCost = holdCost;
}

/*
* Read data from the stream until the PID does not match
* ours anymore. We assume the reader is cued up to our
* information and we leave it cued to the next item's
* information UNLESS there is no more information, in which
* case we close the reader to indicate as much.
*/
public void readData(SqlDataReader reader)
{
size = 0;
do
{
if (reader.GetInt32(0) == pid)
{
size++;
dates.Add(reader.GetDateTime(1));
quantities.Add(reader.GetInt32(2));
}
else
{
return;
}
}
while (reader.Read());

// reader ran out. close it.
reader.Close();
}

/*
* This method is called to compute the production schedule
* for the item. It does no I/O, but puts in motion the
* dynamic programming algorithm which produces the schedule.
*/
public void ComputeSchedule()
{
int[] days = ComputeProductionDays();
schedule = new int[size];
for (int i = 0; i < size; i++)
{
schedule[days[i]] += (Int32)(quantities[i]);
}
}

/*
* Pipe the schedule to the user, computing it if need be.
*/
public void OutputSchedule(SqlPipe pipe)
{
// Ensure that the schedule has been computed.
if (schedule == null)
{
ComputeSchedule();
}

// Make a record in which to store the data.
SqlDataRecord record = new SqlDataRecord(metadata);
record.SetSqlChars(0, name);

// Start up the output pipe.
pipe.SendResultsStart(record);
for (int i = 0; i < size; i++)
{
// Pipe each day of production. Omit zero production
// days.
if (schedule[i] != 0)
{
record.SetDateTime(1, (DateTime)(dates[i]));
record.SetInt32(2, schedule[i]);
pipe.SendResultsRow(record);
}
}
pipe.SendResultsEnd();
}

/*
* Compute the table and then walk it to find the best
* days to produce the item.
*/
private int[] ComputeProductionDays()
{
// We fill this in. It says when each day's quota is
// actually produced.
int[] productionDays = new int[size];

// First, compute the table.
int[][] table = ComputeTable();

// Then walk the table, creating a second table which encodes
// the best production days.
int[] optimal = new int[size + 1];
int[] optimalLoc = new int[size];
optimal[size] = 0;
for (int i = size - 1; i >= 0; i--)
{
int min = table[i][i] + optimal[i + 1];
int minloc = i;
for (int j = i+1; j < size; j++)
{
int temp = table[i][j] + optimal[j + 1];
if (temp < min)
{
min = temp;
minloc = j;
}
}
optimal[i] = min;
optimalLoc[i] = minloc;
}

// Finally, decode the optimal values into production days.
int pday = 0;
int until = optimalLoc[0] + 1;
for (int i = 0; i < size; i++)
{
if (until == i)
{
pday = i;
until = optimalLoc[i] + 1;
}
productionDays[i] = pday;
}

// We now have a list of days which we will produce the good.
return productionDays;
}

/*
* The main part of the dynamic programming solution. Each entry
* table[i,j] stores the cost of producing enough of the good on
* day i to meet needs through day j. This table is only half-
* filled when complete.
*/
private int[][] ComputeTable()
{
int[][] table = new int[size][];
for (int i = 0; i < size; i++) table[i] = new int[size];
for (int i = 0; i < size; i++)
{

// If we produce the good on the same day we ship it we
// incur a startup cost.
table[i][i] = startCost;

// For other days, we have the cost for the previous
// cell plus the cost of storing the good for this long.
for (int j = i + 1; j < size; j++)
{
table[i][j] = table[i][j - 1] +
(((int)quantities[j]) * holdCost *
diff((DateTime)(dates[i]), (DateTime)(dates[j])));
}
}
return table;
}

/*
* A utility to compute the difference between two days.
*/
private int diff(DateTime start, DateTime end)
{
TimeSpan diff = end.Subtract(start);
return diff.Days;
}

}
};

کدهای مربوط به یک Grid در #C

ارسال شده توسط administrator
24. مي 2010 17:29

clip_image002

مقدمه

SourceGrid یک کنترل windows form است که کلاً در c# نوشته می شود، هدف من ایجاد یک grid ساده اما انعطاف پذیر جهت استفاده در همه مواردی است که برای نمایش یا تغییر داده ها در یک table format لازم است. کنترلهای زیادی از این نوع در دسترس هستند، اما معمولاً سنگین هستند و به سختی customize می شوند، یا با .NET سازگار نیستند. به نظر من Microsoft DataGridخیلی DataSet گرا است، و بنابراین نتایج معمولاً برای استفاده در مواردی که source data، یک DataSet نیست و به اندازه کافی قابل customize نیست، پیچیده می شوند.

این کنترل توسط با Microsoft Framework. NET 1.1، و reference the assembly SourceLibrary.dll 1.2.0.0، compile می شود؛ این، یک library کوچک با کاربرد عادی است. جهت استفاده از SourceGrid، داشتن Visual Studio.NET 2003 یا یک محیط development سازگار، لازم است.

در این مقاله می خواهم چشم اندازی از استفاده و کاربرد کنترل SourceGrid ارایه کنم، جهت جزییات بیشتر در مورد کلاسها، propertyها، یا متدها، می توانید از documentation کمک بگیرید.

استفاده از SourceGrid

در assembly SourceGrid2.dll حاضر هستند، 2 کنترل که می توان درجعبه ابزار Visual Studio قرار داد و در هر formی استفاده کرد:

GridVirtual – یک گرید از سلولهای مجازی (ICellVirtual).

Grid – یک گرید از سلولهای واقعی ( ICell).

بنابراین دو تفاوت اساسی وجود دارد: سلولهای مجازی و سلولهای واقعی.

سلولهای مجازی، سلولهایی هستند که ظاهر و رفتار سلول را تعیین می کنند، اما شامل value نمی شوند، سلولهای واقعی، دارای همان ویژگی های سلولهای مجازی هستند، اما value را هم در بر می گیرند. بنابراین مربوط به موقعیت معینی از grid می شوند.

clip_image003

هر سلول از سه بخش اصلی تشکیل شده است:

DataModel: DataModel، کلاسی است که valueی سلول را مدیریت می کند. Valueی سلول را به یک string برای نمایش بصری (visual representation) تبدیل می کند، editor سلول را ایجاد می کند و valueهای insert شده را اعتبار سنجی می کند.

VisualModel: VisualModel، کلاسی است که سلول را ترسیم می کند و دربر گیرنده propertyهای visual است.

BehaviorModel: BehaviorModel کلاسی است که رفتار سلول را تعیین می کند.

این subdivision، انعظاف و قابلیت استفاده مجدد به code می دهد، و در زمان صرفه جویی می کند. برای موارد رایج تر، کلاسهایی وجود دارند که قبلاً مدیریت و پیکربندی شده اند، اما با خطهای کم کد، ایجاد سلولهای customize شده، ممکن است.

Grid

اگر می خواهید انعطاف و سادگی بیشترین را بدون سلولهای زیاد می خواهید، کنترل Grid ایده آل است. در واقع، در این کنترل، همه سلولها توسط یک کلاس .NET ارایه می شود و بنابراین مقدار معینی از منابع را اشغال می کنند. به علاوه، این تنها gridیی است که ویژگی های RowSpan و ColumnSpan را ساپورت می کند.

بعد از insert کردن کنترل در فرم، می توانیم نوشتن کدمان را جهت استفاده از grid در رویداد Load فرم شروع کنیم:

grid1.Redim(2, 2);
grid1[0,0] = new SourceGrid2.Cells.Real.Cell("Hello from Cell 0,0");
grid1[1,0] = new SourceGrid2.Cells.Real.Cell("Hello from Cell 1,0");
grid1[0,1] = new SourceGrid2.Cells.Real.Cell("Hello from Cell 0,1");
grid1[1,1] = new SourceGrid2.Cells.Real.Cell("Hello from Cell 1,1");

 

کد قبلی یک جدول با 2 خط و دو ستون ایجاد می کند (متد Redim) و همه positionها را با یک سلول اشغال می کند. از از فضای اسمی SourceGrid2.Cells.Real استفاده کرده ام که در همه سلولهای واقعی وجود دارد.

همه سلولها، تمامی خصوصیات لازم نمایش را دارند، مثلاً جهت تغییر رنگ پیش زمینه سلول می توانیم بنویسیم:

SourceGrid2.Cells.Real.Cell l_Cell = new SourceGrid2.Cells.Real.Cell(
"Custom back color");
l_Cell.BackColor = Color.LightGreen;
grid1[0,0] = l_Cell;

 

اینها، خصوصیات ظاهری یک سلول هستند: BackColor، ForeColor، Border, Font، TextAlignment، WordWrap.

حالا سعی می کنیم یک grid کلی با header، و automatic sort، و resize ستونها با ماوس، رشته (string)، و ویرایشگر DateTime و یک checkbox ایجاد کنیم.

grid1.BorderStyle = BorderStyle.FixedSingle;
grid1.ColumnsCount = 3;
grid1.FixedRows = 1;
grid1.Rows.Insert(0);
grid1[0,0] = new SourceGrid2.Cells.Real.ColumnHeader("String");
grid1[0,1] = new SourceGrid2.Cells.Real.ColumnHeader("DateTime");
grid1[0,2] = new SourceGrid2.Cells.Real.ColumnHeader("CheckBox");
for (int r = 1; r < 10; r++)
{
grid1.Rows.Insert(r);
grid1[r,0] = new SourceGrid2.Cells.Real.Cell("Hello "
+ r.ToString(), typeof(string));
grid1[r,1] = new SourceGrid2.Cells.Real.Cell(
DateTime.Today, typeof(DateTime));
grid1[r,2] = new SourceGrid2.Cells.Real.CheckBox(true);
}
grid1.AutoSizeAll();

 

در کد قبلی، من grid border، تعداد ستونها، تعداد ردیفهای ثابت را تنظیم و اولین ردیف header را ایجاد کرده ام. برای header، من از یک سلول ColumnHeader استفاده کرده ام. کلاس Cell به طور اتوماتیک، یک editor مناسب برای نوع داده ای معین، ایجاد می کند. برای آخرین ستون، من از یک سلول CheckBox استفاده کرده ام که یک checkbox را مستقیماً روی سلول نمایش میدهد. این فرم باید شبیه figure زیر باشد:

clip_image001

GridVirtual

کنترل GridVirtual، وقتی نمایش سلولهای زیاد واجب است و وقتی ساختاری

این نوع grid، دارای همان ویژگی های کنترل Grid است، به غیر از automatic sort (زیرا grid نمی تواند هیچ ساختار داده ای را بدون کپی کردن محتویاتش مرتب کند) و ویژگی RowSpan و ColumnSpan که اجازه span کردن یک سلول را در سلولهای مجاور دیگر می دهد. عیب دیگر این است که ایجاد یک grid مجازی کمی مشکل است.

مفهوم اصلی یک grid مجازی این است که سلولها، valueها را دربر نمی گیرند، اما value را در یک ساختار داده ای خارجی می خوانند و می نویسند. این ایده با یک کلاس انتزاعی CellVirtual اجرا می شود که در آن تعریف دوباره متدهای GetValue و SetValue لازم هستند.

جهت استفاده از GridVirtual، ایجاد یک کلاس که از CellVirtual مشتق شده و شخصی کردن reading با استفاده از منبع داده انتخاب شده، واجب است. معمولاً بهتر است یک کنترل که از GridVirtual مشتق می شود نیز ایجاد کنیم تا انعطاف بیشتر و کدی solid تر داشته باشیم که متد GetCell را override می کند. اگر مایل باشید، می توانید مستقیماً از کنترل GridVirtual و رویداد GettingCell استفاده کنید. هدف متد GetCell و رویداد GettingCell، بازگرداندن سلول انتخابی است. این کار انعطاف زیادی را ایجاد می کند، زیرا می توانید برای یک نوع داده ای معین، هر ICellVirtual را بازگردانید؛ مثلاً می توانید سلول یک type header را وقتی که ردیف 0 است، بازگردانید.

در مثال زیر، من یک grid مجازی ایجاد می کنم که valueها را در یک آرایه می نویسد. ابتدا، کنترل GridVirtual را در یک فرم insert می کنم، سپس این کد را که کلاس مجازی ما را تعریف می کند، می نویسم:

public class CellStringArray : SourceGrid2.Cells.Virtual.CellVirtual
{
private string[,] m_Array;
public CellStringArray(string[,] p_Array):base(typeof(string))
{
m_Array = p_Array;
}
public override object GetValue(SourceGrid2.Position p_Position)
{
return m_Array[p_Position.Row, p_Position.Column];
}
public override void SetValue(SourceGrid2.Position p_Position,
object p_Value)
{
m_Array[p_Position.Row, p_Position.Column] = (string)p_Value;
OnValueChanged(new SourceGrid2.PositionEventArgs(p_Position, this));
}
}

 

با کد قبلی، من یک سلول مجازی با یک editor از نوع string ایجاد کردم که valueها را در یک آرایه، که در constructor معین شده، می خواند و می نویسد. بعد از فراخوانی متد SetValue، باید متد OnValueChangedرا فرابخوانیم تا grid را از update کردن این سلول آگاه کنیم.

من کد زیر را در در رویداد Load در Form قرار داده ام:

private void frmSample15_Load(object sender, System.EventArgs e)
{
gridVirtual1.GettingCell += new SourceGrid2.PositionEventHandler(
gridVirtual1_GettingCell);
gridVirtual1.Redim(1000,1000);
string[,] l_Array = new string[gridVirtual1.RowsCount,
gridVirtual1.ColumnsCount];
m_CellStringArray = new CellStringArray(l_Array);
m_CellStringArray.BindToGrid(gridVirtual1);
}

من یک event handler به رویداد GettingCell اضافه کرده ام، و grid وآرایه را با 1000 ردیف و 1000 ستون ایجاد کردم، سپس مثال جدیدی از سلول قبلی ایجاد کردم و با استفاده از متد BindToGrid، سلول را به grid لینک کردم. یک سلول واحد ایجاد کردم که برای هر موقعیت matrix استفاده خواهد شد. فراخوانی متد BindToGrid روی سلولهایی که می خواهیم در یک grid مجازی استفاده کنیم، همیشه لازم است.

جهت اتمام کار، باید متد GettingCell را بنویسیم و متغیر را برای سلول تعریف کنیم:

private CellStringArray m_CellStringArray;
private void gridVirtual1_GettingCell(object sender,
SourceGrid2.PositionEventArgs e)
{
e.Cell = m_CellStringArray;
}

 

نتیجه حاصله باید شبیه تصویر زیر باشد:

clip_image001[5]

VisualModel

فضای نامی: SourceGrid2.VisualModels

همه سلولها یک property دارند که یک رابط type IvisualModel را باز می گرداند. سلول از این رابط (Interface) جهت ترسیم و سفارشی کردن ویژگی های ظاهری (visual properties) سلول استفاده می کند.

هدف VisualModel، جدا سازی کد drawing از بقیه کد است و اجازه به اشتراک گذاشتن همان مدل visual بین سلولهای بیشتری است. در حقیقت،می توان از همان مثال VisualModel روی سلولهای بیشتری به طور همزمان استفاده کرد تا منابع سیستم را بهینه کند. کلاسهای پیش فرض VisualModel، فقط خواندنی (read-only) هستند، اما یک متد Clone، که به شما اجازه می دهد نمونه های همسانی از همان مدل را ایجاد کنید، در اختیار هر VisualModel قرار می گیرد.

در زیر کلاسهای پیش فرض VisualModel در namespace آورده شده است.

  • SourceGrid2.VisualModels.Common: برای سلولهای کلاسیک استفاده می شود. در این مدل، شما می توانید رنگها، فونت، borderها و propertyهای زیاد دیگری را customize کنید.
  • SourceGrid2.VisualModels.CheckBox*: برای سلولهای checkbox style استفاده می شود. می توان checkbox را انتخاب، و غیر فعال (disable) کرد و می تواند یک caption داشته باشد.
  • SourceGrid2.VisualModels.MultiImages: اجازه ترسیم بیشتر از یک image را در یک سلول می دهد.

VisualModel که با ستاره مشخص شده است، نیاز به یک رابط (interface) دارد که درست کار کند، مثلاً مدل CheckBox، نیاز به یک سلول دارد تا رابط ICellCheckBox را ساپورت کند.

هر یک از این کلاسها شامل یک یا چند property ثابت با چند نمونه فقط خواندنی است که براحتی قابل استفاده هستند.

· SourceGrid2.VisualModels.Common.Default

· SourceGrid2.VisualModels.Common.LinkStyle

· SourceGrid2.VisualModels.CheckBox.Default

· SourceGrid2.VisualModels.CheckBox.MiddleLeftAlign

· SourceGrid2.VisualModels.Header.Default

· SourceGrid2.VisualModels.Header.ColumnHeader

· SourceGrid2.VisualModels.Header.RowHeader

این کد چگونگی تخصیص دادن همان VisualModel را به سلولهای بیشتری که قبلاً ایجاد شده اند، نشان می دهد و سپس بعضی از propertyها را تغییر می دهد:

 

SourceGrid2.VisualModels.Common l_SharedVisualModel = 
new SourceGrid2.VisualModels.Common();
grid1[0,0].VisualModel = l_SharedVisualModel;
grid1[1,0].VisualModel = l_SharedVisualModel;
grid1[2,0].VisualModel = l_SharedVisualModel;
l_SharedVisualModel.BackColor = Color.LightGray;

 

در نظر داشته باشید وقتی Cell.BackColor را می نویسید، property، به طور اتوماتیک، property of the VisualModel associated را فرا می خواند. جهت تسهیل استفاده از propertyهای رایج تر، اگر بنویسید: Cell.BackColor = Color.Black، سلول به طور اتوماتیک، VisualModel کنونی را clone می کند، backcolor را به رنگ نمونه clone شده تغییر می دهد و نمونه clone شده را به سلول اختصاص می دهد.

DataModel

فضای نامی: SourceGrid2.DataModels

برای نشان دادن valueی یک سلول در یک string format و فراهم کردن یک cell data editor، لازم است property DataModel سلول را populate کنیم. اگر این property، خالی باشد، امکان تغییر valueهای property سلول وجود ندارد و تبدیل رشته (string conversion)، یک ToString ساده value خواهد بود.

یک DataModel معمولاً از یک TypeConverter از نوع داده ی فراخوانده شده جهت مدیریت تبدیل های لازم استفاده می کند، مخصوصاً از تبدیل رشته (که برای نمایش cell value استفاده می شود)

اینها، کلاسهای پیش فرض DataModel در فضای اسمی SourceGrid2.DataModels هستند:

DataModelBase: متد تبدیل را فراهم می کند و اجازه تغییر cell value را فقط توسط کد می دهد، و graphic interface را فراهم نمی کند. این کلاس، پایه همه editorهای دیگری است و همچنین برای مدیریت سلولهای فقط خواندنی بکار می رود، اما با formattingهای customize شده و editorهای مخصوص (مانند CheckBox) سلول از یک editor فقط خواندنی استفاده می کند، زیرا value با کلیک مستقیم روی checkbox تغییر می کند.

EditorControlBase: یک کلاس انتزاعی (Abstract Class) که به استفاده از یک کنترل به عنوان editor سلول کمک می کند.

:EditorTextBox یک TextBox editor. این، کلاسی است که تمامی typeهایی که string conversion را ساپورت می کنند، از آن زیاد استفاده می کنند.

EditorComboBox: یک ComboBox editor

EditorDateTime: یک DateTimePicker editor

EditorNumericUpDown: یک UpDown editor شماره ای

EditorTextBoxButton: امکان edit کردن سلول همه typeهایی را که دارای یک UITypeEditor هستند، فراهم می کند.

یک DataModel را می توان بین سلولهای بیشتری به اشتراک گذاشت، مثلاً می توانید از یک DataModel برای همه سلولهای یک ستون استفاده کرد.

دو راه برای ایجاد یک سلول قابل ویرایش، وجود دارد:

سلول را ایجاد و value type را مشخص کنید.در این حالت، سلول به طور اتوماتیک یک utility function (Utility.CreateDataModel) را فرا می خواند که یک DataModel را در پایه به type مشخص شده باز می گرداند.

grid1[0,0] = new SourceGrid2.Cells.Real.Cell("Hello", 
typeof(string));

 

DataModel را به صورت جداگانه ایجاد کنید و آن را به سلولها تخصیص دهید.

SourceGrid2.DataModels.EditorTextBox l_TextBox =
new SourceGrid2.DataModels.EditorTextBox(typeof(string));
l_TextBox.MaxLength = 20;
l_TextBox.AttachEditorControl(grid1);
l_TextBox.GetEditorTextBox(grid1).CharacterCasing =
CharacterCasing.Upper;
grid1[2,0].DataModel = l_TextBox;

بعضی از propertyها تا سطح یک DataModel تعریف می شوند، در حالیکه propertyهای دیگر تا سطح یک editor control، در این مورد، property CharacterCasing، تا سطح یک کنترل TextBox تعریف می شود. بنابراین برای استفاده از این propertyها، force کردن یک linking of the editor به grid با متد AttachEditorControl، و سپس فراخوانی متد GetEditorTextBox جهت باز گرداندن نمونه TextBox ، واجب است. این مکانیزم برای ایجاد کردن ویرایشگر مخصوص مانند ComboBox editor نیز مفید است. برای insert کردن یک ComboBox، باید کد زیر را بنویسید:

SourceGrid2.DataModels.EditorComboBox l_ComboBox = 
new SourceGrid2.DataModels.EditorComboBox(
typeof(string),
new string[]{"Hello", "Ciao"},
false);
grid1[3,0].DataModel = l_ComboBox;

 

البته، ایجاد ویرایشگر DataModel با کنترل سفارشی یا با رفتار مخصوص نیز ممکن است. در تصویر زیر، مشاهده بیشتر ویرایشگرهای دردسترس و بعضی از optionها مانند image properties ممکن است:

clip_image001[7]

BehaviorModel

فضای نامی: SourceGrid2.BehaviorModels

هر سلول دارای یک BehaviorModel است که می توانید آن را با Behaviors property بخوانید. BehaviorModel کلاسی است که رفتار سلول را تعیین می کند. می توان مدلی را را بین سلولهای بیشتری به اشتراک گذاشت و اتعطاف پذیری و سادگی هر ویژگی جدید را امکان پذیر می کند.

اینها، کلاسهای پیش فرض type BehaviorModel هستند:

SourceGrid2.BehaviorModels.Common: رفتار عادی یک سلول

SourceGrid2.BehaviorModels.Header: رفتار یک row header، با ویژگی resize کردن

SourceGrid2.BehaviorModels.ColumnHeader: رفتار یک column header، با ویژگی sort و resize کردن. (به ICellSortableHeader نیاز دارد)

SourceGrid2.BehaviorModels.CheckBox: رفتار یک CheckBox. (به ICellCheckBox نیاز دارد)

SourceGrid2.BehaviorModels.Cursor: اجازه لینک کردن یک cursor را به یک سلول معین می دهد. (به ICellCursor نیاز دارد)

SourceGrid2.BehaviorModels.Button: رفتا ریک Button

SourceGrid2.BehaviorModels.Resize: اجازه resize شدن را با ماوس به سلول می دهد. (مدلهای header، به طور اتوماتیک از این مدل استفاده می کنند)

SourceGrid2.BehaviorModels.ToolTipText: اجازه نمایش یک ToolTipText لینک شده به یک سلول را می دهد. (به ICellToolTipText نیاز دارد)

SourceGrid2.BehaviorModels.Unselectable: نمی گذارد یک سلول، focus را دریافت کند.

SourceGrid2.BehaviorModels.ContextMenu: اجازه نمایش یک context menu لینک شده به یک سلو ل را می دهد. (به ICellContextMenu نیاز دارد)

SourceGrid2.BehaviorModels.CustomEvents: لیستی از رویدادها را که می توان بدون مشتق کردن از یک BehaviorModel، استفاده کرد، expose می کند.

SourceGrid2.BehaviorModels.BindProperty: اجازه لینک کردن valueی یک سلول را به یک property خارجی می دهد.

SourceGrid2.BehaviorModels.BehaviorModelGroup: اجازه ایجاد یک BehaviorModel را می دهد که به طور اتوماتیک لیستی از BehaviorModel را فرا می خواند. زمانی مفید است که یک رفتار، به رفتارهای دیگر نیاز دارد تا درست کار کند.

BehaviorModel که با علامت ستاره مشخص شده است، نیاز به سلولهای مخصوصی دارد تا بتواند وظایفش را اجرا کند، مثلاً، کلاس CheckBox، نیاز به سلولی دارد که interface IcellCheckBox را ساپورت کند.

هر کلاس دارای چند property ثابت است که یک instance پیش فزض را باز می گرداند:

SourceGrid2.BehaviorModels.Common.Default

SourceGrid2.BehaviorModels.Button.Default

SourceGrid2.BehaviorModels.CheckBox.Default

SourceGrid2.BehaviorModels.ColumnHeader.SortableHeader

SourceGrid2.BehaviorModels.ColumnHeader.NotSortableHeader

SourceGrid2.BehaviorModels.Cursor.Default

SourceGrid2.BehaviorModels.Header.Default

SourceGrid2.BehaviorModels.Resize.ResizeHeight

SourceGrid2.BehaviorModels.Resize.ResizeWidth

SourceGrid2.BehaviorModels.Resize.ResizeBoth

SourceGrid2.BehaviorModels.RowHeader.Default

SourceGrid2.BehaviorModels.ToolTipText.Default

SourceGrid2.BehaviorModels.Unselectable.Default

در کد زیر، من یک BehaviorModel ایجاد کرده ام که backcolor سلول را هنگامی که کاربر ماوس را روی آن قرار میدهد، تغییر می دهد:

 

public class CustomBehavior : SourceGrid2.BehaviorModels.BehaviorModelGroup
{
public override void OnMouseEnter(SourceGrid2.PositionEventArgs e)
{
base.OnMouseEnter (e);
((SourceGrid2.Cells.Real.Cell)e.Cell).BackColor = Color.LightGreen;
}
public override void OnMouseLeave(SourceGrid2.PositionEventArgs e)
{
base.OnMouseLeave (e);
((SourceGrid2.Cells.Real.Cell)e.Cell).BackColor = Color.White;
}
}

 

برای استفاده از این BehaviorModel، این کد را در رویداد Load یک فرم، insert کنید:

grid1.Redim(2,2);

CustomBehavior l_Behavior = new CustomBehavior();
for (int r = 0; r < grid1.RowsCount; r++)
for (int c = 0; c < grid1.ColumnsCount; c++)
{
grid1[r,c] = new SourceGrid2.Cells.Real.Cell("Hello");
grid1[r,c].Behaviors.Add(l_Behavior);
}

سلولها

فضای نامی: SourceGrid2.Cells

اینها، سلولهای پیش فرض در دسترس هستند:

SourceGrid2.Cells.Virtual: این فضای نامی، حاوی تمامی سلولهای مجازی است که می توان با یک کنترل GridVirtual استفاده کرد، همه اینها، سلولهای انتزاعی (abstract) هستند و باید از این سلولها مشتق شوند تا بتوانید از منبع داده سفارشی تان استفاده کنید.

CellVirtual: سلول پایه همه پیاده سازی های دیگر (implementation)، برای رایج ترین نوع سلولهای مجازی استفاده میز شود.

Header: یک سلول header.

ColumnHeader: یک سلول column header.

RowHeader: یک سلول row header.

Button: یک سلول button

CheckBox: یک سلول checkbox

ComboBox: یک سلول combobox

Link: یک سلول link style

SourceGrid2.Cells.Real: این فضای نامی حاوی تمامی سلولهای واقعی است می توان با یک کنترل Grid استفاده کرد.

Header: یک سلول header.

ColumnHeader: یک سلول column header.

RowHeader: یک سلول row header.

Button: یک سلول button

CheckBox: یک سلول checkbox

ComboBox: یک سلول combobox

Link: یک سلول link style

هدف این کلاسها، ساده کردن استفاده از VisualModel، DataModel، و BehaviorModel است. اگر ما نگاهی به کد هر کدام از این کلاسها بیاندازیم، می ببنینم که این کلاسها از مدلهای قبلی استفاده می کنند. اما مدلهایی وجود دارند که به interface های مخصوصی نیاز دارند و در این مورد، همه interface های لازم را انجام می دهد. مثلاً کد زیر، کد سلول SourceGrid2.Cells.Real.CheckBox است:

public class CheckBox : Cell, ICellCheckBox
{
public CheckBox(string p_Caption, bool p_InitialValue)
{
m_Caption = p_Caption;

DataModel = new SourceGrid2.DataModels.DataModelBase(typeof(bool));
VisualModel = SourceGrid2.VisualModels.CheckBox.MiddleLeftAlign;
Behaviors.Add(BehaviorModels.CheckBox.Default);

Value = p_InitialValue;
}
public bool Checked
{
get{return GetCheckedValue(Range.Start);}
set{SetCheckedValue(Range.Start, value);}
}
private string m_Caption;
public string Caption
{
get{return m_Caption;}
set{m_Caption = value;}
}
public virtual bool GetCheckedValue(Position p_Position)
{
return (bool)GetValue(p_Position);
}
public virtual void SetCheckedValue(
Position p_Position, bool p_bChecked)
{
if (DataModel!=null && DataModel.EnableEdit)
DataModel.SetCellValue(this, p_Position, p_bChecked);
}
public virtual CheckBoxStatus GetCheckBoxStatus(Position p_Position)
{
return new CheckBoxStatus(DataModel.EnableEdit,
GetCheckedValue(p_Position), m_Caption);
}
}

همانطور که می بینید، کلاس CheckBox، فقط از مدلهای SourceGrid2.DataModels.DataModelBase(typeof(bool))، استفاده می کند. SourceGrid2.VisualModels.CheckBox.MiddleLeftAlign e BehaviorModels.CheckBox.Default، رابط ICellCheckBoxرا با متد GetCheckBoxStatus اجرا می کند. متدهای Checked، Caption، GetCheckedValue، و SetCheckedValue، متدهایی هستند که ویرایش valueی سلول را ساده می کنند.

ساختار Grid

ردیفها و ستونها (Rows and Columns)

اجزای اصلی یک grid، ردیفها و ستونها هستند. جهت تغییر دادن این اطلاعات، SourceGrid، دو property فراهم می کند:

Rows: مجموعه ای از type RowInfoCollection را بازمی گرداند که یک strip از کلاسهای RowInfo است.

Columns: مجموعه ای از type ColumnInfoCollection را بازمی گرداند که لیستی از کلاسهای ColumnInfo است.

اینها، بعضی از propertyهای کلاس RowInfo هستند: Height ، Top، Bottom،

اینها هم، propertyهای کلاس ColumnInfo هستند: Width، Left، Right، Index، Tag.

راههای زیادی جهت تغییر دادن (manipulate) ردیفها و ستونها وجود دارد:

grid1.Redim(2,2);

grid1.RowsCount = 2;
grid1.ColumnsCount = 2;

 

grid1.Rows.Insert(0);
grid1.Rows.Insert(1);
grid1.Columns.Insert(0);
grid1.Columns.Insert(1);


این سه مثال، تمامی کارهای ایجاد یک جدول با 2 ردیف و 2 ستون را انجام می دهد.

جهت تغییر پهنا یا ارتفاع یک ردیف یا ستون، می توان از این کد استفاده کرد:

grid1.Rows[0].Height = 100;
grid1.Columns[0].Width = 100;

 

propertyهای Top، Bottom، Left و Right، با استفاده از پهنا و ارتفاع ردیفها و ستونها به طور اتوماتیک محاسبه می شوند.

Panelها
برای مدیریت صحیح scrollbarها، ستونها، ردیفهای ثابت، و جزییات دیگر، ساختار panelهای grid داخلی باید مانند شکل زیر باشد:

1.    TopLeftPanel : سلولهای ردیف و ستون ثابت را نگه می دارد.
2.    TopPanel : ردیفهای ثابت را نگه می دارد.
3.    LeftPanel : ستونهای را ثابت نگه می دارد.
4.    ScrollablePanel : سلولهای غیر ثابت را ثابت نگه می دارد.
5.    HScrollBar : ScrollBar افقی
6.    VScrollBar : ScrollBar عمودی
7.    BottomRightPanel : پنل مدیریت فضای کم بین دو scrollbar

رویدادها
رویدادهای ماوس و کیبورد را می توان با یک BehaviorModel استفاده کرد یا می توان مستقیماً به grid وصل کرد. همه رویدادها ابتدا به panelها و سپس به طور اتوماتیک به کنترلهای GridVirtual و Grid،اجرا می شوند. جهت استفاده از این رویدادها می توانید کد زیر را بنویسید:

grid.MouseDown += new System.Windows.Forms.MouseEventHandler(
grid_MouseDown);

می توان این کار را با Visual Studio designer نیز انجام داد.

ContextMenu

Grid، دارای یک ContextMenu پیش فرض است که می توان آنرا با ContextMenuStyle property، سفارشی کرد. می توان یک ContextMenu را به شی Selection با Grid.Selection و ContextMenuItemsمتصل کرد، که برای تمامی سلولهای انتخاب شده قابل استفاده است؛ در غیر این صورت، می توانید یک ContextMenu را مستقیماً به یک سلول معین متصل کرد.

اطلاعات دیگر

Focus و Selection

یک سلول می تواند یا انتخاب شود و یا focus داشته باشد. فقط یک سلول می تواند focus را داشته باشد، که FocusCellPosition property of the grid آن را شناسایی می کند، در عوض، سلولهای زیادی می توانند انتخاب شوند. یک سلول وقتی انتخاب می شود که در Selection object of the grid حاضر باشد. سلول دارای focus، تمامی رویدادهای ماوس و کیبورد را دریافت می کند، در حالیکه سلولهای انتخاب شده اعمالی از قبیل copy و paste را دریافت می کنند.

Position و Range

دو تا از پرکاربردترین اشیاء در پروژه SourceGrid، Position و Range هستند. Position، موقعیتی را با یک ردیف و ستون مشخص می کند، درحالیکه Range، گروهی از سلولهای نامحدود را از یک start Position و یک end Position، شناسایی می کند.

Performance

برای بهینه کردن اجرای این کنترل، از کنترل GridVirtual، هنگامی که نمایش سلولهای زیادی واجب است، استفاده کنیدو همیشه سعی کنید مدلهای DataModel، VisualModel، و BehaviorModel را بین سلولهای بیشتری به اشتراک بگذارید. اجرای (performance)grid ، حتی اگر ترسیم (drawing) کد هنوز قابل بهینه سازی باشد، تقریباً خوب است، مخصوصاً هنگام scroll کردن.

Extensionها

در پروژه SampleProject، مثالها و بخشهای زیادی از کد وجود دارند که می توانند ایده ها و پیشنهادهایی درباره چگونگی پیاده سازی custom grid، ارایه می کنند، بویژه در پوشه Extensionها، gridهایی وجود دارند عملکردهایی مثل bind کردن به یک DataSet (DataTable)، به یک Array، و به یک ArrayList را فراهم می کنند.

Screenshotها

clip_image001[9]

clip_image002[6]

clip_image003[6]

چگونگی انتخاب کل یک ردیف:

grid1.Rows[1].Select = true;

چگونگی انتخاب همه سلولها:

grid1.Selection.AddRange(grid1.CompleteRange);

چگونگی ایجاد یک editor با قانون اعتبارسنجی پیشرفته:

grid1[0,0] = new SourceGrid2.Cells.Real.Cell(2, typeof(int));
grid1[0,0].DataModel.MinimumValue = 2;
grid1[0,0].DataModel.MaximumValue = 8;
grid1[0,0].DataModel.DefaultValue = null;
grid1[0,0].DataModel.AllowNull = true;

چگونگی ایجاد یک ویرایشگر ComboBox برای نمایش یک valueی متفاوت از valueی واقعی استفاده شده.

در این مورد، زمانیکه valueی واقعی یک double است، به صورت یک string نمایش داده می شود.

double[] l_RealValues = new double[]{0.0,0.5,1.0};
SourceGrid2.DataModels.EditorComboBox l_EditorCombo =
new SourceGrid2.DataModels.EditorComboBox(typeof(double));
l_EditorCombo.StandardValues = l_RealValues;
l_EditorCombo.StandardValuesExclusive = true;
l_EditorCombo.AllowStringConversion = false;
SourceLibrary.ComponentModel.Validator.ValueMapping l_Mapping =
new SourceLibrary.ComponentModel.Validator.ValueMapping();
l_Mapping.ValueList = l_RealValues;
l_Mapping.DisplayStringList = new string[]{"Zero", "One Half", "One"};
l_Mapping.BindValidator(l_EditorCombo);
grid1[0,0] = new SourceGrid2.Cells.Real.Cell(0.5, l_EditorCombo);

ویژگی ها

کارهایی که SourceGrid می تواند امجام دهد:

سفارشی کر دن ظاهر گرافیکی، نوع ویرایشگر و رفتار (cursor، tooltiptext، contextmenu) هر سلول.

همه انواع داده هایی را که TypeConvertor یا UITypeEditor دارند، ساپورت می کند.

هر کنترل .NET را می توان مثل editor با خطوط کم کد استفاده کرد.

می توانید ردیف ها و ستون ها را اضافه، حذف و جابجا کنید.

می توان ارتفاع و پهنای هر ستون و ردیف را به طور مستقل سفارشی کرد، و یا می توان به طور اتوماتیک و بر اساس محتویات سلول محاسبه کرد.

ویژگی های RowSpan و ColumnSpan را جهت یکی کردن سلولهای بیشتری ساپورت می کند.

عملیات اتوماتیک Copy و Paste را ساپورت می کند.

column sort را ساپورت می کند.

می توانید پهنا و ارتفاع ستونها و ردیفها را تغییر دهید.

در همه سلولها، سفارشی کردن تصویر و alignment متن و تصویر امکان پذیر است.

متن MultiLine و WordWrap را ساپورت می کند.

خروج (export) HTML را ساپورت می کند.

با بعضی از extensionها، ویژگی های data binding را ساپورت می کند.

سلولهای مجازی که برای binding هر نوع منبع داده استفاده می شود را ساپورت می کند.

و کارهایی که نمی تواند انجام دهد:

SourceGrid، دارای designer نیست، همه را باید با کد انجام داد.

چاپ کردن را هم ساپورت نمی کند.

تغییر دادن کد SourceGrid

تغییر دادن، compile مجدد، و توزیع کنترل SourceGrid، برای استفاده شخصی و تجاری مجاز است.

Developmentهای آینده

بهبود ترسیم کد (drawing code)

ساپورت Masked Edit textbox

مشکلات شناخته شده

ساپورت Cut وجود ندارد

ویرایشگر NumericUpDown، به طور صحیح به سلول align نمی شود.

کلید shift با arrow keys کار نمی کند و سلولهای header هنوز مشکل دارند.

نسخه های قبلی

نسخه 2 SourceGrid، دارای تغییرات زیادی بود، و لیست کردن همه چیز ممکن نبود. حالت استفاده خیلی شیبه است، اما تبدیل یک کد نوشته شده با نسخه های قبلی ساده نیست. چند پیشنهاد وجود دارد:

ویژگی اصلی grid این است که با ICellVirtual interface کار می کند، نه با کلاس Cell. این Interface، فقط حاوی متدهای لازم است و بنابراین ضعیفتر است.

کدی که قبلاً برای این وجود داشت:

grid[0,0] = new SourceGrid.Cell("Ciao");
grid[0,0].BackColor = Color.White;

و حالا باید این گونه باشد:

SourceGrid2.Cells.Real.Cell l_Cell = 
new SourceGrid2.Cells.Real.Cell("Ciao");
l_Cell.BackColor = Color.White;
grid[0,0] = l_Cell;

در نسخه قبلی، سلول پایه از Cell متمایز شد، در حالیکه interface IcellVirtual وجود دارد و تغییر مهم این کلاس این است که اطلاعات در مورد موقعیت ردیف و ستون را در بر نمی گیرد.

حالا Grid به طور ذاتی property BorderStyle را که قادر به حذف Panel نهایی است، و قبلاً جهت ایجاد یک border لازم بود، ساپورت می کند.

حالا همه کدهایی که ابتدا به رویدادهای یک سلول bind شدند، باید به یک BehaviorModel منتقل شوند، مثلاً می توانید از SourceGrid2.BehaviorModels.CustomEvents استفاده کنید.

شی Selection، دیگر مجموعه ای از سلولها محسوب نمی شود، بلکه مجموه ای از range است.

با insert کردن اشیاء ردیف و ستون، کدی که ابتدا باید روی خطوط و ستونها کار کند، نتیجه ساده تری دارد، به علاوه،متدهای زیاد دیگری که قبلاً درGrid بودند، حالا در کلاسهای ColumnInfoCollection یا ColumnInfo هستند.

شی CellsContainer، دیگر وجود ندارد، و حتی اگر از panelها جایگزین شود، commonهای بیشتری به مستقبماً به grid لینک می شوند، و بنابراین کدی که قبلاً از CellsContainer استفاده می کرد، حالا میط تواند مستقیماً از کنترل grid استفاده کند.

شی قدیمی ICellModel، حالا شی IDataModel است، در حالیکه شی VisualProperties الان IVisualModel شده است.

کلاس CellControl، فعلاً دیگر ساپورت نمی شود.

نسخه های قبلی این کنترل و اطلاعات دیگر در سایت http://www.devage.com/ قابل دسترسی هستند.

 


تقویم فارسی و فارسی ساز تاریخ در .NET

ارسال شده توسط administrator
24. مي 2010 16:14

مقدمه

تقویم فارسی در اکثر کشورهای فارسی زبان استفاده می شود، گرجه در بعضی مناطق از اسامی متفاوتی برای نام ماهها استفاده می شود. تقویم فارسی، تقویم رسمی ایران و افغانستان و یکی از تقویمهای جایگزین در مناطقی از قبیل قزاقستان و تاجیکستان است. تقویم فارسی بر اساس سال خورشیدی است و تقریباً 365 روز است. هر سال دارای 4 فصل است و سال جدید زمانی شروع می شود که خورشید ظاهراً از نیم کره ((hemisphere جنوبی به نیم کره شمالی، هنگام نگاه از مرکز زمین، از استوا رد می شود. سال جدید با اولین روز ماه فروردین که اولین روز بهار در نیم کره شمالی است شروع می شود.

هر یک از 6 ماه نخستین 31 روز، 5 ماه بعدی 30 روز و ماه آخر در سال معمولی 29 و در سال کبیسه 30 روز هستند. سال کبیسه سالی است که وقتی بر 33 تقسیم می شود، باقیمانده اش 1، 5، 9، 13، 17، 22، 26، یا 30 می شود. به عنوان مثال، سال 1370، سال کبیسه است؛ زیرا اگر آنرا بر 33 تقسیم کنیم، باقیمانده 17 می شود. تقریباً 8 سال کبیسه در هر چرخه 33 ساله وجود دارد.

از آنجاییکه .NET Framework هیج نوع تقویم فارسی را ساپورت نمی کنند، و تقویم فارسی در .Net FrameWork 2 دارای Bug و بی فایده است، در اینجا من قصد دارم چگونگی نوشتن DataType لازم و کنترلهای GUI را برای کار کردن با این نوع داده ای (DataType) نشان دهم.

کلاس تقویم فارسی

به نظر می رسد که مایکرسافت فراموش کرده که تقویم فارسی را در.NET 1.1 ساپورت کند، ولی در حالی که کلاسی را برای این نوع مخصوص از تقویم در نسخه دوم Framework مهیا کرده است، اما تا حدودی در محاسبه دچار اشتباه شده است. ما قصد داریم یک تقویم فارسی به علاوه یک DataType که جایگزین کلاس DateTime استاندارد شود، بنویسیم؛ در حالی که همان ساختار را حفظ کنیم. لازم است که DateTime استاندارد را به PersianDate تبدیل کنیم. من یک کلاس تبدیل کننده نیز در اختیار خواهم گذاشت. از آنجاییکه استفاده از این کلاسها، زمانی که با کنترلهای GUI ترکیب می شوند، به حداکثر می رسد، من یک MonthView و یک کنترل DatePicker نیز در اختیار خواهم گذاشت.

زمان طراحیIntegration

من کمی Integration زمان طراحی را تغییر داده ام. ار آنجاییکه بعصی از برنامه نویسان به استفاده از کلاسهای PersianTime و بعضی به DateTime برای کار کردن با این libraries علاقه دارند، من از هر دوی این خصوصیات تحت عنوان SelectedDate و SelectedDateTime نام میبرم. برای SelectedDate که دارای نوع PersianDate است، یک TypeConverter جهت تبدیل Text به مثالهای PersianDate زمان طراحی وجود دارد.

ساپورت پوسته (Theme)

من از .NET 2.0 VisualStyleRenderer, که یک wrapper تنظیم شده برای styleهای XP است استفاده می کنم. به علاوه، کلاسهای دیگری جهت شبیه سازی rendering آفیس 2003 وجود دارد. همچنین، یک style برای آفیس 2000 تیز موجود است. اگر سیستم عام (theming) را ساپورت نکند و یا آن را غیر فعال کند، rendering را با استفاده از آقیس 2000 کنترل میکند.

clip_image002

Popup و سایه ها (Shadow)

کنترلهای popup باید سایه داشته باشند. در کنترلهای RTL، سایه ها باید درپایین سمت چپ کنترلها باشند. تمامی stringهایی که در کنترلها بکار می روند، از یک کلاس localizer manager استفاده می کنند که stringها را بر اساس شمارش StringID پایه ریزی می کند. جهت استفاده از نسخه محلی شده (localized)، باید سرنخهای thread)) culture جاری و خصوصیات CultureUI را به یکی از culture های تعریف شده تغییر دهید: (AR-SA برای Culture عرب، و FA-IR برای Culture فارسی، و InvariantCulture برای culture انگلیسی و خنثی. در زیر چگونگی انچام این کار در متد اصلی برنامه نشان داده شده:

//
// The main entry point for the application.
//
[STAThread]
static void Main()
{
Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo("fa-IR");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}

در زیر چگونگی رفتار صحیح کنترلهای popup با stringهای استاندارد و نسخه محلی شده(localized) برای FA-IR culture آورده شده است.

clip_image001clip_image002[6]

استفاده از Cultureهای عربی و خنثی

قبلاً من از culture AR-AE برای عربی استفاده می کردم، ولی در نسخه 1.3.1.0، مجبور بودم آنرا به AR-SA تغییر دهم. دلیلش هم این است که تقویم هجری، تقویم پیش فرض culture AR-AE نیست؛ در حالی که در culture برای AR-SA، تقویم پیش فرض است. حالا کنترل، وقتی که culture را روی عربی تنظیم می کنید، به طور صحیح کشیده می شود.

حالا Render کردن در culture خنثی در جهت چپ به راست انجام می شود. عموماً کنترل، بر اساس جهت خواندن culture حاضر، تصمیم به render کردن در RTL یا LTR می کند.

clip_image003

استفاده از کد

میتوان از کلاس PersianDate همان قدر که در DateTime استفاده میشود، استفاده کرد. در زیر نمونه هایی آورده شده است:

 

 

//
// Use of Persian date class
//
PersianDate pd1 = PersianDate.Now.ToString(); // prints 1385/01/13
// constructs a new instance with the specified vales
PersianDate pd2 = new PersianDate(1386, 4, 26);

// You can use operator overloading, too!
if(pd1 >= pd2)
MessageBox.Show("Date is greater or equal");

// Use of normal DateTime and Conversion
PersianDate pd3 = PersianDateConverter.ToPersianDate(DateTime.Now);
DateTime dt1 = PersianDateConverter.ToGregorianDate(PersianDate.Now);

 

نکته های جالب

اگر پیشنهاد، نظر یا بازخوردی (feedback) دارید، لطفاً به آدرس email یا Forum انتهای صفحه بفرستید. جهت کسب اطلاعات بیشتر میتوانید از سایت من نیز دیدن کنید.

ساپورت .NET 1.1 و VisualStudio .NET 2003

این library روی VS.NET 2005 و .NET Framework نسخه 2 تست شده است. پس روی VS.NET 2003 اجرا نخواهد شد. اگر واقعاً تیاز به این کار دارید، باید آنرا به VS.NET 2003 تبدیل کنید و قسمت رسم کننده (Drawing) کد را تغییر دهید، زیرا واقعاً از کلاسهای صبقه بندی شده .NET 2.0 جهت رسم (draw) در ویندوز XP استفاده می کند. باید از یک wrapper در Theme API استفاده کنید تا این کار را برای شما انجام دهد. (یک پروژه در codeproject وجود دارد) جهت آسانتر کردن porting، می توانید تمامی drawingها را در ویندوز XP وStyle آفیس 2003 کات (cut) کنید و به themeهای قدیمی و خوب آفیس 2000 که از کلاسهای GDI+ استفاده می کنند متوسل شوید.

نسخه WPF

نسخه WPF از رده خارج است. می توانید نگاهی به بخش Vista در سایت Codeproject بیاندازید.

سابقه

نسخه 1.9.0.0

  • ثابت: bugهایی که ناشی از mapping اشتباه روزهای هفته فارسی/عربی یا گریگوری بودند، که منجر به نمایش اشتباه روزهای هفته در تقویم گریگوری شدند.
  • اضافه شده: handling بهتر modeهای چند انتخابی (multi-selection). میتوان خصوصیت SelectedDateRange را اضافه یا حذف کرد، و تغییرات روی UI منعکس می شوند.
  • اضافه شده: شمارش PersianDayOfWeek با ترتیب صحیح روزها.
  • اضافه شده: خصوصیت DayOfWeek به کلاس PersianDate، جهت بازگشت به روز صحیح هفته.

نسخه 1.6.0.0

  • ثابت: بعضی bugها از طریق feedback پست شدند.
  • اضافه شده: کنترل MessageBox با viewهای RTL و LTR و قابلیت یادآوری مقدار انتخاب شده، همراه با دکمه های MessageBox استاندارد و دلخواه.
  • اضافه شده: فرمت دلخواه متد PersianDate.ToString() (مثل کنترل DateTime)، که قابلیت بازگشت به string فرمت شده را می دهد؛ مانند: Long Date, Long Time, DateTime, و غیره.
  • اضافه شده: draw دلخواه هر روزی که ترجیح داده شده، که میتوان جهت draw کردن بعضی روزها در فرمت غیرفعال استفاده کرد همراه با اتفاق (event) SelectedDateTimeChanging.

نسخه 1.5.0.0

  • ثابت: استثناهای کنترل FAMonthView موقع انتخاب یک تاریخ قبل از MinValue و بیشتر از MaxValue.
  • ثابت: دامنه (range) کلاسهای PersianDate MinValue و MaxValue را گسترش داد.
  • ثابت: MonthNameهای culture یکسان (Invariant)، یک index بر پایه صفر است. نام ماه اول در Invariant ، Culture مشکلی ندارد.
  • ثابت: چک کردن مقادیر (values) سال، ماه،روز روی تنظیمات PersianDate.
  • ثابت: چک کردن مقادیر null روی =! و اپراتورهای == PersianDate.
  • ثابت: خصوصیات مضاعف DateTime و PersianDate و وقایع کنترلهای GUI را حذف کرد. می توان نمونه های DateTime را مستیقیماً به PersianDate تبدیل کرد.
  • ثابت: فرایند اعتبار دهی (validation) به DatePicker.
  • اضافه شده: قابلیت استفاده از cultureها و تقویم های دیگر با کنترلها.
  • اضافه شده: تبدیل مجازی DateTime و PersianDate
  • اضافه شده: خصوصیت اضافه شده Millisecond به کلاس PersianDate برای دقت بیشتر.
  • اضافه شده: کلاس PersianDate حالا قابل سریال بندی (serializable) است و interfaceهای زیر را تکمیل می کند:
  • ICloneable , IComparable , IComparable<T>, IComparer , IComparer<T>, IEquatable<T>.
  • اضافه شده: قابلیت انتخاب scrolling feature در FAMonthView. میتوان روزها، ماهها و سالها را مرور کرد.
  • اضافه شده: کلاسهای FAThemeManager که themeهای انتخاب شده همه کنترلها را handle میکند. جهت تغییر جهانی theme، غفط نیاز به تنظیم خصوصیت (propert) این کلاس Theme دارید.
  • اضافه شده: کلاس FALocalizeManager که cultureهای دیگر را handle می کند. حالا می توانید stringها را در هر culture محلی کنید، (localize)
  • اضافه شده: ساپورت ToolStrip. حالا میتوان کنترلها را در ToolStrip, MenuStrip و غیره یکسان کرد.
  • اضافه شده: ساپورت ستون دلخواه DataGridView Microsoft و editor.

نسخه 1.4.0.0

  • اضاقه شده: تست و demo کارکردهایی را که توسط library، با کد source تکمیل کنید.
  • اضافه شده: سند سازی (documentation)
  • اضافه شده: نمودار (diagram) باری سلسله مراتب (hierarchy) PopupForms, Controls و کلاسهای Painter.
  • احساس و ظاهر تکمیل شده و صحیح در آفیس 2003 روی کنترلهای ComboBox
  • View زیبا و صحیح RTL و LTRاز FADatePickerConverter
  • Bug Fix: کنترل FADa<code>tePicker، popupهای تقویم را مطابق با theme خودش نمایش می دهد.
  • Bug Fix: جهت صحیح کنترلها در حالت RTL و LTR
  • Bug Fix: مکان صحیح سایه کنترل popupها در حالت RTL و LTR
  • Bug Fix: کنترل FADatePicker خصوصیات متن را صحیح نمایش نمیدهد.
  • تغییرات: جابجایی namespaceها.
  • مسایل شناخته شده: محلی کردن (localization) stringها در زبان عربی. ( کسی میتواند اینجا کمکم کند؟)

نسخه 1.3.1.0

  • تکمیل drawingهای ویندوز 2000 در کامپوترهایی که ویندوز XP نداشتند، یا زمانی که visual styleها غیر فعال هستند.
  • Drawing بهتر ویندوز XP و styleهای آفیس 2003
  • Drawing کنترلها بر اساس culture کاربر. در حال حاضر، cultureهای زیر مجاز هستند: خنثی (تقویم گریگوری)، AR-SA (تقویم هجری) و FA-IR (تقویم فارسی).
  • Localization بهتر. میتوان از کلاس Localizer جهت update کردن stringها استفاده کرد.
  • ساپورت از تقویم گریگوری (culture خنثی) و تقویم هجری
  • Rendering چپ به راست برای cultureهایی که راست به چپ نیستند. (مثلاً cultureهای خنثی و culture انگلیسی و غیره.)
  • کنترلهای جدید DatePickerConverter که تاریخ منتخب را با فشار یک دکمه تغییر می دهند.

 

نسخه 1

  • در اختیار گذاشتن کلاسهای base، و کنترلهای GUI برای کار کردن با تاریخهای فارسی. بعضی از این ویژگیها ( مثلاً در drawing آفیس 2000) هنوز تکمیل نشده اند.

بکارگیری متد PUSH از Crystal Report در ASP.NET

ارسال شده توسط administrator
24. مي 2010 11:03

مقدمه

این مقاله چگونکی استفاده از متد PUSH را جهت ترسیم گزارشها توضیح می دهد. همچنین چگونگی استفاده از DataSetهای کاربر را در صفحات گزارش ASP.NET توضیح می دهد. دو نوع متد برای ترسیم گزارش ها وجود دارد:

متد PULL - crystal report به database وصل می شود، و fields data را می آورد و گزارش را ترسیم می کند.

متد PUSH – یک DataSet ایجاد می کنیم، fieldهای DataSet را به عنوان fieldهای گزارش انتخاب می کنیم و سپس آن را به crystal report push می کنیم. در اینجا من قصد دارم فقط متد PUSH را توضیح دهم.

مراحل 1

یک پروژه جدید ASP.NET ایجاد کنید.

clip_image001

آیتم جدیدی به عنوان DataSetوارد کنید.

clip_image002

عناصری را به DataSetیی که می خواهید در گزارش باشد، اضافه کنید. Save All، سپس روی نام فایل DataSet راست کلیک کنید و فرمان "Build and browse". را انتخاب کنید.

سپس یک آیتم جدید به عنوان Crystal report به پروژه اضافه کنید و یک گزارش خالی insert کنید

در مرورگر سرور، روی database field کلیک کنید و database expert را انتخاب و project data را expand و DataSet را در table selector انتخاب کنید . OK کنید.

clip_image003

سپس fieldها را از fieldها database در server explorer در بخش جزییات گزارش drag کنید. Fieldها را هر طور که دوست دارید، مرتب کنید. کنترل crystal report viewer را از جعبه ابزار روی صفحه add کنید. این کار، متغیر زیر را add خواهد کرد:

protected CrystalDecisions.Web.CrystalReportViewer CrystalReportViewer1;

DataSet را populate کنید. گزارش DataSource و منبع گزارش CrystalReportViewer را set کنید.

 

 

private void Page_Load(object sender, System.EventArgs e)
{
CrystalReport1 report=new CrystalReport1();
CrystalReportViewer1.Visible=true;
DataSet ds=new DataSet("Account");//give same name as on
//dataset1 table header
DataTable table=new DataTable("Account");//give same name as on
//dataset1 table header
table.Columns.Add("Fname",typeof(System.String));
table.Columns.Add("Lname",typeof(System.String));
table.Columns.Add("Salary",typeof(System.String));
DataRow row=table.NewRow();
row["Fname"]="Mathew";
row["Lname"]="Hayden";
row["Salary"]="5000$";
// add to table
table.Rows.Add(row);
ds.Tables.Add(table);
// set report's dataset
report.SetDataSource(ds);
// set report source
CrystalReportViewer1.ReportSource =report;
}

 

خروجی (output) به شرح زیر خواهد بود:

clip_image001[6]

چند نکته که باید مد نظر قرار داد:

همان اسمهای Dataset و نام عنصر جدول Datasetیی که insert شده را بدهید.

وقتی Dataset را اصلاح (modify) می کنید، دوباره آن را بسازید و connectionهای کنونی در گزارش را log off کنید. موقعیت DataSource را درباره set کنید طوری که به DataSet اشاره کند، در غیر این صورت، fieldهای database گزارش، تغییرات را قبول نخواهد کرد.

می توان از از تنظیم proprtyهای DataBind در report viewer پرهیز کرد. می توان آن را در زمان run شدن انجام داد.

راهنمای Crystal Reports

ارسال شده توسط administrator
23. مي 2010 12:29

clip_image002

مقدمه

کارکردن با Crystal Reports آسان نیست، و عیب و ایرادهای زیادی دارد.

یکی از بدترین ویژگی های Crystal Reports، روش مدیریت کردن پارامترهای گزارش توسط نمایش دادن هر پارامتر در یک صفحه جدا است که clientها واقعاً از آن بیزارند. برای حل این مشکل، این مقاله به شما نشان خواهد داد چگونه همه پارامترهای یک فایل .rpt را در یک صفحه واحد به کاربر نشان دهید.

این مقاله، گزارشهایی را که از در صفحه استفاده می کنند، نشان می دهد، یک Reports Viewer و یک Parameters Viewer. می توان این صفحه ها را با هم ترکیب کرد که بسیار آسان است. این صفحه ها شامل موارد زیر می شوند:

  • Reports Viewer: این صفحه دارای یک ComboBoxاست که لیستی از گزارشهایی را که کاربر می تواند انتخاب کند نشان می دهد. وقتی کاربر دکمه "Get Report" را فشار می دهد، یک پنجره که Parameters Viewer نامیده می شود باز می شود.
  • :Parameters Viewer این صفحه پارامترهای موجود در گزارشی را که در یک datagrid انتخاب شده نمایش می دهد، و ما به طور پویا (dynamic) کنترلهایی را به آن اضافه می کنیم تا valueهای انتخاب پارامتر را راحتتر کنیم. بعد از انتخاب valueهای پارامتر گزارش انتخابی، کاربر Parameters Viewer را می بندد و Reports Viewer، گزارش انتخاب شده را با پارامترهای انتخاب شده نمایش می دهد.

Crystal Reports، یک فایل باینری rpt ایجاد می کند که شامل همه اطلاعاتی می شود که برای نمایش هر گزارشی نیاز داریم.

  • فایل *.rpt را انتخاب کنید.
  • اطلاعات پارامتر را مستقیماً از فایل *.rpt بخوانید.
  • جدولی ایجاد کنید که همه پارامترهای انتخاب شده در فایل *.rpt را نمایش دهد.
  • جدول پارامتری که ایجاد می کنیم، باید همه کنترلهایی را هم که انتخاب پارامترهای ایجاد شده از قبیل تقویم، checkboxها، و غیره، را برای کاربر راحت می کنند، در بر بگیرد.
  • باید کنترلهای اعتبار سنجی ای از داده های پارامتر ایجاد کنیم که از فایل *.rpt خوانده ایم.
  • نهایتاً، داده های پارامتر را به Crystal Viewer ارسال می کنیم و Crystal Report انتخابی مان را نمایش می دهیم.
  • اطلاعات Database connection را از فایل*.rpt خوانده می شود.

خواندن داده های پارامتر از فایل های *.rpt

ما با ستونهای از پیش تعریف شده Datagrid پارامترهایمان از قبیل Parameter، Kind، Value، Min، Max، Prompt شروع کردیم. و هر نوع پارامتر را به عنوان یک ردیف جدید در datagrid مان اضافه خواهیم کرد.

  • BooleanParameter
  • CurrencyParameter
  • DateParameter
  • DateTimeParameter
  • NumberParameter
  • StringParameter
  • TimeParameter

برای ساختن یک datagrid پارامترها، پارامترها را به صورت زیر از فایل *.rpt می خوانیم:

 

CrystalDecisions.CrystalReports.Engine.ParameterFieldDefinitions crParamFieldDefinitions
= crDoc.DataDefinition.ParameterFields;

foreach (CrystalDecisions.CrystalReports.Engine.ParameterFieldDefinition _
def in crParamFieldDefinitions)

برای هر پارامتر در حلقه foreach، یک ردیف جدید با datagrid پویا که ستونهای از پیش تعریف شده دارد، اضافه می کنیم.

اضافه کردن کنترل به Datagrid پارامترها

چیزی که در مورد datagrid پارامترمان عجیب است، این است که ما هر پارامتر را به عنوان ردیفی جدید به جای ستون جدیدی، که در datagridها رایج تر است، اضافه می کنیم. این بدین معناست که باید یک از یک روش متفاوت برای اضافه کردن کنترلهای کاربر استفاده کرد. بدین منظور، باید از GotFocus event of the datagrid جهت حذف یا اضافه کردن کنترلها بر اساس نوع پارامتر که در ستون دوم datagrid لیست شده، استفاده کرد.

به همین جهت، من فقط سه نوع کنترل را پیاده کردم:

  • DateTime
  • TimePicker
  • CheckBox

به عنوان مثال، وقتی کاربری روی datagrid کلیک می کند، اگر ستون 2، دارای یک valueی DateTime باشد، آنگاه یک کنترل DateTime در سلول داده (data cell) ظاهر می شود که کاربر DateTime را انتخاب کند. در اینجا باید اشاره کرد که بعضی از برنامه نویسان استفاده از "Radio Button" را برای valueهای Boolean ترجیح می دهند. واضح است که شما می توانید از هر کنترلی که دوست دارید یا client درخواست می کند، استفاده کنید.

کنترل DateTime Picker

DateTime Picker، یک کنترل است که به کاربر اجازه می دهد یک valueی date، datetime یا یک valueی time را انتخاب کند و دو رابط (interface) مجزا بزای یک Calendar و یک Time Spinner در اختیار می گذارد.

کنترل DataTime

 

clip_image001 

 

image

جهت تنظیم کنترل DateTime به عنوان یک Time Spinner، ShowUpDown Boolean property را روی true قرار دهید، و Format property را به Time value تغییر دهید. Time Picker control، یک دکمه spin است که از بخشهای مختلفی تشکیل شده است. مقدار ساعت (hours value)، مقدار دقیقه (minutes value)، مقدار ثانیه (seconds value) که اختیاری است و رشته (string) AM/PM. جهت تغییر زمان، کاربر روی یک بخش کلیک می کند و از ماوس یا کیبورد جهت افزایش یا کاهش یک مقدار استفاده می کند. جهت تغییر داده مقدار دیگر، کاربر ابتدا باید روی آن کلیک کند و سپس از دکمه spin استفاده کند.

به طور پیش فرض، time با استفاده از فرمت H:M:SS AM/PM نمایش داده می شود. این بدین معناست که time از عدد 1 برای ساعتهای 0 تا 9، و دقایق 0 تا 9، و ثانیه ای 0 تا 9؛ و از AM یا PM برای نشان دادن صبح یا بعداز ظهر استفاده می کند. جهت سفارشی کردن روش نمایش دادن time، ابتدا Format property را به Custom تغییر دهید. سپس، در CustomFormat property، از ترکیبی از این کارکترها جهت ایجاد یک فرمت سفارشی استفاده کنید: hh:mm:tt. اگر بخواهید زمان متفاوتی را تنظیم کنید، ترکیب Format را به Value property اعمال کنید. به همان طریق، می توانید مقدار time روی کنترل را با دسترسی به Value property بازیابی کنید.

نکات جالب

خروج فایلها (File Experts)

می توان گزارشهای بارگذاری شده را در فرمتهای گوناگون export کرد:

  • Rich Text Document (RTF)
  • Portable Document (PDF)
  • Microsoft Word (DOC)
  • Microsoft Excel (XLS)
  • Crystal Report (RPT)
  • HTML 3.2 (HTML)
  • HTML 4.0 (HTML)

فایل اختیاری Uranus

در حالیکه سیاره اورانوس، یک سیاره تاریک و پر از گاز است، در این مقاله من Uranus را به عنوان یک فایل XML موقت آورده ام که می توانید در صورت تمایل از آن جهت ذخیره داده های پارامتر استفاده کرد.

Pathwayهای پروژه

این کلاس، یک کلاس ساده است که ارسال valueهای پارامترهای جدا را به Crystal Reports آسانتر می کند. این کلاس به شما اجازه می دهد optionهای پارامتر را سفارشی کنید، بدون اینکه مجبور به بازنویسی همه کدها جهت ارسال پارامترها به Crystal Reports باشید.

Drop Down اتوماتیک تقویم

اگر می خواهید تقویم را هنگامی که روی calendar field در datagrid کلیک می شود، drop down کنید، می توانید پیام ویندوز WM_KEYDOWN = 0x100 را جهت update کردن این دکمه با استفاده از کد زیر ارسال کنید:


 

Win32.SendMessage(dtp.Handle, 0x100, 0x73, 0x3E0001)

 

تنظیم پهنای ComboBox DropDown

ما به طور اتوماتیک پهنای combobox ایجاد شده در datagrid cell را با پهنای طولانی ترین عنصر، با استفاده از رویداد combobox's DropDown به شرح زیر تنظیم می کند:

private void AdjustWidthComboBox_DropDown(object sender, System.EventArgs e) {
ComboBox senderComboBox = (ComboBox)sender;
int width = senderComboBox.DropDownWidth;
Graphics g = senderComboBox.CreateGraphics();
Font font = senderComboBox.Font;
int vertScrollBarWidth =
(senderComboBox.Items.Count>senderComboBox.MaxDropDownItems)?
SystemInformation.VerticalScrollBarWidth:0;
int newWidth;
foreach (Object s in senderComboBox.Items) {
string ss = senderComboBox.GetItemText(s);
newWidth = (int)g.MeasureString(ss, font).Width + vertScrollBarWidth;
if (width < newWidth) { width = newWidth; }
}
senderComboBox.DropDownWidth = width;
}

 

override کردن رویداد CrystalViewer's Refresh

اگر رویداد CrystalViewer's Refresh را override نکنیم، هر بار که صفحه را refresh می کنیم، یک صفحه popup برای دریافت پارامترهای Crystal Viewer ظاهر می شود. ما فقط کار زیر را انجام می دهیم.

private void CrystalReportViewer1_ReportRefresh(object source,
CrystalDecisions.Windows.Forms.ViewerEventArgs e) { e.Handled = true; }


نحوه کاربرد Crystal Reports در برنامه های تحت ویندوز C#.NET

ارسال شده توسط administrator
23. مي 2010 11:49

Crystal Reporting

با استفاده از C#.NET

2003 Windows Application

مرحله 1: جهت باز کردن سند crystal report، روی پروژه راست کلیک کنید.

Add و سپس Add New item را انتخاب کنید. پنجره جدیدی مانند زیر ظاهر می شود. به گزارشتان اسم بدهید.

clip_image001

مرحله 2:

بعد از کلیک روی Open، پنجره دیگری خواهید دید که به شما امکان می دهد چگونگی ایجاد گزارش را با استفاده از Report Expert یا از Blank Reportیا از یک گزارش موجود انتخاب کنید. در اینجا، من گزینه دوم یعنی یک Blank Report را انتخاب می کنم.

حالا سند Crystal Report شما باز خواهد شد. در سمت چپ، شما Field Explorer را می بینید که توسط آن می توانید با database در ارتباط باشید.

(اگر Field Explorer موجود نباشد، می توانید از Ctrl + Alt + T استفاده کنید)

مرحله 3:

حالا برای انتخاب database، روی Database Fieldsدر Field Explorer راست کلیک کنید.

Add/Remove Database -> OLEDB(ADO) ->Make New Connection -> Microsoft OLE DB Provider for SQL Server را انتخاب کنید. روی Next کلیک کنید و اطلاعات connectionتان را بدهید، روی Next کلیک کنید و سپس روی Finish کلیک کنید.

حالا می توانید جدولها، viewهایی را که برای Report احتیاج دارید، اضافه کنید. پنجره، شبیه این خواهد بود.

clip_image002

مرحله 4:

حالا در Field Explorerتان می توانید Table/View انتخابی تان را ببینید. Fieldهایی را که می خواهید در گزارش در قسمت جزییات (Details Section) نشان دهید، drag کنید. صفحه زیر ظاهر خواهد شد.

clip_image003

Fieldهای Details section، اشیاء Field هستند، و Fieldهای Page Header و Report Header، اشیاء متن هستند.

مرحله 5:

حالا، جهت نشان دادن این گزارش، یک فرم بردارید و یک CrystalReportViewer به آن اضافه کنید. (فقط CrystalReportViewer را از Tool Box روی فرم drag کنید)

using CrystalDecisions.Shared;
using CrystalDecisions.CrystalReports.Engine;

حالا حتی اگر databaseهای متصل به گزارش هم داشته باشیم، اما اگر database را تغییر داده باشیم، آنگاه ایجاد مجدد یک Crystal reports ممکن نخواهد بود، پس می توانیم از TableLogOnInfo جهت متصل کردن گزارش به database استفاده کرد.

ایجاد شیء

private TableLogOnInfo LogInfo = new TableLogOnInfo();

می توان این تابع را به صورت زیر نوشت

privatevoid SetLogonInfo()
{
try
{
.ConnectionInfo.ServerName=�SerVerName�;
.ConnectionInfo.UserID=�sa�;
.ConnectionInfo.Password=�ok�
.ConnectionInfo.DatabaseName=�CrystalSample�;
}
atch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}

حالا باید شی گزارش را ایجاد کنیم. راههای متنوعی جهت نمایش دادن گزارش وجود دارد.

یک شی از Report Document ایجاد کنید که بتوان در هر گزارشی که قرار است روی آن CrystalreportViewer نشان داده شود، قابل استفاده باشد:

ReportDocumentO_Report=new ReportDocument();
rt.Load(@"E:\CrytstalSample\rptShowList.rpt");

اشیاء متفاوتی برای گزارشهای متفاوت به شرح زیر ایجاد کنید:
rptShowList O_showList =new rptShowList();
ort1.Database.Tables[0].ApplyLogOnInfo(LogInfo);

مرحله 6:

حالا می خواهم AddressList کاربرانی را که عددشان بزرگتر از 15 است نشان دهم. بدین منظور، من DataTable را با این رکوردها پر می کنم و سپس آن را به گزارش الصاق (attach) می کنم.

DataTable DT =new DataTable();
try
{
string CmdStr="";
CmdStr = �Select * fromVw_AddressList whereUser_Number>15�;
Connection.Open();
SqlDataAdapterAdpt=newSqlDataAdapter(CmdStr,Connection);
DT.Clear();
Adpt.Fill(DT);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Connection.Close();
}

مرحله 7:

حالا DataTable پرشده بالا را به عنوان ReportsdataSource تنظیم کنید.

 
O_showlist.Database.Tables[0].SetDataSource(DT);

مرحله 8:

گزارش را به عنوان crystal report viewer report sourceتنظیم کنید تل گزارش را به viewer، bind کنید..

CryStalReportViewer1.ReportSource = O_Report1111;
CryStalReportViewer1.Zoom(1);

همان کار را می توان با شی متنی هم کرد.

همچنین می توان properties of Formula Field را به روشهای دیگر هم تغییر داد.

O_showlist.DataDefinition.FormulaFields["Header"].Text = �AddressList�;

Fieldهای :Formula

در اینجا من فقط نمونه ای از چگونگی استفاده از formula field را توضیح خواهم داد. شرح کامل formula fieldاز حوصله این بحث خارج است.

روی Formula Field و سپس New راست کلیک کنید و آن را نامگذلری کنید.

clip_image001[4]صفحه ویرایش Formula مانند زیر ظاهر خواهد شد:

بخش سمت چپ شامل fieldهای databaseیی می شود که می توانیم روی آن fieldیی را که می خواهیم formula را در آن بنویسیم، انتخاب کنیم. بخش میانی تابع ها هستند. می توانیم انواع تابع ها را به Formula اعمال کنیم. سمت راست مخصوص عمل کننده ها (operator) است، یعنی جاییکه می توانیم از عمل کننده ها در formula استفاده کنیم.

می توان کد را به صورت زیر نوشت:


if({Vw_AddressList.Gender})= 'M' then
'Male'
else
'Female'

 

 

 

 

 

 

 

 

 

نصب برنامه CrystalReport. Net

مرحله اول: روی Solution وسپس روی Add New Project راست کلیک کنید.

clip_image001[6]

SetUp and Deployment Projects و سپس Setup Project را انتخاب کنید. به setupتان را نامگذاری کنید.

مرحله 2: اضافه کردن فایل Exe و فایلهای دیگر

در File System، روی Application Folder کلیک راست کنید (معمولاً این کار پوشه ای را در Win/Sys ایجاد می کند) سپس Add > Project Output > و سپس Primary output و فایلهای لازم دیگر را انتخاب کنید.

تا اینج فراید معمول نصب کردن بود. اگر برنامه شما Crystal Reports را هم در بر بگیرد، باید چند کار دیگر هم بکنید.

اضافه کردن Merge Module:

روی Setup project > Add > Merge Module > کلیک راست کنید.

ابندا Crystal_regwiz2003.msm و سپس Crystal_Managed2003.msm و Crystal_Database_Access2003_enu.msm و Crystal_Managed2003.msm را انتخاب کنید.

روی Open کلیک کنید.

حالا چبز مهم این است که وقتی این Set up را روی یک سیستم دیگر اجرا می کنید، با این error مواجه می شوید: Keycode32.Dll

دلیل این error، ندادن Registration key به فایل Merge Module است.

Crystal_regwiz2003.msm، جهت انجام این کار روی Crystal_regwiz2003.msm کلیک راست کنید و Properties را انتخاب کنید.

در Properties، (Merge Module Properties)را انتخاب کنید و key را در License Key وارد کنید.

حالا License key می تواند Crystal Report product key باشد، یا می توانید آنرا از منوی Help > About Microsoft Development Environment بدست آورید.

در آنجا رشته (string) Crystal Reports for Visual Studio .NETAAP50-GS00000-U7000RN را خواهید دید (ممکن است برای نسخه های دیگر متفاوت باشد). Key شما is'AAP50-GS00000-U7000RN' است.

حالا solution را بسازید، و set up شما آماده است.

استفاده ار تراکنش (transaction) در ADO.NET

ارسال شده توسط administrator
22. مي 2010 16:16

یک Transaction چیست؟

در یک application database، اغلب با موقعیتی روبرو می شوید که نیاز به اجرای دو یا چند فرمان SQL دارید، به طوریکه اگر یکی از عبارات اجرا نشود، آنگاه هیچ عبارت دیگری قادر به تغییر database نخواهد بود. مثال کلاسیک این مورد، انتقال پول از یک حساب بانکی به حساب دیگر است.

 

UPDATE Accounts SET Balance = Balance □ 10 WHERE Customer = 1;
UPDATE Accounts SET Balance = Balance + 10 WHERE Customer = 2;

 

اگر قرار بود عبارت اول SQL، اجرا شود و عبارت دوم SQL اجرا نشود، آنگاه 10 دلار از حساب مشتری اول کم می شود، اما هرگز به حساب مشتری دوم واریز نمی شود. وقتی 10 دلار به طور کلی ناپدید شود، زیاد جالب نیست.

یک راه برای مقابله با این مشکل، کنترل وضعتت در database access codeتان است. می توان این کار را توسط گرفتن System.Data.SqlClient.SqlException هنگام انجام database access، انجام دهید. اما این موقعیت آنقدر که ابتدا به نظر می رسد آسان نیست. ممکن است عبارات SQL در جاهایی اجرا نشود:

  • قبل از اینکه اولین عبارت SQL اجرا شود.
  • بعد از اینکه اولین عبارت SQL اجرا شود.
  • بعد از اینکه دومین عبارت SQL اجرا شود.

این بدین معناست که شما باید ترازهای هر دو حساب را قبل از اتمام انتقال تعیین کنید، و مشخص کنید تراز کنونی کدام حساب با تراز ابتدایی یکی نیست.، و یک عبارت UPDATE اجرا کنید.

این شرایط توسط این واقعیت که SQL Server یک محیط دارای چندین کاربر است، بدتر هم می شود. در هر زمان، کاربرهای دیگر ممکن است به جدول حسابها دسترسی پیدا کنند و اگر به آن جدولی که بین اجرا نشدن عبارت شما و تصحیحات جدول وجود دارد دسترسی پیدا کنند، به خوبی می توانند به داده های نامعتبر دسترسی پیدا کنند. این بسیار بد خواهد بود، و ممکن است به مشکل شدن پیدا کردن bugها در کد شما منجر شود. (پیدا کردن bugهای مربوط به داده های همسان سازی (synchronization) در طول چندین فرایند بسیار مشکل است، زیرا آنها نام مخصوص خود را دارند: Heisenbugها )

نظریه database مدرن، پیشنهاد می کند که در یک دنیای transaction بی نقص، یک database دارای یک سری از خصوصیاتی (property) باشد که تحت عنوان ACID شناخته شوند. این خصوصیات عبارتند از:

Atomic: همه عبارات موجوددر یک گروه باید اجرا شوند، یا هیچ عبارتی نباید اجرا شود.

Consistent: به طور طبیعی از atomic تبعیت می کند؛ گروهی از عبارات SQL باید یک database را از یک حالت شناخته شده شروع تا یک حالت شناخته شده پایان بگیرند. اگر عبارات اجرا شوند، database باید در حالت شناخته شده پایانی باشد. اگر عبارت اجرا نشود، database باید در حالت شناخته شده شروع باشد.

Isolated: گروهی از عبارات باید مستقل از هر گروه عبارت دیگری که همزمان در حال اجرا شدن هستند، اجرا شوند. اگر اینگونه نباشد، ممکن نیست گروههای عبارت consistent (به هم پیوسته) باشند. ممکن است حالت شناخته شده پایانی توسط یک کد که شما هیچ کنترل یا اطلاعی از آن ندارید، تغییر کند. این، یکی از مفاهیمی است که در تئوری عالی است، اما Isolation کلی، تاثیرات اجرایی مهمی در دنیایی واقعی دارد. در مورد اینکه SQL Server چگونه این را اجرا می کند، بعداً بیشتر توضیح داده خواهد شد.

Durable: وقتی گروهی از عبارات SQL اجرا می شوند، نتایج باید در یک رسانه دایم ذخیره شود، که اگر database درست بعد از اینکه گروهی از عبارات SQL اجرا می شوند، از بین بروند، بازگرداندن حالت database به نقطه ای که بعد از اینکه آخرین transaction به حافظه سپرده میشود، ممکن باشد.

 

در SQL Server، ACID بودن، توسط مفهوم transactionها مهیا می شود. Transaction راهی برای گروه بندی عبارات SQL است، طوری که وقتی اجرا می شوند، transaction از اصول ACID تبعیت می کند. یک transaction با یک اتصال به database فعال می شود و به تمامی فرمانهای اجرا شده در آن اتصال اعمال می شود، تا وقتی که transaction تمام شود. زمانی که شما یک transaction دارید، دو کار می توانید با آن بکنید. یا می توانید transaction را بعد از اتمام به حافظه database بسپارید، یا می توانید transactionرا رها کنید و تغییرات بوجود آمده درآنرا به حالت اول برگردانید.

طبق Transact-SQL، سه فرمان مهم برای کنترل یک transaction وجود دارد. TRANSACTION BEGIN: یک transaction را شروع می کند،

COMMIT TRANSACTION: transaction را به حافظه database می سپارد.

ROLLBACK TRANSACTION: transaction را به حالت اول برمی گرداند.

این عبارات ممکن است واقعاً کمی پیچیده باشند، می توانید به documentation سایت MSDN که راجع به این عبارات هستند رجوع کنید.

Transactionهای .NET

در .NET، transactionها با کلاس System.Data.SqlClient.SqlTransaction کنترل می شوند. بازهم یک transaction روی یک object SqlConnection وجود دارد و بدین ترتیب شما همه objectهای SqlCommand را با استفاده از آن اتصال ایجاد می کنید. بایید نگاهی به این مثال بیاندازیم.

 

public class TransactionDemo
{
public TransactionDemo()
{

}

[STAThread]
public static void Main()
{
Demo1();
}

private static void Demo1()
{
SqlConnection db = new SqlConnection("connstringhere");
SqlTransaction transaction;

db.Open();
transaction = db.BeginTransaction();
try
{
new SqlCommand("INSERT INTO TransactionDemo " +
"(Text) VALUES ('Row1');", db, transaction)
.ExecuteNonQuery();
new SqlCommand("INSERT INTO TransactionDemo " +
"(Text) VALUES ('Row2');", db, transaction)
.ExecuteNonQuery();
new SqlCommand("INSERT INTO CrashMeNow VALUES " +
"('Die', 'Die', 'Die');", db, transaction)
.ExecuteNonQuery();
transaction.Commit();
}
catch (SqlException sqlError)
{
transaction.Rollback();
}
db.Close();
}

 

همانطور که در مثال بالا دیدید، ابتدا به یک database متصل شویم. سپس، متد BeginTransaction ازconnection را فرا می خوانیم و مرجعی را به Object که برمی گرداند نگه می داریم. در این نقطه، connection، محدود به object SqlTransaction می شود که برگردانده شده. این بدین معنی است که هر SqlCommand اجرا شده در آن اتصال، در درون transaction خواهد بود. در بلوک (block) try{، می بینید که ما سه object SqlCommand ایجاد و اجرا کردیم. گرچه متوجه خواهید شد که در این مورد ما ازاین stringهای استفاده می کنیم:

constructor شی SqlCommand دارای یک overload می باشد که شی transaction را به عنوان پارامتر ورودی به آن پاس می دهیم.

دلیلش هم این است که SqlCommand object، نیاز به رد شدن از محدوده transaction به یک connection دارد. قصور در انجام این کار منجر به روی دادن خطای زمان اجرا خواهد شد. به نظر من، این، نقطه ضعف این مدل است، زیرا یک transaction کاملاً محدود به connection است، و SqlCommand باید قادر به بیرون کشیدنSqlTransaction object از SqlConnection باشد.

در مثال بالا، در فرمان نخستین SqlCommand کاملاً معتبر هستند و TransactionDemo در database وجود دارند. اما جدول CrashMeNow وجود ندارد. از آنجاییکه این جدول وجود ندارد، یک شی SqlException ، روی ExecuteNonQuery ، پرتاب خواهد شد. تشخیص اینکه داشتن یک transaction، یک خطای زمان اجرای استاندارد را، که مکانیزم را کنترل می کند، تعویض نمی کند. اگر فکر می کنید ممکن است عبارات شما اجرا نشوند، باید SqlException را catch کنید، و در بلوک catchتان، transaction را به حالت اول برگردانید.

دو عملیات وجود دارد که می توانید روی SqlTransaction object انجام دهید. Rollback، transaction را کنسل و تمامی تغییرات اعمال شده را لغو می کند. Commit، باعث نوشته شدن دایمی transaction در database می شود. هر دو عملیات، باعث اتمام transaction می شوند.

اگر کد بالا را اجرا کنید و به جدول TransactionDemo نگاه کنید، خواهید دید که هیچ ردیفی (row) اضافه نشده است، transaction ، بعد از روی دادن خطای زمان اجرا ،به حالت اول برگردانده شد. اگر خط مزاحم SQL را حذف و برنامه را دوباره اجرا و دوباره به آن نگاه کنید، خواهید دید که دو ردیف اضافه شده است. در واقع بدین معنی است که transaction فعال است.

Transactionهای پیشرفته و سطوح (level) isolation

اینجا، مرز transactionها نیست. همانطور که قبلاً در توصیف از خصوصیات ACID گفتم، transactionها، لزوماً با دقسق ترین تعریف isolated هماهنگ نیست. دلیلش این است که وقتی شما transaction را ایجاد می کنید، می توانید سطح isolation یک transaction را configure کنید.

چرا می خواهید این کار را بکنید؟ performance. در حالی که isolation در تئوری بسیار جالب است، کمونیزم همه اینگونه بود. در واقعیت، قفل کردن گروهی از ردیفها وقتی که یک transaction روی آن کار می کند، ممکن است به خاطر performance شدنی نباشد. لزوماً نباید هر reader را از خواندن یک جدول متوقف کنید، زیرا transaction شما همه چیز را قفل می کند.

برای سبک کردن این نگرانی، .NET دارای قابلیت تعیین کردن سطوح isolation هنگام ایجاد یک transaction است. انجام این کار فقط نیاز به supply کردن یک System.Data.IsolationLevel value به متد BeginTransaction است. Valueهای در دسترس برای SQL عبارتند از:

ReadUncommitted: این، isolation نیست. هر کسی می تواند داده های قرارداده شده در جدول یا داده های update شده ای را که بلافاصله بعد از اینکه عبارت SQL باعث تغییر می شود، بخواند. نیازی به commit نیست. ممکن است این موضوع به فرایندی منجر شود که دارای داده های قدیمی (out-of-date) باشد. ممکن است از ورژنی از داده هااستفاده کند که از جدول باز گردانده شده اند.

ReadCommitted: این، کمی isolate تر است. در این مورد، یک transaction می تواند فقط داده هایی از جدول بخواند که قبلاً commit شده اند. وقتی یک transaction می خواهد یک داده ها را update کند، نیاز به یک قفل(lock) مشترک(shared) روی آن داده ها دارد و (اگر با موفقیت قفل را بدست آورد) داده ها را update می کند.transactionهای خارج از آن transaction نمی توانند داده های آن جدول را update کنند. این، فقط کمی isolateتر است ، اما یک عبارت SQL که دوبار در یک transaction اجرا شده است، می تواند نتایج متفاوتی را بازگرداند، اگر transaction دیگری، داده هایی را که عبارات SQL در بین دو عبارت اجرا می کنند، تغییر و commit کند. این، سطح isolation پیش فرض برای SqlTransaction است.

RepeatableRead: آرام آرام isolate تر می شود. در این مورد، یک قفل مشترک، روی تمامی داده های query شده در یک transaction اعمال می شود. این بدین معناست که هیچ transaction دیگری نمی تواند داده های استفاده شده در transaction شما را تغییر دهند.

Serializable: قفلها روی دامنه های (range) جداولی که استفاده می کنید قرار می گیرند و اجازه نمی دهند کاربرهای دیگر داده های شما را تغییر دهند یا ردیفهای جدیدی اضافه کنند. این، isolateترین سطح isolation است.

در SQL Server 2005، یک سطح جدید isolation به نام " snapshot isolation" اضافه خواهد شد. در snapshot isolation، وقتی در یک transaction به ردیفها دسترسی پیدا می شود،version میشوند. این بدین معناست که زمانیکه یک transaction، به یک سری از valueها دسترسی پیدا می کند، ماندن آن valueها به همان صورت، تا زمانی که شما transaction را commit یا rollback کنید، تضمین می شود. Transactionهای دیگر که وسط transaction اول شروع شده اند، یک کپی از database اصلی می گیرند تا عملیات را روی آن انجام دهند. گرچه قبل از اینکه هر transaction تمام شود، SQL Server داده های اصلی را که داشتند رویشان عملیات انجام می گرفت را تست می کند تا مطمئن شود که همان داده های کنونی در database هستند. اگر اینگونه شود، transaction تمام می شود؛ وگرنه، transaction به همان حالت اول بازمی گردد و کاربر مجبور خواهد بود batch را دوباره امتحان کند.

نتیجه گیری

Transactionها، برای چندین چیز دیگر نیز مفید هستند. اولاً، راهی را جهت به حالت اول برگرداندن گروهی از عبارات SQL در اختیار می گذارد. به یاد داشته باشید که failure، می تواند بیشتر از یک error که برگردانده می شود، باشد. همچنین یک failure می تواند منطقی باشد – در مثال بالا، شاید حسابی که از آن پول منتقل می شد، پول کافی جهت انتقال ندارد. در این صورت، می توانید transaction را، هنگامی که به این واقعیت پی می برید، به حالت اول بازگردانید. دوماً، آنها راهی را برای isolate کردن داده هایی که رویشان کار می کند در اختیار می گذارد، به طوریکه دیگر در مورد surpriseها نگران نیستید. در همه موارد، باید سطح isolation را که واقعاً نیاز دارید امتحان کنید و از تاثیرات performance همه آنها آگاه باشید.