مطالب دوره‌ها
متدهای توکار استفاده از نوع داده‌ای XML - قسمت دوم
امکان ترکیب داده‌های یک بانک اطلاعاتی رابطه‌ای و XML در SQL Server به کمک یک سری تابع کمکی خاص به نام‌های sql:variable و sql:column پیش بینی شده‌است. sql:variable امکان استفاده از یک متغیر T-SQL را داخل یک XQuery میسر می‌سازد و توسط sql:column می‌توان با یکی از ستون‌های ذکر شده در قسمت select، داخل XQuery کار کرد. در ادامه به مثال‌هایی در این مورد خواهیم پرداخت.

ابتدا جدول xmlTest را به همراه چند رکورد ثبت شده در آن، درنظر بگیرید:
 CREATE TABLE xmlTest
(
 id INT IDENTITY PRIMARY KEY,
 doc XML
)
GO
INSERT xmlTest VALUES('<Person name="Vahid" />')
INSERT xmlTest VALUES('<Person name="Farid" />')
INSERT xmlTest VALUES('<Person name="Mehdi" /><Person name="Hamid" />')
GO

استفاده از متد sql:column

در ادامه می‌خواهیم مقدار ویژگی name رکوردی را که نام آن Vahid است، به همراه id آن ردیف، توسط یک XQuery بازگشت دهیم:
 SELECT doc.query('
for $p in //Person
where $p/@name="Vahid"
return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
')
FROM xmlTest
یک sql:column حتما نیاز به یک نام ستون دو قسمتی دارد. قسمت اول آن نام جدول است و قسمت دوم، نام ستون مورد نظر.
در مورد متد data در قسمت قبل بیشتر بحث شد و از آن برای استخراج داده‌ی یک ویژگی در اینجا استفاده شده‌است. عبارات داخل {} نیز پویا بوده و به همراه سایر قسمت‌های ثابت return، ابتدا محاسبه و سپس بازگشت داده می‌شود.
اگر این کوئری را اجرا کنید، ردیف اول آن مساوی عبارت زیر خواهد بود
 <li>Vahid has id = 1</li>
به همراه دو ردیف خالی دیگر در ادامه. این ردیف‌های خالی به علت وجود دو رکورد دیگری است که با شرط where یاد شده تطابق ندارند.
یک روش برای حذف این ردیف‌های خالی استفاده از متد exist است به شکل زیر:
 SELECT doc.query('
for $p in //Person
where $p/@name="Vahid"
return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
')
FROM xmlTest
WHERE doc.exist('
for $p in //Person
where $p/@name="Vahid"
return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
')=1
در اینجا فقط ردیفی انتخاب خواهد شد که نام ویژگی آن Vahid است.
روش دوم استفاده از یک derived table و بازگشت ردیف‌های غیرخالی است:
 SELECT * FROM
(
 (SELECT doc.query('
 for $p in //Person
 where $p/@name="Vahid"
 return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li>
 ') AS col1
 FROM xmlTest)
) A
WHERE CONVERT(VARCHAR(8000), col1)<>''


استفاده از متد sql:variable

 DECLARE @number INT = 1
SELECT doc.query('
for $p in //Person
where $p/@name="Vahid"
return <li>{data($p/@name)} has number = {sql:variable("@number")}</li>
')
FROM xmlTest
در این مثال نحوه‌ی بکارگیری یک متغیر T-SQL را داخل یک XQuery توسط متد sql:variable ملاحظه می‌کنید.


استفاده از For XML برای دریافت یکباره‌ی تمام ردیف‌های XML

اگر کوئری معمولی ذیل را اجرا کنیم:
 SELECT doc.query('/Person') FROM xmlTest
سه ردیف خروجی را مطابق سه رکوردی که ثبت کردیم، بازگشت می‌دهد.
اما اگر بخواهیم این سه ردیف را با هم ترکیب کرده و تبدیل به یک نتیجه‌ی واحد کنیم، می‌توان از For XML به نحو ذیل استفاده کرد:
 DECLARE @doc XML
SET @doc = (SELECT * FROM xmlTest FOR XML AUTO, ELEMENTS)
SELECT @doc.query('/xmlTest/doc/Person')


بررسی متد xml.nodes

متد xml.nodes اندکی متفاوت است نسبت به تمام متدهایی که تاکنون بررسی کردیم. کار آن تجزیه‌ی محتوای XML ایی به ستون‌ها و سطرها می‌باشد. بسیار شبیه است به متد OpenXML اما کارآیی بهتری دارد.
 DECLARE @doc XML ='
<people>
  <person><name>Vahid</name></person>
  <person><name id="2">Farid</name></person>
  <person><name>Mehdi</name></person>
  <person><name>Hooshang</name><name id="1">Hooshi</name></person>
  <person></person>
</people>
'
در اینجا یک سند XML را درنظر بگیرید که از چندین نود شخص تشکیل شده‌است. اغلب آن‌ها دارای یک name هستند. چهارمین نود، دو نام دارد و آخری بدون نام است.
در ادامه قصد داریم این اطلاعات را تبدیل به ردیف‌هایی کنیم که هر ردیف حاوی یک نام است. اولین سعی احتمالا استفاده از متد value خواهد بود:
 SELECT @doc.value('/people/person/name', 'varchar(50)')
این روش کار نمی‌کند زیرا متد value، بیش از یک مقدار را نمی‌تواند بازگشت دهد. البته می‌توان از متد value به نحو زیر استفاده کرد:
 SELECT @doc.value('(/people/person/name)[1]', 'varchar(50)')
اما حاصل آن دقیقا چیزی نیست که دنبالش هستیم؛ ما دقیقا نیاز به تمام نام‌ها داریم و نه تنها یکی از آن‌ها را.
سعی بعدی استفاده از متد query است:
 SELECT @doc.query('/people/person/name')
در این حالت تمام نام‌ها را بدست می‌آوریم:
 <name>Vahid</name>
<name id="2">Farid</name>
<name>Mehdi</name>
<name>Hooshang</name>
<name id="1">Hooshi</name>
اما این حاصل دو مشکل را به همراه دارد:
الف) خروجی آن XML است.
ب) تمام این‌ها در طی یک ردیف و یک ستون بازگشت داده می‌شوند.

و این خروجی نیز چیزی نیست که برای ما مفید باشد. ما به ازای هر شخص نیاز به یک ردیف جداگانه داریم. اینجا است که متد xml.nodes مفید واقع می‌شود:
 SELECT
tab.col.value('text()[1]', 'varchar(50)') AS name,
tab.col.query('.'),
tab.col.query('..')
from @doc.nodes('/people/person/name') AS tab(col)
خروجی متد xml.nodes یک table valued function است؛ یک جدول را باز می‌گرداند که دقیقا حاوی یک ستون می‌باشد. به همین جهت Alias آن‌را با tab col مشخص کرده‌ایم. tab متناظر است با جدول بازگشت داده شده و col متناظر است با تک ستون این جدول حاصل. این نام‌ها در اینجا مهم نیستند؛ اما ذکر آن‌ها اجباری است.
هر ردیف حاصل از این جدول بازگشت داده شده، یک اشاره‌گر است. به همین جهت نمی‌توان آن‌ها را مستقیما نمایش داد. هر سطر آن، به نودی که با آن مطابق XQuery وارد شده تطابق داشته است، اشاره می‌کند. در اینجا مطابق کوئری نوشته شده، هر ردیف به یک نود name اشاره می‌کند. در ادامه برای استخراج اطلاعات آن می‌توان از متد text استفاده کرد.
اگر قصد داشتید، اطلاعات کامل نود ردیف جاری را مشاهده کنید می‌توان از
 tab.col.query('.'),
استفاده کرد. دات در اینجا به معنای self است. دو دات (نقطه) پشت سرهم به معنای درخواست اطلاعات والد نود می‌باشد.
روش دیگر بدست آوردن مقدار یک نود را در کوئری ذیل مشاهده می‌کنید؛ value دات و data دات. خروجی  value مقدار آن نود است و خروجی data مقدار آن نود با فرمت XML.

 SELECT
tab.col.value('.', 'varchar(50)') AS name,
tab.col.query('data(.)'),
tab.col.query('.'),
tab.col.query('..')
from @doc.nodes('/people/person/name') AS tab(col)

همچنین اگر بخواهیم اطلاعات تنها یک نود خاص را بدست بیاوریم، می‌توان مانند کوئری ذیل عمل کرد:
 SELECT
tab.col.value('name[.="Farid"][1]', 'varchar(50)') AS name,
tab.col.value('name[.="Farid"][1]/@id', 'varchar(50)') AS id,
tab.col.query('.')
from @doc.nodes('/people/person[name="Farid"]') AS tab(col)

در مورد کار با جداول، بجای متغیرهای T-SQL نیز روال کار به همین نحو است:
 DECLARE @tblXML TABLE (
 id INT IDENTITY PRIMARY KEY,
 doc XML
 )

INSERT @tblXML VALUES('<person name="Vahid" />')
INSERT @tblXML VALUES('<person name="Farid" />')
INSERT @tblXML VALUES('<person />')
INSERT @tblXML VALUES(NULL)

SELECT
id,
doc.value('(/person/@name)[1]', 'varchar(50)') AS name
FROM @tblXML
در اینجا یک جدول حاوی ستون XML ایی ایجاد شده‌است. سپس چهار ردیف در آن ثبت شده‌اند. در آخر مقدار ویژگی نام این ردیف‌ها بازگشت داده شده‌است.


نکته : استفاده‌ی وسیع SQL Server از XML برای پردازش کارهای درونی آن

بسیاری از ابزارهایی که در نگارش‌های جدید SQL Server اضافه شده‌اند و یا مورد استفاده قرار می‌گیرند، استفاده‌ی وسیعی از امکانات توکار XML آن دارند. مانند:
Showplan، گراف‌های dead lock، گزارش پروسه‌های بلاک شده، اطلاعات رخدادها، SSIS Jobs، رخدادهای Trace و ...

مثال اول: کدام کوئری‌ها در Plan cache، کارآیی پایینی داشته و table scan را انجام می‌دهند؟

 CREATE PROCEDURE LookForPhysicalOps (@op VARCHAR(30))
AS
SELECT sql.text, qs.EXECUTION_COUNT, qs.*, p.*
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(sql_handle) sql
CROSS APPLY sys.dm_exec_query_plan(plan_handle) p
WHERE query_plan.exist('
declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
/ShowPlanXML/BatchSequence/Batch/Statements//RelOp/@PhysicalOp[. = sql:variable("@op")]
') = 1
GO

EXECUTE LookForPhysicalOps 'Table Scan'
EXECUTE LookForPhysicalOps 'Clustered Index Scan'
EXECUTE LookForPhysicalOps 'Hash Match'
اطلاعات Query Plan در SQL Server با فرمت XML ارائه می‌شود. در اینجا می‌خواهیم یک سری متغیر مانند Clustered Index Scan و امثال آن‌را از ویژگی PhysicalOp آن کوئری بگیریم. بنابراین از متد  sql:variable کمک گرفته شده‌است.
اگر علاقمند هستید که اصل این اطلاعات را با فرمت XML مشاهده کنید، کوئری نوشته شده را تا پیش از where آن یکبار مستقلا اجرا کنید. ستون آخر آن query_plan نام دارد و حاوی اطلاعات XML ایی است.

مثال دوم:   استخراج اپراتورهای رابطه‌ای (RelOp) از یک Query Plan ذخیره شده

 WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT RelOp.op.value(N'../../@NodeId', N'int') AS ParentOperationID,
RelOp.op.value(N'@NodeId', N'int') AS OperationID,
RelOp.op.value(N'@PhysicalOp', N'varchar(50)') AS PhysicalOperator,
RelOp.op.value(N'@LogicalOp', N'varchar(50)') AS LogicalOperator,
RelOp.op.value(N'@EstimatedTotalSubtreeCost ', N'float') AS EstimatedCost,
RelOp.op.value(N'@EstimateIO', N'float') AS EstimatedIO,
RelOp.op.value(N'@EstimateCPU', N'float') AS EstimatedCPU,
RelOp.op.value(N'@EstimateRows', N'float') AS EstimatedRows,
cp.plan_handle AS PlanHandle,
st.TEXT AS QueryText,
qp.query_plan AS QueryPlan,
cp.cacheobjtype AS CacheObjectType,
cp.objtype AS ObjectType
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
CROSS APPLY qp.query_plan.nodes(N'//RelOp') RelOp(op)
در اینجا کار کردن با WITH XMLNAMESPACES در حین استفاده از متد xml.nodes ساده‌تر است؛ بجای قرار دادن فضای نام در تمام کوئری‌های نوشته شده.


بررسی متد xml.modify

تا اینجا تمام کارهایی که صورت گرفت و نکاتی که بررسی شدند، به مباحث select اختصاص داشتند. اما insert، delete و یا update قسمتی از یک سند XML بررسی نشدند. برای این منظور باید از متد xml.modify استفاده کرد. از آن در عبارات update و یا set کمک گرفته شده و ورودی آن نباید نال باشد. در ادامه در طی مثال‌هایی این موارد را بررسی خواهیم کرد.
ابتدا فرض کنید که سند XML ما چنین شکلی را دارا است:
DECLARE @doc XML = '
<Invoice>
<InvoiceId>100</InvoiceId>
<CustomerName>Vahid</CustomerName>
<LineItems>
<LineItem>
<Sku>134</Sku>
<Quantity>10</Quantity>
<Description>Item 1</Description>
<UnitPrice>9.5</UnitPrice>
</LineItem>
<LineItem>
<Sku>150</Sku>
<Quantity>5</Quantity>
<Description>Item 2</Description>
<UnitPrice>1.5</UnitPrice>
</LineItem>
</LineItems>
</Invoice>
'
در ادامه قصد داریم یک نود جدید را پس از CustomerName اضافه کنیم.
 SET @doc.modify('
insert <InvoiceInfo><InvoiceDate>2014-02-10</InvoiceDate></InvoiceInfo>
after /Invoice[1]/CustomerName[1]
')

SELECT @doc
اینکار را با استفاده از دستور insert، به نحو فوق می‌توان انجام داد. از عبارت Set و متغیر doc مقدار دهی شده، کار شروع شده و سپس نود جدیدی پس از (after) اولین نود CustomerName موجود insert می‌شود. Select بعدی نتیجه را نمایش خواهد داد.
<Invoice>
  <InvoiceId>100</InvoiceId>
  <CustomerName>Vahid</CustomerName>
  <InvoiceInfo>
        <InvoiceDate>2014-02-10</InvoiceDate>
  </InvoiceInfo>
  <LineItems>
...

در SQL Server 2008 به بعد، امکان استفاده از متغیرهای T-SQL نیز در اینجا مجاز شده‌است:
 SET @x.modify('insert sql:variable("@x") into /doc[1]')
بنابراین اگر نیاز به تعریف متغیری در اینجا داشتید از جمع زدن رشته‌ها استفاده نکنید. حتما نیاز است متغیر تعریف شود و گرنه باخطای ذیل متوقف خواهید شد:
 The argument 1 of the XML data type method "modify" must be a string literal.


افزودن ویژگی‌های جدید به یک سند XML توسط متد xml.modify

اگر بخواهیم یک ویژگی (attribute) جدید را به نود خاصی اضافه کنیم می‌توان به نحو ذیل عمل کرد:
 SET @doc.modify('
insert attribute status{"backorder"}
into /Invoice[1]
')

SELECT @doc
که خروجی دو سطر ابتدایی آن پس از اضافه شدن ویژگی status با مقدار backorder به نحو ذیل است:
 <Invoice status="backorder">
  <InvoiceId>100</InvoiceId>
....


حذف نودهای یک سند XML توسط متد xml.modify

اگر بخواهیم تمام LineItemها را حذف کنیم می‌توان نوشت:
 SET @doc.modify('delete /Invoice/LineItems/LineItem')
SELECT @doc
با این خروجی:
 <Invoice status="backorder">
  <InvoiceId>100</InvoiceId>
  <CustomerName>Vahid</CustomerName>
  <InvoiceInfo>
      <InvoiceDate>2014-02-10</InvoiceDate>
  </InvoiceInfo>
  <LineItems />
</Invoice>


به روز رسانی نودهای یک سند XML توسط متد xml.modify

اگر نیاز باشد تا مقدار یک نود را تغییر دهیم می‌توان از replace value of استفاده کرد:
 SET @doc.modify('replace value of
  /Invoice[1]/CustomerName[1]/text()[1]
  with "Farid"
')
SELECT @doc
با خروجی ذیل که در آن نام اولین مشتری با مقدار Farid جایگزین شده است:
 <Invoice status="backorder">
  <InvoiceId>100</InvoiceId>
  <CustomerName>Farid</CustomerName>
  <InvoiceInfo>
       <InvoiceDate>2014-02-10</InvoiceDate>
  </InvoiceInfo>
  <LineItems />
</Invoice>
replace value of فقط با یک نود کار می‌کند و همچنین، فقط مقدار آن نود را تغییر می‌دهد. به همین جهت از متد text استفاده شده‌است. اگر از text استفاده نشود با خطای ذیل متوقف خواهیم شد:
 The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content.


به روز رسانی نودهای خالی توسط متد xml.modify

باید دقت داشت، نودهای خالی (بدون مقدار)، مانند LineItems پس از delete کلیه اعضای آن در مثال قبل، قابل replace نیستند و باید مقادیر جدید را در آن‌ها insert کرد. یک مثال:

 DECLARE @tblTest AS TABLE (xmlField XML)

INSERT INTO @tblTest(xmlField)
VALUES
 (
'<Sample>
   <Node1>Value1</Node1>
   <Node2>Value2</Node2>
   <Node3/>
</Sample>'
)
 
DECLARE @newValue VARCHAR(50) = 'NewValue'

UPDATE @tblTest
SET xmlField.modify(
'insert text{sql:variable("@newValue")} into
  (/Sample/Node3)[1] [not(text())]'
)

SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
در این مثال اگر از replace value of برای مقدار دهی نود سوم استفاده می‌شد:
 UPDATE @tblTest
SET xmlField.modify(
'replace value of (/Sample/Node3/text())[1]
  with sql:variable("@newValue")'
)
تغییری را پس از اعمال دستورات مشاهده نمی‌کردید؛ زیرا این المان ()text ایی را برای replace شدن ندارد.
نظرات مطالب
روش نصب اندروید 10 بر روی گوشی‌های قدیمی سامسونگ
روش اصلاح daylight saving time در سیستم عامل اندروید


باتوجه به اینکه از امسال دیگر تغییر ساعت بهاری و تابستانی را نداریم، از ابتدای سال، سیستم عامل‌های اندرویدی، یک ساعت جلوتر را نمایش می‌دهند که در ابتدا تصور بر این بود که مشکل از اپراتورهاست، اما اینطور نیست! ساعت ارسالی توسط آن‌ها صحیح است. مشکل از قدیمی بودن بانک اطلاعاتی tzdata سیستم عامل‌های اندرویدی است.

نگارش جدید و کامپایل شده‌ی این فایل‌ها را از اینجا می‌توانید دریافت کنید: tzdata-new.zip

محل ذخیره شدن بانک اطلاعاتی time-zones در اندروید

بسته به نگارش اندروید شما، یکی از مکان‌های زیر، محل ذخیره سازی دو فایل tzdata و tz_version است:
/data/misc/zoneinfo/tzdata
/system/usr/share/zoneinfo
/apex/com.android.tzdata/etc/tz/tzdata
برای مثال بر روی اندروید 10 من (که روش نصب آن در این مطلب آمده)، مسیر آخری، مسیر اصلی و تاثیرگذار است.


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

تا اینجا مشخص شد که برای رفع مشکل time-zone و به روز رسانی اطلاعات آن، باید دو فایل tzdata و tz_version را در مسیرهای یاد شده، بازنویسی کنیم؛ اما ... اندروید چنین اجازه‌ای را نمی‌دهد!
در سیستم عامل LineageOS 17.1 ای که من نصب کردم، می‌توان با نصب برنامه‌ی معروف Magisk، دسترسی روت پیدا کرد. روش نصب آن هم به صورت زیر است:
1) فایل apk آن‌را از این آدرس دریافت کرده و پسوند آن‌را به zip. تغییر دهید.
2) سیستم را ری‌استارت کرده و رفتن به گزینه‌ی recovery را انتخاب کنید تا پس از ری‌استارت سیستم، وارد برنامه‌ی TWRP شویم (که روش نصب آن در مطلب جاری بحث شده).
3) در برنامه‌ی TWRP، گزینه‌ی install را انتخاب و سپس فایل zip. برنامه‌ی magisk را انتخاب و نصب کنید.
4) پس از نصب، یکبار هم کش را پاک کنید (Wipe cache/dalvik).
5) در آخر بر روی دکمه‌ی Reboot System تا ... وارد اندروید شوید.
 6) پس از راه اندازی سیستم، برنامه‌ی Magisk را اجرا کنید تا مرحله‌ی آخر نصب آن به پایان برسد (یکسری از فایل‌ها را نیاز دارد تا از اینترنت دریافت و نصب کند؛ این نصب، از github و به صورت خودکار است).

پس از نصب magisk، برنامه‌ی total commander را هم نصب کنید. به محض اجرای آن، پیام درخواست دسترسی root آن هم ظاهر می‌شود (این برنامه‌ی magisk هست که این درخواست را تشخیص داده و مدیریت می‌کند) و نیاز است این دسترسی را بدهید تا بتوان:
 1) به مسیر file system root و سپس apex/com.android.tzdata/etc/tz/tzdata وارد شد و دو فایل موجود و قدیمی tzdata و tz_version را به نحو متداولی پاک کرد.
 2) سپس دو فایل جدید tzdata و tz_version را (که لینک دریافت آن کمی بالاتر ارائه شد) به سیستم خود منتقل کنید (همانند تمام فایل‌های دیگر از طریق usb) و در آخر با استفاده از total commander ای که اکنون دسترسی root هم دارد، این فایل‌ها را به مسیر یاد شده، کپی کنید.
 3) اکنون سیستم را ری‌استارت کنید تا تنظیمات جدید خوانده شوند (همانند تصویر زیر، که در آن منطقه زمانی جدید 3:30+ قابل مشاهده‌است):

یک نکته: « Magisk-Modules-Repo / Systemless_TZData » در صورت به روز شدن، این کار نصب دستی tzdata را به صورت خودکار انجام می‌دهد که در زمان نگارش این مطلب، هنوز به‌روز نشده.

مطالب
تعریف قالب‌های جداول سفارشی و کار با منابع داده‌ای از نوع Anonymous در PdfReport
تعدادی قالب جدول پیش فرض در PdfReport تعریف شده‌اند، مانند BasicTemplate.RainyDayTemplate ،BasicTemplate.SilverTemplate و غیره. نحوه تعریف این قالب‌ها بر اساس پیاده سازی اینترفیس ITableTemplate است. برای نمونه اگر یک قالب جدید را بخواهیم ایجاد کنیم، تنها کافی است اینترفیس یاد شده را به نحو زیر پیاده سازی نمائیم:
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.HexDump
{
    public class GrayTemplate : ITableTemplate
    {
        public HorizontalAlignment HeaderHorizontalAlignment
        {
            get { return HorizontalAlignment.Center; }
        }

        public BaseColor AlternatingRowBackgroundColor
        {
            get { return new BaseColor(Color.WhiteSmoke); }
        }

        public BaseColor CellBorderColor
        {
            get { return new BaseColor(Color.LightGray); }
        }

        public IList<BaseColor> HeaderBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(ColorTranslator.FromHtml("#990000")), new BaseColor(ColorTranslator.FromHtml("#e80000")) }; }
        }

        public BaseColor RowBackgroundColor
        {
            get { return null; }
        }

        public IList<BaseColor> PreviousPageSummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.LightSkyBlue) }; }
        }

        public IList<BaseColor> SummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.LightSteelBlue) }; }
        }

        public IList<BaseColor> PageSummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.Yellow) }; }
        }

        public BaseColor AlternatingRowFontColor
        {
            get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
        }

        public BaseColor HeaderFontColor
        {
            get { return new BaseColor(Color.White); }
        }

        public BaseColor RowFontColor
        {
            get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
        }

        public BaseColor PreviousPageSummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public BaseColor SummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public BaseColor PageSummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public bool ShowGridLines
        {
            get { return true; }
        }
    }
}
و برای استفاده از آن خواهیم داشت:
.MainTableTemplate(template =>
{
      template.CustomTemplate(new GrayTemplate());
})
چند نکته:
- در کتابخانه iTextSharp، کلاس رنگ توسط BaseColor تعریف شده است. به همین جهت خروجی رنگ‌ها را در اینجا نیز بر اساس BaseColor مشاهده می‌کنید. اگر نیاز داشتید رنگ‌های تعریف شده در فضای نام استاندارد System.Drawing را به BaseColor تبدیل کنید، فقط کافی است آن‌را به سازنده کلاس BaseColor ارسال نمائید.
- اگر علاقمند هستید که معادل رنگ‌های HTML ایی را در اینجا داشته باشید، می‌توان از متد توکار ColorTranslator.FromHtml استفاده کرد.
- برای تعریف رنگی به صورت شفاف (transparent) آن‌را مساوی null قرار دهید.
- در اینترفیس فوق، تعدادی از خروجی‌ها به صورت IList است. در این موارد می‌توان یک یا دو رنگ را حداکثر معرفی کرد. اگر دو رنگ را معرفی کنید یک گرادیان خودکار از این دو رنگ، تشکیل خواهد شد.
- اگر قالب جدید زیبایی را طراحی کردید، لطفا در این پروژه مشارکت کرده و آن‌را به صورت یک وصله ارائه دهید!


تهیه یک منبع داده ناشناس

مثال زیر را در نظر بگیرید. در اینجا قصد داریم معادل Ascii اطلاعات Hex را تهیه کنیم:
using System;
using System.Collections;
using System.Linq;

namespace PdfReportSamples.HexDump
{
    public static class PrintHex
    {
        public static char ToSafeAscii(this int b)
        {
            if (b >= 32 && b <= 126)
            {
                return (char)b;
            }
            return '_';
        }

        public static IEnumerable HexDump(this byte[] data)
        {
            int bytesPerLine = 16;
            return data
                        .Select((c, i) => new { Char = c, Chunk = i / bytesPerLine })
                        .GroupBy(c => c.Chunk)
                        .Select(g =>
                                  new
                                  {
                                      Hex = g.Select(c => String.Format("{0:X2} ", c.Char)).Aggregate((s, i) => s + i),
                                      Chars = g.Select(c => ToSafeAscii(c.Char).ToString()).Aggregate((s, i) => s + i)
                                  })
                        .Select((s, i) =>
                                        new
                                        {
                                            Offset = String.Format("{0:d6}", i * bytesPerLine),
                                            Hex = s.Hex,
                                            Chars = s.Chars
                                        });
        }
    }
}
نکته مهم این منبع داده، خروجی IEnumerable آن و Select نهایی عبارت LINQ ایی است که مشاهده می‌کنید. در اینجا اطلاعات به یک شیء ناشناس با اعضای Offset، Hex و Chars نگاشت شده‌اند.
مفهوم فوق از دات نت 3 به بعد تحت عنوان anonymous types در دسترس است. توسط این قابلیت می‌توان یک شیء را بدون نیاز به تعریف ابتدایی آن ایجاد کرد. این نوع‌های ناشناس توسط واژه‌های کلیدی new و var تولید می‌شوند. کامپایلر به صورت خودکار برای هر anonymous type یک کلاس ایجاد می‌کند.

نکته‌ای مهم حین کار با کلاس‌های ناشناس:
کلاس‌های ناشناس به صورت خودکار توسط کامپایلر تولید می‌شوند و ... از نوع internal هم تعریف خواهند شد. به عبارتی در اسمبلی‌های دیگر قابل استفاده نیستند. البته می‌توان توسط ویژگی assembly:InternalsVisibleTo ، تعاریف internal یک اسمبلی را دراختیار اسمبلی دیگری نیز گذاشت. ولی درکل باید به این موضوع دقت داشت و اگر قرار است منبع داده‌ای به این نحو تعریف شود، بهتر است داخل همان اسمبلی تعاریف گزارش باشد.

برای نمایش این نوع اطلاعات حاصل از کوئری‌های LINQ می‌توان از منبع داده پیش فرض AnonymousTypeList به نحو زیر استفاده کرد:
using System;
using System.Text;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.HexDump
{
    public class HexDumpPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\COUR.ttf",
                    Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.TTF");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .PagesHeader(header =>
            {
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                    defaultHeader.Message("Hex Dump");
                });
            })
            .MainTableTemplate(template =>
            {
                template.CustomTemplate(new GrayTemplate());
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
            })
            .MainTableDataSource(dataSource =>
            {
                var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog.");
                var list = data.HexDump();
                dataSource.AnonymousTypeList(list);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("Offset");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(0.5f);
                    column.HeaderCell("Offset");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("Hex");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(2.5f);
                    column.HeaderCell("Hex");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("Chars");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(1f);
                    column.HeaderCell("Chars");
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\HexDumpSampleRpt.pdf"));
        }
    }
}

توضیحات:
در اینجا منبع داده بر اساس کلاس‌های کمکی که تعریف کردیم، به نحو زیر مشخص شده است:
            .MainTableDataSource(dataSource =>
            {
                var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog.");
                var list = data.HexDump();
                dataSource.AnonymousTypeList(list);
            })
و سپس برای معرفی ستون‌های متناظر با این منبع داده ناشناس، فقط کافی است آن‌ها را به صورت رشته‌ای معرفی کنیم:
column.PropertyName("Offset");
//...
column.PropertyName("Hex");
//...
column.PropertyName("Chars");



نکته‌ای در مورد خواص تودرتو:
در حین استفاده از AnonymousTypeList امکان تعریف خواص تو در تو نیز وجود دارد. برای مثال فرض کنید که Select نهایی به شکل زیر تعریف شده است و در اینجا OrderInfoData نیز خود یک شیء است:
.Select(x => new
{
   OrderInfo = x.OrderInfoData
})
برای استفاده از یک چنین منبع داده‌ای، ذکر مسیر خاصیت تودرتوی مورد نظر نیز مجاز است:
column.PropertyName("OrderInfo.Price");

 
مطالب
SharePoint2010 و قابلیت FileStream

اگر خاطرتان باشد یک مقاله سه قسمتی در مورد قابلیت جدید FileStream مربوط به SQL Server 2008 چندی قبل در این سایت منتشر شد (+ و + و +).
خبر خوش این است که این قابلیت تحت عنوان Remote Blob Storage یا RBS در شیرپوینت 2010 (که نسخه‌ی بتای آن یکی دو روزی است که به مشترکین MSDN ارائه شده) قابل استفاده می‌باشد و به این صورت می‌توان به سادگی از مزایای این فناوری جدید بهره‌مند شد.
مستندات رسمی فعال سازی این قابلیت در شیرپوینت 2010:


این قابلیت Remote Blob Storage در شیرپوینت 2007 هم قابل پیاده سازی است اما پشتیبانی رسمی نمی‌شود:


پ.ن.
ارزش این چند سطری که مطالعه فرمودید حدود یک میلیون و 200 هزار تومان مطابق قیمتی است که از یکی از شرکت‌های داخلی مدعی اختراع این فناوری برای شیرپوینت 2007، دریافت شده است!

مطالب
تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد
در زمان ساخت مدل از بانک اطلاعاتی در روش Database First به صورت پیش فرض تنظیمات مربوط به اتصال (Connection String) مدل به بانک اطلاعاتی در فایل config برنامه ذخیره می‌شود. مشکل این روش آن است که در سیستم‌های مختلف، بسته به بستری که نرم افزار قرار است بر روی آن اجرا شود، باید تنظیمات مربوط به بانک اطلاعاتی صورت گیرد.
مثلا فرض کنید شما در زمان توسعه نرم افزار، SQL Server را به صورت Local بر روی سیستم خود نصب کرده اید و Connection String ساخته شده توسط ویزارد Entity Framework بر همین اساس ساخته و ذخیره شده‌است. حال بعد از انتشار برنامه، شخصی تصمیم دارد برنامه را بر روی سیستمی نصب کند که بانک اطلاعاتی Local نداشته و تصمیم به اتصال به یک بانک اطلاعاتی بر روی سرور دیگر یا با مشخصات (Login و Password و ...) دیگر را دارد. برای این مواقع نیاز به پیاده سازی روشی است تا کاربر نهایی بتواند تنظیمات مربوط به اتصال به بانک اطلاعاتی را تغییر دهد.
روش‌های مختلفی مثل تغییر فایل app.config به صورت Runtime یا ... در سایت‌های مختلف ارائه شده که اکثرا روش‌های غیر اصولی و زمانبری جهت پیاده سازی هستند.
ساده‌ترین روش جهت انجام این کار، اعمال تغییری کوچک در Constructor کلاس مدل مشتق شده از DBContext می‌باشد. فرض کنید مدلی از بانک اطلاعاتی Personnely با نام PersonallyEntities ساخته اید که حاصل آن کلاس زیر خواهد بود:
    public partial class PersonallyEntities : DbContext
    {
        public PersonallyEntities()
            : base("name=PersonallyEntities")
        {
        }
    }
همانطور که مشاهده می‌کنید، در Constructor این کلاس، نام Connection String مورد استفاده جهت اتصال به بانک اطلاعاتی به صورت زیر آورده شده که به Connection String ذخیره شده در فایل Config اشاره می‌کند:
"name=PersonallyEntities"
اگر به Connection String ذخیره شده در فایل Config دقت کنید متوجه می‌شوید که Connection String ذخیره شده، دارای فرمتی خاص و متفاوتی نسبت به Connection String معمولی ADO.NET است. متن ذخیره شده شامل تنظیمات و Metadata مدل ساخته شده جهت ارتباط با بانک اطلاعاتی نیز می‌باشد:
 metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=Personally;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
جهت تولید پویای Connection String، بسته به تنظیمات کاربر، نیاز است تا در آخر Connection String ی با فرمت بالا در اختیار Entity Framework قرار دهیم تا امکان اتصال به بانک فراهم شود. جهت تبدیل Connection String معمول ADO.NET به Connection String قابل فهم EF میتوان از کلاس EntityConnectionStringBuilder به صورت زیر استفاده کرد:
        public static string BuildEntityConnection(string connectionString)
        {
            var entityConnection = new EntityConnectionStringBuilder
            {
                Provider = "System.Data.SqlClient",
                ProviderConnectionString = connectionString,
                Metadata = "res://*"
            };

            return entityConnection.ToString();
        }
همانطور که مشاهده می‌کنید، متد بالا با دریافت یک connectionString که همان ADO.NET ConnectionString ما می‌باشد، تنظیمات و Metadata مورد نیاز Entity Framework را به آن اضافه کرده و یک EF ConnectionString برمی‌گرداند.
برای اینکه بتوان EF ConnectionString تولید شده را در هنگام اجرای برنامه به صورت Runtime اعمال کرد، نیاز است تا تغییر کوچکی در Constructor کلاس مدل تولید شده توسط Entity Framework ایجاد کرد. کلاس PersonnelyEntities به صورت زیر تغییر پیدا می‌کند:

    public partial class PersonallyEntities : DbContext
    {
        public PersonallyEntities(string connectionString)
            : base(connectionString)
        {

        }
    }
با اضافه شدن پارامتر connectionString به سازنده کلاس PersonnelyEntities برای ساخت یک نمونه از مدل ساخته شده در کد نیاز است تا Connection String مورد نظر جهت برقراری ارتباط با بانک را به عنوان پارامتر، به متد سازنده پاس دهیم. سپس مقدار این پارامتر به کلاس والد ( DbContext ) جهت برقراری ارتباط با بانک اطلاعاتی ارجاع داده شده: 
: base(connectionString)
در آخر به صورت زیر میتوان توسط EF به بانک اطلاعاتی مورد نظر متصل شد :
var entityConnectionString = BuildeEntityConnection("Data Source=localhost;Initial Catalog=Personally; Integrated Security=True");
var PersonallyDb = new PersonallyEntities(entityConnectionString);
با این روش میتوان ADO Connection String مربوط به اتصال بانک اطلاعاتی را به راحتی به صورت داینامیک به وسیله اطلاعات وارد شده توسط کاربر و کلاس‌های تولید Connection String نظیر SQLConnectionStringBuilder تولید کرد و بدون تغییر در کد‌های برنامه، به بانک‌های مختلفی متصل شد. همچنین با داینامیک کردن متد Provider کلاس EntityConnectionStringBuilder که در کد بالا با "System.Data.SqlClient" مقدار دهی شده، می‌توان وابستگی برنامه بانک اطلاعی خاص را از بین برد و بسته به تنظیمات مورد نظر کاربر، به موتورهای مختلف بانک اطلاعاتی متصل شد که البته لازمه این کار رعایت یکسری نکات فنی در پیاده سازی پروژه است که از حوصله این مقاله خارج است.
موفق باشید
مطالب
EF Code First #6

ادامه بررسی Fluent API جهت تعریف نگاشت کلاس‌ها به بانک اطلاعاتی

در قسمت‌های قبل با استفاده از متادیتا و data annotations جهت بررسی نحوه نگاشت اطلاعات کلاس‌ها به جداول بانک اطلاعاتی آشنا شدیم. اما این موارد تنها قسمتی از توانایی‌های Fluent API مهیا در EF Code first را ارائه می‌دهند. یکی از دلایل آن هم به محدود بودن توانایی‌های ذاتی Attributes بر می‌گردد. برای مثال حین کار با Attributes امکان استفاده از متغیرها یا lambda expressions و امثال آن وجود ندارد. به علاوه شاید عده‌ای علاقمند نباشند تا کلاس‌های خود را با data annotations شلوغ کنند.

در قسمت دوم این سری، مروری مقدماتی داشتیم بر Fluent API. در آنجا ذکر شد که امکان تعریف نگاشت‌ها به کمک توانایی‌های Fluent API به دو روش زیر میسر است:
الف) می‌توان از متد protected override void OnModelCreating در کلاس مشتق شده از DbContext کار را شروع کرد.
ب) و یا اگر بخواهیم کلاس Context برنامه را شلوغ نکنیم بهتر است به ازای هر کلاس مدل برنامه، یک کلاس mapping مشتق شده از EntityTypeConfiguration را تعریف نمائیم. سپس می‌توان این کلاس‌ها را در متد OnModelCreating یاد شده، توسط متد modelBuilder.Configurations.Add جهت استفاده و اعمال، معرفی کرد.

کلاس‌های مدلی را که در این قسمت بررسی خواهیم کرد، همان کلاس‌های User و Project قسمت سوم هستند و هدف این قسمت بیشتر تطابق Fluent API با اطلاعات ارائه شده در قسمت سوم است؛ برای مثال در اینجا چگونه باید از خاصیتی صرفنظر کرد، مسایل همزمانی را اعمال نمود و امثال آن.
بنابراین یک پروژه جدید کنسول را آغاز نمائید. سپس با کمک NuGet ارجاعات لازم را به اسمبلی‌های EF اضافه نمائید.
در پوشه Models این پروژه، سه کلاس تکمیل شده زیر، از قسمت سوم وجود دارند:
using System;
using System.Collections.Generic;

namespace EF_Sample03.Models
{
public class User
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public string LastName { set; get; }

public string FullName
{
get { return Name + " " + LastName; }
}

public string Email { set; get; }
public string Description { set; get; }
public byte[] Photo { set; get; }
public IList<Project> Projects { set; get; }
public byte[] RowVersion { set; get; }
public InterestComponent Interests { set; get; }

public User()
{
Interests = new InterestComponent();
}
}
}

using System;

namespace EF_Sample03.Models
{
public class Project
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Title { set; get; }
public string Description { set; get; }
public virtual User User { set; get; }
public byte[] RowVesrion { set; get; }
}
}

namespace EF_Sample03.Models
{
public class InterestComponent
{
public string Interest1 { get; set; }
public string Interest2 { get; set; }
}
}


سپس یک پوشه جدید به نام Mappings را به پروژه اضافه نمائید. به ازای هر کلاس فوق، یک کلاس جدید را جهت تعاریف اطلاعات نگاشت‌ها به کمک Fluent API اضافه خواهیم کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;

namespace EF_Sample03.Mappings
{
public class InterestComponentConfig : ComplexTypeConfiguration<InterestComponent>
{
public InterestComponentConfig()
{
this.Property(x => x.Interest1).HasMaxLength(450);
this.Property(x => x.Interest2).HasMaxLength(450);
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;

namespace EF_Sample03.Mappings
{
public class ProjectConfig : EntityTypeConfiguration<Project>
{
public ProjectConfig()
{
this.Property(x => x.Description).IsMaxLength();
this.Property(x => x.RowVesrion).IsRowVersion();
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample03.Mappings
{
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.ToTable("tblUser", schemaName: "guest");
this.Property(p => p.AddDate).HasColumnName("CreateDate").HasColumnType("date").IsRequired();
this.Property(x => x.Name).HasMaxLength(450);
this.Property(x => x.LastName).IsMaxLength().IsConcurrencyToken();
this.Property(x => x.Email).IsFixedLength().HasMaxLength(255); //nchar(128)
this.Property(x => x.Photo).IsOptional();
this.Property(x => x.RowVersion).IsRowVersion();
this.Ignore(x => x.FullName);
}
}
}

توضیحاتی در مورد کلاس‌های تنظیمات نگاشت‌های خواص به جداول و فیلدهای بانک اطلاعاتی

نظم بخشیدن به تعاریف نگاشت‌ها
همانطور که ملاحظه می‌کنید، جهت نظم بیشتر پروژه و شلوغ نشدن متد OnModelCreating کلاس Context برنامه، که در ادامه کدهای آن معرفی خواهد شد، به ازای هر کلاس مدل، یک کلاس تنظیمات نگاشت‌ها را اضافه کرده‌ایم.
کلاس‌های معمولی نگاشت‌ها ازکلاس EntityTypeConfiguration مشتق خواهند شد و جهت تعریف کلاس InterestComponent به عنوان Complex Type، اینبار از کلاس ComplexTypeConfiguration ارث بری شده است.

تعیین طول فیلدها
در کلاس InterestComponentConfig، به کمک متد HasMaxLength، همان کار ویژگی MaxLength را می‌توان شبیه سازی کرد که در نهایت، طول فیلد nvarchar تشکیل شده در بانک اطلاعاتی را مشخص می‌کند. اگر نیاز است این فیلد nvarchar از نوع max باشد، نیازی به تنظیم خاصی نداشته و حالت پیش فرض است یا اینکه می‌توان صریحا از متد IsMaxLength نیز برای معرفی nvarchar max استفاده کرد.

تعیین مسایل همزمانی
در قسمت سوم با ویژگی‌های ConcurrencyCheck و Timestamp آشنا شدیم. در اینجا اگر نوع خاصیت byte array بود و نیاز به تعریف آن به صورت timestamp وجود داشت، می‌توان از متد IsRowVersion استفاده کرد. معادل ویژگی ConcurrencyCheck در اینجا، متد IsConcurrencyToken است.

تعیین کلید اصلی جدول
اگر پیش فرض‌های EF Code first مانند وجود خاصیتی به نام Id یا ClassName+Id رعایت شود، نیازی به کار خاصی نخواهد بود. اما اگر این قراردادها رعایت نشوند،‌ می‌توان از متد HasKey (که نمونه‌ای از آن‌را در کلاس UserConfig فوق مشاهده می‌کنید)، استفاده کرد.

تعیین فیلدهای تولید شده توسط بانک اطلاعاتی
به کمک متد HasDatabaseGeneratedOption،‌ می‌توان مشخص کرد که آیا یک فیلد Identity است و یا یک فیلد محاسباتی ویژه و یا هیچکدام.

تعیین نام جدول و schema آن
اگر نیاز است از قراردادهای نامگذاری خاصی پیروی شود، ‌می‌توان از متد ToTable جهت تعریف نام جدول متناظر با کلاس جاری استفاده کرد. همچنین در اینجا امکان تعریف schema نیز وجود دارد.

تعیین نام و نوع سفارشی فیلدها
همچنین اگر نام فیلدها نیز باید از قراردادهای دیگری پیروی کنند، می‌توان آن‌ها را به صورت صریح توسط متد HasColumnName معرفی کرد. اگر نیاز است این خاصیت به نوع خاصی در بانک اطلاعاتی نگاشت شود، باید از متد HasColumnType کمک گرفت. برای مثال در اینجا بجای نوع datetime، از نوع ویژه date استفاده شده است.

معرفی فیلدها به صورت nchar بجای nvarchar
برای نمونه اگر قرار است هش کلمه عبور در بانک اطلاعاتی ذخیره شود، چون طول آن ثابت می‌باشد، توصیه شده‌است که بجای nvarchar از nchar برای تعریف آن استفاده شود. برای این منظور تنها کافی است از متد IsFixedLength استفاده شود. در این حالت طول پیش فرض 128 برای فیلد درنظر گرفته خواهد شد. بنابراین اگر نیاز است از طول دیگری استفاده شود، می‌توان همانند سابق از متد HasMaxLength کمک گرفت.
ضمنا این فیلدها همگی یونیکد هستند و با n شروع شده‌اند. اگر می‌خواهید از varchar یا char استفاده کنید، می‌توان از متد IsUnicode با پارامتر false استفاده کرد.

معرفی یک فیلد به صورت null پذیر در سمت بانک اطلاعاتی
استفاده از متد IsOptional، فیلد را در سمت بانک اطلاعاتی به صورت فیلدی با امکان پذیرش مقادیر null معرفی می‌کند.
البته در اینجا به صورت پیش فرض byte arrayها به همین نحو معرفی می‌شوند و تنظیم فوق صرفا جهت ارائه توضیحات بیشتر در نظر گرفته شد.

صرفنظر کردن از خواص محاسباتی در تعاریف نگاشت‌ها
با توجه به اینکه خاصیت FullName به صورت یک خاصیت محاسباتی فقط خواندنی، در کدهای برنامه تعریف شده است، با استفاده از متد Ignore، از نگاشت آن به بانک اطلاعاتی جلوگیری خواهیم کرد.


معرفی کلاس‌های تعاریف نگاشت‌ها به برنامه

استفاده از کلاس‌های Config فوق خودکار نیست و نیاز است توسط متد modelBuilder.Configurations.Add معرفی شوند:

using System.Data.Entity;
using System.Data.Entity.Migrations;
using EF_Sample03.Mappings;
using EF_Sample03.Models;

namespace EF_Sample03.DataLayer
{
public class Sample03Context : DbContext
{
public DbSet<User> Users { set; get; }
public DbSet<Project> Projects { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new InterestComponentConfig());
modelBuilder.Configurations.Add(new ProjectConfig());
modelBuilder.Configurations.Add(new UserConfig());

//modelBuilder.ComplexType<InterestComponent>();
//modelBuilder.Ignore<InterestComponent>();

base.OnModelCreating(modelBuilder);
}
}

public class Configuration : DbMigrationsConfiguration<Sample03Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample03Context context)
{
base.Seed(context);
}
}
}

در اینجا کلاس Context برنامه مثال جاری را ملاحظه می‌کنید؛ به همراه کلاس Configuration مهاجرت خودکار که در قسمت‌های قبل بررسی شد.
در متد OnModelCreating نیز می‌توان یک کلاس را از نوع Complex معرفی کرد تا برای آن در بانک اطلاعاتی جدول جداگانه‌ای تعریف نشود. اما باید دقت داشت که اینکار را فقط یکبار می‌توان انجام داد؛ یا توسط کلاس InterestComponentConfig و یا توسط متد modelBuilder.ComplexType. اگر هر دو با هم فراخوانی شوند، EF یک استثناء را صادر خواهد کرد.

و در نهایت، قسمت آغازین برنامه اینبار به شکل زیر خواهد بود که از آغاز کننده MigrateDatabaseToLatestVersion (قسمت چهارم این سری) نیز استفاده کرده است:

using System;
using System.Data.Entity;
using EF_Sample03.DataLayer;

namespace EF_Sample03
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample03Context, Configuration>());

using (var db = new Sample03Context())
{
var project1 = db.Projects.Find(1);
if (project1 != null)
{
Console.WriteLine(project1.Title);
}
}
}
}
}

ضمنا رشته اتصالی مورد استفاده تعریف شده در فایل کانفیک برنامه نیز به صورت زیر تعریف شده است:

<connectionStrings>
<clear/>
<add
name="Sample03Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
/connectionStrings>


در قسمت‌های بعد مباحث پیشرفته‌تری از تنظیمات نگاشت‌ها را به کمک Fluent API، بررسی خواهیم کرد. برای مثال روابط ارث بری، many-to-many و ... چگونه تعریف می‌شوند.


نظرات مطالب
کار با Kendo UI DataSource
- «خطا می‌گیره» برای بررسی کافی نیست. اطلاعات بیشتر
دفعه‌ی قبل هم همین رویه را تکرار کردی و پاسخی نگرفتی. چون برای رفع مشکل، اگر نتوان شرایط شما را خیلی «سریع»، «تکرار» کرد، امکان دیباگ و رفع اشکال آن هم نیست. بنابراین اگر قسمتی از یک پروژه‌ی بزرگ را اینجا عنوان کنید که کار نمی‌کند، هیچ کسی کمکی به شما نخواهد کرد (چون امکان بررسی و شبیه سازی جزئیات آن از راه دور میسر نیست).
بنابراین قبل از اینکه سؤالی را مطرح کنید، این سؤال را خودتان بپرسید:
- آیا طرف مقابل می‌تواند به «سرعت» مشکل من را شبیه سازی و «تکرار» کند؟
اگر پاسخ آن خیر است، احتمال اینکه پاسخ مناسبی را دریافت کنید، خیلی کم است.

- نیازی به تنظیم ProxyCreationEnabled = false در این قسمت از کد نیست و روش بهتری برای آن وجود دارد. اطلاعات بیشتر
- اگر قسمتی از اسکیمای JSON شما چنین شکلی را دارد:
"address":{
   "street":"test 59",
   "city":"City test",
   "post_number":"25050"
},
معادل Kendo UI Data source آن به صورت زیر خواهد بود:
columns : [
        { field: "address.street", title: "Street" },
        { field: "address.city", title: "City" },
        { field: "address.post_number", title: "Post#" }
    ]
یک مثال کامل
نظرات مطالب
نحوه صحیح تولید Url در ASP.NET MVC
فرم ادیت رو به صورت strongly typed از نوع یک partial view مستقل درست کنید.
سپس در کنترلر مرتبط، یک اکشن متد را مخصوص رندر کردن این partial view در نظر بگیرید. کار آن دریافت اطلاعات مرتبط با Model ارسالی به همین partial view است. سپس در آخر کار آن هم خواهیم داشت:

return PartialView("_MyPartialViewName", data);
حالا فراخوانی این اکشن متد توسط jQuery Ajax سبب پر شدن خودکار فیلدهای فرم strongly typed شما هم می‌شود. در حین درخواست، اطلاعات مدل از بانک اطلاعاتی دریافت شده و به Partial view ارسال می‌شود. چون strongly typed است، فیلدهای آن به صورت خودکار پر شده و نهایتا کل partial view به صورت یک رشته آماده شده در اختیار شما خواهد بود. بنابراین، قسمت عمده‌ای از کدهای سمت کاربر فوق کاهش پیدا می‌کنند. چون یک view کامل حاضر و آماده از سرور دریافت شده است که باید به صفحه توسط jQuery اضافه شود.

+
لطفا اینجا رو تبدیل به یک انجمن عمومی نکنید. عنوان بحث ساخت Url بود ... بعد تغییر جهت پیدا کرد به یک عنوان دیگر.
با تشکر از توجه شما.