مطالب
مونیتور کردن میزان مصرف CPU در اس کیوال سرور

در این مقاله قصد داریم نحوه مونیتور کردن میزان مصرف CPU توسط اس کیوال سرور را بررسی کنیم. برای بدست آوردن میزان CPU مصرفی اس کیوال سرور می‌توان به اسکریپت زیر رجوع کرد:

DECLARE @CPU_BUSY int, @IDLE int
SELECT @CPU_BUSY = @@CPU_BUSY, @IDLE = @@IDLE WAITFOR DELAY '000:00:01'
SELECT (@@CPU_BUSY - @CPU_BUSY)/((@@IDLE - @IDLE + @@CPU_BUSY - @CPU_BUSY) *1.00) *100 AS 'CPU Utilization by sqlsrvr.exe'

ماخذ

در ادامه قصد داریم، هر 5 دقیقه به صورت خودکار بررسی کنیم که آیا میزان مصرف CPU در اس کیوال سرور بالای 50 درصد است؟ و اگر بله، ایمیلی را به مسؤول مربوطه جهت بررسی ارسال کنیم.

بنابراین اولین کاری که باید صورت گیرد، فعال سازی Database Mail در اس کیوال سرور است که به صورت پیش فرض غیرفعال است. برای این منظور تنها کافی است اسکریپت زیر را بر روی سرور اجرا کنید:

USE [master]
GO
sp_configure 'show advanced options',1
GO
RECONFIGURE WITH OVERRIDE
GO
sp_configure 'Database Mail XPs',1
GO
--RECONFIGURE
GO
-- Create a New Mail Profile for Notifications
EXECUTE msdb.dbo.sysmail_add_profile_sp
@profile_name = 'DBA_Notifications',
@description = 'Profile for sending Automated DBA Notifications'
GO
-- Set the New Profile as the Default
EXECUTE msdb.dbo.sysmail_add_principalprofile_sp
@profile_name = 'DBA_Notifications',
@principal_name = 'public',
@is_default = 1 ;
GO
-- Create an Account for the Notifications
EXECUTE msdb.dbo.sysmail_add_account_sp
@account_name = 'SQLMonitor',
@description = 'Account for Automated DBA Notifications',
@email_address = 'nasiri@site.net', -- Change This
@display_name = 'SQL Monitor',
@mailserver_name = 'mail.site.net' -- Change This
GO
-- Add the Account to the Profile
EXECUTE msdb.dbo.sysmail_add_profileaccount_sp
@profile_name = 'DBA_Notifications',
@account_name = 'SQLMonitor',
@sequence_number = 1

GO

ماخذ

این اسکریپت برای اس کیوال سرورهای 2005 به بعد طراحی شده و تنها دو سطر آن‌را پیش از اجرا باید ویرایش کنید. سطر مربوط به email_address و mailserver_name . آدرس ایمیل درحقیقت آدرس ایمیل قسمت from پیغام ارسالی را تشکیل می‌دهد. نام سرور میل هم، منظور آدرس smtp server شما در شبکه است.

یا اگر علاقمند بودید که این‌کار را توسط ویزاردهای management studio انجام دهید (که در نهایت هیچ تفاوتی با اسکریپت فوق نخواهد داشت)، می‌توان به این مقاله رجوع کرد.

پس از اجرای اسکریپت فوق، برای بررسی صحت عملکرد فوق می‌توان دستور زیر را اجرا کرد:

--test
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@Subject = 'Test Message generated from SQL Server DatabaseMail',
@Body = 'This is a test message from SQL Server DatabaseMail'

سایر پارامترهای این دستور را در MSDN می‌توان ملاحظه نمود.

تا اینجا اس کیوال سرور برای ارسال ایمیل آماده شد. در ادامه قصد داریم یک job جدید در اس کیوال سرور ایجاد کنیم تا تمام موارد فوق را لحاظ کند.


مطابق تصویر فوق ابتدا یک job جدید را آغاز خواهیم کرد.



در ادامه اسکریپت زیر را جهت اجرا به آن معرفی می‌کنیم. توسط این اسکریپت، میزان جاری مصرف CPU اس کیوال سرور محاسبه شده و اگر این میزان بیشتر از 50 بود، یک ایمیل به مسؤول مربوطه با ذکر میزان CPU usage ارسال می‌گردد.

DECLARE @CPUUsage INT
DECLARE @CPU_BUSY INT,
@IDLE INT

SELECT @CPU_BUSY = @@CPU_BUSY,
@IDLE = @@IDLE

WAITFOR DELAY '000:00:01'
SELECT @CPUUsage = (@@CPU_BUSY - @CPU_BUSY) / ((@@IDLE - @IDLE + @@CPU_BUSY - @CPU_BUSY) * 1.00)
* 100 -- CPU Utilization by sqlsrvr.exe


IF @CPUUsage > 50
BEGIN
DECLARE @msg NVARCHAR(1000)
SET @msg = 'Please check SQL server, CPU usage is ' + CAST(@CPUUsage AS NVARCHAR(50))
+ '%.'

EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'nasiri@site.net', -- Change This
@Subject = 'CPU overload',
@Body = @msg
,@importance = 'High'
END



و در آخر زمان اجرای آن را به هر روز، هر 5 دقیقه یکبار تنظیم خواهیم کرد.

اگر نیاز به راه حلی پخته‌تر و بررسی متوسط چندین مقدار قبلی ، مقایسه آن‌ها و سپس ارسال ایمیل داشتید، می‌توان به فصل 14 کتاب Super SQL Server Systems مراجعه کرد.

مطالب
اس کیوال سرور 2008 و عملگرهای C مانند

اگر با زبان C و مشتقات آن آشنایی داشته باشید، حتما با عملگرهای ترکیبی آن‌ها که جهت خلاصه نویسی بکار می‌روند، نیز کار کرده‌اید. برای مثال:

int i =5;
i += 15; // i = i + 15;

اس کیوال سرور 2008 نیز از اینگونه عملگرها پشتیبانی به عمل می‌آورد. برای نمونه:
DECLARE @x1 int = 27;
SET @x1 += 2 ;
SELECT @x1 AS Added_2;
در دستورات T-SQL فوق دو نکته قابل توجه است:
الف) امکان تعریف و مقدار دهی همزمان یک متغیر (مقدار دهی همزمان با تعریف، تا قبل از اس کیوال سرور 2008 پشتیبانی نمی‌شد)
ب) امکان استفاده از عملگرهای C مانند در عبارات T-SQL

لیست این عملگرهای جدید به شرح زیر است:
+= (Add EQUALS) (Transact-SQL)
-= (Subtract EQUALS) (Transact-SQL)
*= (Multiply EQUALS) (Transact-SQL)
/= (Divide EQUALS) (Transact-SQL)
%= (Modulo EQUALS) (Transact-SQL)
&= (Bitwise AND EQUALS) (Transact-SQL)
^= (Bitwise Exclusive OR EQUALS) (Transact-SQL)
|= (Bitwise OR EQUALS) (Transact-SQL)

نظرات مطالب
امکان انجام محاسبات سمت کلاینت در EF Core
ارتقاء به EF Core 3.0
تا پیش از EF Core 3.0، استفاده از قابلیت Client-Side Evaluation در هر قسمتی از کوئری میسر است که سبب شده استفاده‌های نادرستی از آن صورت گیرد و کارآیی کوئر‌ی‌ها بی‌جهت کاهش یابد. از نگارش 3 به بعد، این نوع محاسبات فقط در قسمت Select نهایی مجاز است و نه هیچ قسمت دیگری از کوئری؛ در غیراینصورت یک استثناء را دریافت خواهید کرد. برای نمونه در مثالی که در اینجا ارائه شده، از متد ComputeHash در قسمت Where کوئری استفاده شده‌است که اکنون در EF Core 3.0 دیگر مجاز نیست. اگر نیاز است چنین کاری را انجام دهید، خودتان یک ToList را بر روی کوئری، فراخوانی کنید و سپس بر روی نتیجه‌ی LINQ to Objects حاصل، یک Where را بنویسید.
اشتراک‌ها
اجرای کوئری باتاخیر و اجرای کوئری بلافاصله
در اجرای کوئری باتاخیر عبارات نوشته شده با LINQ، وقتی شما داخل کد برنامه ای که نوشتید از کوئری LINQ استفاده کردید، هنگام اجرا، وقتی برنامه به کوئری LINQ می‌رسد، در اصل کوئری اجرا نمی‌شود، بلکه هنگامی کوئری اجرا می‌شود که از نتایج کوئری استفاده شود. به همین دلیل به اجرای کوئری‌های LINQ در زبان انگلیسی Deferred-Query-Execution یا اجرای کوئری با تاخیر نیز می‌گویند. البته می‌توان کوئری‌ها را در همان خطی که کوئری نوشته شده است اجرا کرد. این کار به استفاده از متد‌های ToList، ToArray، ToDictionary و ToLookup امکان پذیر است. این متد‌ها نتیجه کوئری را به یکی از مجموعه‌های List، Array، Dictionary و یا LookUp تبدیل می‌کنند. 
اجرای کوئری باتاخیر و اجرای کوئری بلافاصله
مطالب دوره‌ها
تهیه کوئری بر روی ایندکس‌های Full Text Search
در دو قسمت قبل ابتدا سیستم FTS را نصب و فعال کردیم و سپس تعدادی رکورد را ثبت کرده، کاتالوگ‌های FTS، ایندکس‌ها و Stop words متناظری را ایجاد کردیم. در این قسمت قصد داریم از این اطلاعات ویژه، استفاده کرده و کوئری بگیریم. مواردی که بررسی خواهند شد اصطلاحا Predicates نام داشته و شامل توابع مخصوصی مانند Contains و Freetext می‌شوند.


با استفاده از Contains predicate چه اطلاعاتی را می‌توان جستجو کرد؟

متد Contains مخصوص FTS، قابلیت یافتن کلمات و عبارات، تطابق کامل با عبارت در حال جستجو و یا حتی جستجوهای فازی را دارد. همچنین حالات مختلف صرفی یا inflectional یک کلمه را نیز می‌تواند جستجو کند (مانند jump، jumps و jumped). البته این مورد وابسته است به زبانی که در حین ایجاد ایندکس مشخص می‌شود. امکان یافتن کلماتی نزدیک و مشابه به کلماتی دیگر نیز پیش بینی شده‌است. پیشوندها و پسوندها را نیز می‌توان جستجو کرد. امکان تعیین وزن و اهمیت کلمات در حال جستجو وجود دارند (برای مثال در این جستجوی خاص، کلمه‌ی ویژه اهمیت بیشتری نسبت به بقیه دارد). متد Contains امکان جستجوی Synonyms را نیز دارد. برای مثال یافتن رکوردهایی که معنایی مشابه need دارند اما دقیقا حاوی کلمه‌ی need نیستند.


بررسی ریز جزئیات توانمندی‌های Contains predicate

1) جستجوی کلمات ساده
 -- Simple term
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'data');
در این کوئری که بر روی جدول Documents قسمت قبل انجام می‌شود، به دنبال عین واژه‌ی در حال جستجو هستیم.
باید دقت داشت که این نوع کوئری‌ها، حساس به حروف کوچک و بزرگ نیستند.
همچنین عبارت وارد شده از نوع یونیکد است. به همین جهت برای جلوگیری از تغییر encoding رشته وارد شده (و تفسیر آن بر اساس Collation بانک اطلاعاتی)، یک N به ابتدای عبارت افزوده شده‌است.

2) جستجوی عبارات
 -- Simple term - phrase
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'"data warehouse"');
اگر نیاز به یافتن عین عبارتی که از چند کلمه تشکیل شده‌است می‌باشد، نیاز است آن‌را با "" محصور کرد.

3) استفاده از عملگرهای منطقی مانند OR و AND
 -- Simple terms with logical OR
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'data OR index');
در این کوئری نحوه‌ی استفاده از عملگر منطقی OR را مشاهده می‌کنید.
و یا نحوه‌ی بکارگیری AND NOT در کوئری ذیل مشخص شده‌است:
 -- Simple terms with logical AND NOT
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'data AND NOT mining');
در این کوئری به دنبال رکوردهایی هستیم که docexcerpt آن‌ها دارای کلمه‌ی data بوده، اما شامل mining نمی‌شوند.
به علاوه با استفاده از پرانتزها می‌توان تقدم و تاخر عملگرهای منطقی را بهتر مشخص کرد:
 -- Simple terms with mny logical operators, order defined with parentheses
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'data OR (fact AND warehouse)');

4) جستجوی پیشوندها
 -- Prefix
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'"add*"');
در کوئری فوق به دنبال رکوردهایی هستیم که docexcerpt آن‌ها با کلمه‌ی add شروع می‌شوند. در این حالت نیز استفاده از "" اجباری است. اگر از "" استفاده نشود، FTS به دنبال تطابق عینی با عبارت وارد شده خواهد گشت.

5) جستجوهای Proximity

Proximity در اینجا به معنای یافتن واژه‌هایی هستند که نزدیک (از لحاظ تعداد فاصله بر حسب کلمات) به واژه‌ای دیگر می‌باشند.
 -- Simple proximity
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'NEAR(problem, data)');
برای این منظور از واژه‌ی NEAR استفاده می‌شود؛ به همراه ذکر دو واژه‌ای که به دنبال آن‌ها هستیم. معنای کوئری فوق این است: رکوردهایی را پیدا کن که در آن در یک جایی از خلاصه سند، کلمه‌ی problem وجود دارد و در جایی دیگر از آن خلاصه‌ی سند، کلمه‌ی data.
همچنین می‌توان مشخص کرد که این نزدیک بودن دقیقا به چه معنایی است:
 -- Proximity with max distance 5 words
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),5)');

-- Proximity with max distance 1 word
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),1)');
در این کوئری‌ها اعداد 1 و 5، بیانگر فاصله‌ی بین دو کلمه‌‌ای هستند (فاصله بر اساس تعداد کلمه) که قرار است در نتایج جستجو حضور داشته باشند. مقدار پیش فرض آن Max است؛ یعنی در هر جایی از سند.
همچنین می‌توان مشخص کرد که ترتیب جستجو باید دقیقا بر اساس نحوه‌ی تعریف این کلمات در کوئری باشد:
 -- Proximity with max distance and order
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),5, TRUE)');
GO
پارامتر آخر یا flag، به صورت پیش فرض false است. به این معنا که ترتیب این دو کلمه در جستجو اهمیتی ندارند.

6) جستجوی بر روی بیش از یک فیلد
در قسمت قبل، FULLTEXT INDEX انتهای بحث را بر روی دو فیلد docexcerpt و doccontent تهیه کردیم. اگر نیاز باشد تا جستجوی انجام شده هر دو فیلد را شامل شود می‌توان به نحو ذیل عمل کرد:
 SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS((docexcerpt,doccontent), N'data');
در این حالت تنها کافی است دو فیلد را داخل یک پرانتز قرار داد.

یک نکته: اگر تعداد ستون‌های ایندکس شده زیاد است و نیاز داریم تا بر روی تمام آن‌ها FTS انجام شود، تنها کافی است پارامتر اول متد Contains را * وارد کنیم. * در اینجا به معنای تمام ستون‌هایی است که در حین تشکیل FULLTEXT INDEX ذکر شده‌اند.

7) جستجوهای صرفی یا inflectional
FTS بر اساس زبان انتخابی، در حین تشکیل ایندکس‌های خاص خودش، یک سری آنالیزهای دستوری را نیز بر روی واژه‌ها انجام می‌دهد. همچنین امکان تعریف زبان مورد استفاده در حین استفاده از متد Contains نیز وجود دارد.
 -- Inflectional forms

-- The next query does not return any rows
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'presentation');

-- The next query returns a row
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'FORMSOF(INFLECTIONAL, presentation)');
GO
در این مثال در کوئری اول به دنبال عین واژه‌ی وارد شده هستیم که با توجه به تنظیمات قسمت قبل و داده‌های موجود، خروجی را به همراه ندارد.
اکنون اگر کوئری دوم را که از FORMSOF جهت تعیین روش INFLECTIONAL استفاده کرده است، اجرا کنیم، به یک رکورد خواهیم رسید که در آن جمع واژه‌ی presentation وجود دارد.


8) جستجو برای یافتن متشابهات

برای نمونه اگر SQL Server 2012 بر روی سیستم شما نصب باشد، محل نصب واژه‌نامه‌های Synonyms یا واژه‌هایی همانند از لحاظ معنایی را در مسیر زیر می‌توانید مشاهده کنید:
 C:\...\MSSQL11.MSSQLSERVER\MSSQL\FTData
این‌ها یک سری فایل XML هستند با ساختار ذیل:
<XML ID="Microsoft Search Thesaurus">
    <thesaurus xmlns="x-schema:tsSchema.xml">
<diacritics_sensitive>0</diacritics_sensitive>
        <expansion>
            <sub>Internet Explorer</sub>
            <sub>IE</sub>
            <sub>IE5</sub>
        </expansion>
        <replacement>
            <pat>NT5</pat>
            <pat>W2K</pat>
            <sub>Windows 2000</sub>
        </replacement>
        <expansion>
            <sub>run</sub>
            <sub>jog</sub>
        </expansion>
        <expansion>
            <sub>need</sub>
            <sub>necessity</sub>
        </expansion>
    </thesaurus>
</XML>
در اینجا diacritics_sensitive به معنای حساسیت به لهجه است که به صورت پیش فرض برای تمام زبان‌ها خاموش است. سپس یک سری expansion و replacement را مشاهده می‌کنید.
فایل tsenu.xml به صورت پیش فرض برای زبان انگلیسی آمریکایی مورد استفاده قرار می‌گیرد. اگر محتویات آن‌را برای مثال با محتویات XML ایی فوق جایگزین کنید (در حین ذخیره باید دقت داشت که encoding فایل نیاز است Unicode باشد)، سپس باید SQL Server را از این تغییر نیز مطلع نمائیم:
 -- Load the US English file
EXEC sys.sp_fulltext_load_thesaurus_file 1033;
GO
 عدد 1033، عدد استاندارد زبان US EN است.
 البته اگر اینکار را انجام ندهیم، به صورت خودکار، اولین کوئری که از THESAURUS انگلیسی استفاده می‌کند، سبب بارگذاری آن خواهد شد.
 -- Synonyms

-- The next query does not return any rows
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'need');

-- The next query returns a row
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(docexcerpt, N'FORMSOF(THESAURUS, need)');
GO
در اولین مثال به دنبال عین واژه‌ی need در رکوردهای موجود هستیم که خروجی را بر نمی‌گرداند.
در ادامه اگر کوئری دوم را که از FORMSOF جهت تعیین روش THESAURUS استفاده کرده است، اجرا کنیم، به یک رکورد خواهیم رسید که در آن واژه‌ی necessity به کمک محتویات فایل tsenu.xml که پیشتر تهیه کردیم، بجای need وجود دارد.

9) جستجو بر روی خواص و متادیتای فایل‌ها
 -- Document properties
SELECT id, title, docexcerpt
FROM dbo.Documents
WHERE CONTAINS(PROPERTY(doccontent,N'Authors'), N'Test');
در اینجا نحوه‌ی جستجوی خواص فایل‌های docx ذخیره شده در قسمت قبل را مشاهده می‌کنید که شامل ذکر PROPERTY و ستون FTS مورد نظر است، به همراه نام خاصیت و عبارت جستجو.


کار با FREETEXT
 -- FREETEXT
SELECT *
FROM dbo.Documents
WHERE FREETEXT(docexcerpt, N'data presentation need');
FREETEXT عموما ردیف‌های بیشتری را نسبت به Contains بر می‌گرداند؛ چون جستجوی عمومی‌تری را انجام می‌دهد. در اینجا جستجو بر روی معنای عبارات انجام می‌شود و نه صرفا یافتن عباراتی دقیقا همانند عبارت در حال جستجو. در اینجا مباحث Synonyms و Inflectional ایی که پیشتر یاد شد، به صورت خودکار اعمال می‌شوند.
در کوئری فوق، کلیه رکوردهایی که با سه کلمه‌ی وارد شده (به صورت مجزا) به نحوی تطابق داشته باشند (تطابق کامل یا بر اساس تطابق‌های معنایی یا دستوری) باز گردانده خواهند شد. 
مطالب دوره‌ها
ساخت یک Mini ORM با AutoMapper
Mini ORM‌ها برخلاف ORMهای کاملی مانند Entity framework یا NHibernate، کوئری‌های LINQ را تبدیل به SQL نمی‌کنند. در اینجا کار با SQL نویسی مستقیم شروع می‌شود و مهم‌ترین کار این کتابخانه‌ها، نگاشت نتیجه‌ی دریافتی از بانک اطلاعاتی به اشیاء دات نتی هستند. خوب ... AutoMapper هم دقیقا همین کار را انجام می‌دهد! بنابراین در ادامه قصد داریم یک Mini ORM را به کمک AutoMapper طراحی کنیم.


کلاس پایه AdoMapper

public abstract class AdoMapper<T> where T : class
{
    private readonly SqlConnection _connection;
 
    protected AdoMapper(string connectionString)
    {
        _connection = new SqlConnection(connectionString);
    }
 
    protected virtual IEnumerable<T> ExecuteCommand(SqlCommand command)
    {
        command.Connection = _connection;
        command.CommandType = CommandType.StoredProcedure;
        _connection.Open();
 
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                return Mapper.Map<IDataReader, IEnumerable<T>>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
 
    protected virtual T GetRecord(SqlCommand command)
    {
        command.Connection = _connection;
        _connection.Open();
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                reader.Read();
                return Mapper.Map<IDataReader, T>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
 
    protected virtual IEnumerable<T> GetRecords(SqlCommand command)
    {
        command.Connection = _connection;
        _connection.Open();
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                return Mapper.Map<IDataReader, IEnumerable<T>>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
}
در اینجا کلاس پایه Mini ORM طراحی شده را ملاحظه می‌کنید. برای نمونه قسمت GetRecords آن مانند مباحث استاندارد ADO.NET است. فقط کار خواندن و همچنین نگاشت رکوردهای دریافت شده از بانک اطلاعاتی به شیء‌ایی از نوع T توسط AutoMapper انجام خواهد شد.


نحوه‌ی استفاده از کلاس پایه AdoMapper

در کدهای ذیل نحوه‌ی ارث بری از کلاس پایه AdoMapper و سپس استفاده از متدهای آن‌را ملاحظه می‌کنید:
public class UsersService : AdoMapper<User>, IUsersService
{
    public UsersService(string connectionString)
        : base(connectionString)
    {
    }
 
    public IEnumerable<User> GetAll()
    {
        using (var command = new SqlCommand("SELECT * FROM Users"))
        {
            return GetRecords(command);
        }
    }
 
    public User GetById(int id)
    {
        using (var command = new SqlCommand("SELECT * FROM Users WHERE Id = @id"))
        {
            command.Parameters.Add(new SqlParameter("id", id));
            return GetRecord(command);
        }
    }
}
در این مثال نحوه‌ی تعریف کوئری‌های پارامتری نیز در متد GetById به نحو متداولی مشخص شده‌است. کار نگاشت حاصل این کوئری‌ها به اشیاء دات نتی را AutoMapper انجام خواهد داد. نحوه‌ی کار نیز، نگاشت فیلد f1 به خاصیت f1 است (هم نام‌ها به هم نگاشت می‌شوند).


تعریف پروفایل مخصوص AutoMapper

ORMهای تمام عیار، کار نگاشت فیلدهای بانک اطلاعاتی را به خواص اشیاء دات نتی، به صورت خودکار انجام می‌دهند. در اینجا همانند روش‌های متداول کار با AutoMapper نیاز است این نگاشت را به صورت دستی یکبار تعریف کرد:
public class UsersProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<IDataRecord, User>();
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
و سپس در ابتدای برنامه آن‌را به AutoMapper معرفی نمود:
Mapper.Initialize(cfg => // In Application_Start()
{
    cfg.AddProfile<UsersProfile>();
});


سفارشی سازی نگاشت‌های AutoMapper

فرض کنید کلاس Advertisement زیر، معادل است با جدول Advertisements بانک اطلاعاتی؛ با این تفاوت که در کلاس تعریف شده، خاصیت TitleWithOtherName تطابقی با هیچکدام از فیلدهای بانک اطلاعاتی ندارد. بنابراین اطلاعاتی نیز به آن نگاشت نخواهد شد.
public class Advertisement
{
    public int Id { set; get; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int UserId { get; set; }
 
    public string TitleWithOtherName { get; set; }
}
برای رفع این مشکل می‌توان حین تعریف پروفایل مخصوص Advertisement، آن‌را سفارشی سازی نیز نمود:
public class AdvertisementsProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<IDataRecord, Advertisement>()
            .ForMember(dest => dest.TitleWithOtherName,
                       options => options.MapFrom(src =>
                            src.GetString(src.GetOrdinal("Title"))));
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
در اینجا پس از تعریف نگاشت مخصوص کار با IDataRecordها، عنوان شده‌است که هر زمانیکه به خاصیت TitleWithOtherName رسیدی، مقدارش را از فیلد Title دریافت و جایگزین کن.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب
کاهش حجم لاگ‌ فایل‌های اس‌کیوال سرور 2005 و 2008
نمی‌دونم تابحال به صورت جدی با SharePoint مایکروسافت کار کردید یا نه؟ این برنامه که عمده کارهای خودش رو با SQL server انجام میده در طول یک روز ممکن است تا 80 گیگ log file اس‌کیوال سرور تولید کند و بعد از چند روز اگر به همین صورت به حال خود رها شود (که عموما هم به همین صورت است!) ممکن است دیگر قادر به استفاده از سرور به دلیل پر شدن درایوی که لاگ فایل‌ها در آن ذخیره می‌شوند نباشید.
همچنین رشد tempdb نیز توسط این برنامه بسیار چشم‌گیر است. بنابراین همیشه به‌خاطر داشته باشید محل قرارگیری tempdb و همچنین محل قرارگیری لاگ فایل‌ها (که هر دو قابل تنظیم هستند) را در درایوهایی قرار دهید که حداقل 100 گیگ فضای خالی در آنها موجود باشد.
با استفاده از اسکریپت زیر می‌شود حجم لاگ فایل‌های اس کیوال سرور را به حداقل رساند و نفس راحتی کشید! این مساله اگر جدی گرفته نشود واقعا تبدیل به یک کابوس می‌شود!
اسکریپت زیر کلیه دیتابیس‌های موجود را یافته و shrink می‌کند. قسمت offline و online کردن آن هم به این خاطر است که ارتباط تمام کاربران متصل را به صورت آنی قطع می‌کند (یکی از چندین روش موجود برای kill کردن کاربران است). (یک stored procedure از آن درست کنید و با تعریف یک job جدید در اس کیوال سرور ، این stored procedure را برای مثال هر روز ساعت 3 بامداد به صورت خودکار اجرا کنید)
Declare @database nvarchar(1000)
Declare @tsql nvarchar(4000)
Declare DatabaseCursor Cursor
Local
Static
For
select name from master.dbo.sysdatabases
open DatabaseCursor
fetch next from DatabaseCursor into @database

while @@fetch_status = 0
begin
print 'database:' + @database
if @database not in ('tempdb','master','model','msdb')
begin
SET @tsql = 'use master;
alter database ['+@database+'] set offline with rollback immediate;
alter database ['+@database+'] set online;
DECLARE @dbLogName nvarchar(500) ;
Use ['+@database+'] ;
select @dbLogName = rtrim(ltrim(name)) from sysfiles WHERE FILEID=2;
ALTER DATABASE ['+@database+'] SET SINGLE_USER ;
DBCC SHRINKFILE(@dbLogName , 2) ;
BACKUP LOG ['+@database+'] WITH TRUNCATE_ONLY ;
DBCC SHRINKFILE(@dbLogName , 2) ;
ALTER DATABASE ['+@database+'] SET MULTI_USER ;'
exec(@tsql)
end
fetch next from DatabaseCursor into @database
end

close DatabaseCursor

deallocate DatabaseCursor
اسکریپت فوق با SQL Server 2005 سازگار است اما در SQL Server 2008 منسوخ شده است! (قسمت truncate کردن)
نسخه سازگار با SQL server 2008 آن به صورت زیر است:

Declare @database nvarchar(1000)
Declare @tsql nvarchar(4000)
Declare DatabaseCursor Cursor
Local
Static
For
select name from master.dbo.sysdatabases
open DatabaseCursor
fetch next from DatabaseCursor into @database

while @@fetch_status = 0
begin
print 'database:' + @database
if @database not in ('tempdb','master','model','msdb')
begin
SET @tsql = 'use master;
alter database ['+@database+'] set offline with rollback immediate;
alter database ['+@database+'] set online;
DECLARE @dbLogName nvarchar(500) ;
Use ['+@database+'] ;
select @dbLogName = rtrim(ltrim(name)) from sysfiles WHERE FILEID=2;
ALTER DATABASE ['+@database+'] SET RECOVERY SIMPLE;
ALTER DATABASE ['+@database+'] SET SINGLE_USER ;
DBCC SHRINKFILE(@dbLogName , 2) ;
ALTER DATABASE ['+@database+'] SET MULTI_USER ;
ALTER DATABASE ['+@database+'] SET RECOVERY FULL;'
exec(@tsql)
end
fetch next from DatabaseCursor into @database
end

close DatabaseCursor

deallocate DatabaseCursor
ماخذ اصلی مورد استفاده:
http://go.microsoft.com/fwlink/?LinkId=111531&clcid=0x409


مطالب
اعمال توابع تجمعی بر روی چند ستون در Entity framework
فرض کنید که می‌خواهیم معادل کوئری زیر را که اعمال توابع تجمعی به چند ستون است،
 SELECT sum([Rating_TotalRating]), sum([Rating_TotalRaters]), sum([Rating_AverageRating]) FROM [BlogPosts]
در Entity framwork به کمک LINQ to Entities تهیه کنیم.
نکته‌ای که در اینجا وجود دارد، نبود گروه بندی (حداقل به ظاهر) در کوئری نوشته شده است. اما واقعیت این است که یک بانک اطلاعاتی به صورت ضمنی در مورد یک چنین کوئری‌هایی نیز گروه بندی را انجام می‌دهد. برای اینکار، کل رکوردهای مدنظر را یک گروه تصور می‌کند.
اگر سعی کنیم چنین کوئری را توسط عبارات LINQ ایجاد کنیم، در سعی اول به چنین کوئری خواهیم رسید که اصلا کامپایل نمی‌شود:
                context.BlogPost.Select(r =>
                                        new
                                        {
                                            Sum1 = r.Sum(x => x.RatingTotalRating),
                                            Sum2 = r.Sum(x => x.RatingTotalRaters),
                                            Sum3 = r.Sum(x => x.RatingAverageRating)
                                        }).FirstOrDefault();
بنابراین به نظر می‌رسد که شاید بهتر باشد از روش ذیل استفاده کنیم:
 var sum1 = context.BlogPost.Sum(x => x.RatingTotalRating);
var sum2 = context.BlogPost.Sum(x => x.RatingTotalRaters);
var sum2 = context.BlogPost.Sum(x => x.RatingAverageRating);
این روش کار می‌کند و نهایتا معادل نتایج کوئری اول را نیز حاصل خواهد کرد؛ اما با سه بار رفت و برگشت به بانک اطلاعاتی که اصلا بهینه نیست.

راه حل: ایجاد گروه بندی ضمنی SQL به صورت صریح در عبارات LINQ

                context.BlogPost
                       .GroupBy(dummyNumber => 0)
                       .Select(r =>
                                        new
                                        {
                                            Sum1 = r.Sum(x => x.RatingTotalRating),
                                            Sum2 = r.Sum(x => x.RatingTotalRaters),
                                            Sum3 = r.Sum(x => x.RatingAverageRating)
                                        }).FirstOrDefault();
در این کوئری جدید که بر اساس عدد ثابت صفر گروه بندی شده است، یک چنین SQL ایی تولید می‌شود:
SELECT TOP (1) 
                        [Extent1].[K1] AS [K1], 
                        Sum([Extent1].[A1]) AS [A1], 
                        Sum([Extent1].[A2]) AS [A2],
                        Sum([Extent1].[A3]) AS [A3]
                        FROM ( SELECT 
                            0 AS [K1], 
                            [Extent1].[RatingTotalRating] AS [A1], 
                            [Extent1].[RatingTotalRaters] AS [A2],
       [Extent1].[RatingAverageRating] AS [A3]
                            FROM [dbo].[BlogPosts] AS [Extent1]
                        )  AS [Extent1]
                        GROUP BY [K1]
ابتدا یک ستون فرضی با مقدار ثابت صفر به رکوردها اضافه می‌شود. سپس بر اساس این ستون فرضی، کلیه ردیف‌ها گروه بندی شده و در ادامه توابع تجمعی بر روی آن‌ها اعمال می‌گردند. به این ترتیب تعداد رفت و برگشت‌ها به بانک اطلاعاتی به همان یک مورد کاهش خواهد یافت.
مطالب
تقسیم جدول در Entity Framework Code First
سناریو هایی هستند که در آن ها، تعداد ستون‌های یک جدول، بیش از اندازه زیاد می‌شوند و یا آن جدول حاوی فیلدهایی هست که منابع زیادی مصرف می‌کنند، به مانند فیلدهای متنی طولانی یا عکس. معمولا متوجه می‌شویم که در اکثر مواقع، به هنگام واکشی اطلاعات آن جدول، احتیاجی به داده‌های آن فیلد‌ها نداریم و با واکشی بی مورد آن ها، سربار اضافه ای به سیستم تحمیل می‌کنیم، چرا که این داده‌ها ، منابع حافظه ای ما را به هدر می‌دهند.
برای مثال، جدول Post مدل بلاگ را در نظر بگیرید که در آن دو فیلد Body و Image تعریف شده اند.فیلد Body از نوع nvarchar max و فیلد Image از نوع varbinary max است و بدیهی است که این دو داده، به هنگام واکشی حافظه‌ی زیادی مصرف می‌کنند.موارد بسیاری وجود دارند که ما به اطلاعات این دو فیلد احتیاجی نداریم از جمله: نمایش پست‌های پر بازدید، پسته هایی که اخیرا ارسال شده اند و اصولا ما فقط به چند فیلد جدول Post احتیاج داریم  و نه همه‌ی آن ها.
namespace SplittingTableSample.DomainClasses
{
    public class Post
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual DateTime CreatedDate { get; set; }
        public virtual string  Body { get; set; }
        public virtual byte[] Image { get; set; }
    }
}

دلیل اینکه در مدل فوق، تمامی خواص به صورت virtual تعریف شده اند، فعال سازی پروکسی‌های ردیابی تغییر است. اگر دستور زیر را برای واکشی اطلاعات post با id=1 انجام دهیم: 

            using (var context = new MyDbContext())
            {
                var post = context.Posts.Find(1);
            }
خروجی زیر را در SQL Server Profiler مشاهده خواهید کرد:

exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate], 
[Extent1].[Body] AS [Body], 
[Extent1].[Image] AS [Image]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1

همان طور که مشاهده می‌کنید، با اجرای دستور فوق تمامی فیلد‌های جدول Posts که id آن‌ها برابر 1 بود واکشی شدند، ولی من تنها به فیلدهای Id و Title آن احتیاج داشتم. خب شاید بگویید که من به سادگی با projection، این مشکل را حل می‌کنم و تنها از فیلد هایی که به آن‌ها احتیاج دارم، کوئری می‌گیرم. همه‌ی این‌ها درست، اما projection هم مشکلات خود را دارد،به صورت پیش فرض، نوع بدون نام بر می‌گرداند و اگر بخواهیم این گونه نباشد، باید مقادیر آن را به یک کلاس(مثلا viewmodel) نگاشت کنیم و کلی مشکل دیگر.
راه حل دیگری که برای حل این مشکل ارائه می‌شود و برای نرمال سازی جداول نیز کاربرد دارد این است که، جدول Posts را به دو جدول مجزا که با یکدیگر رابطه‌ی یک به یک دارند تقسیم کنیم، فیلد‌های پر مصرف را در یک جدول و فیلدهای حجیم و کم مصرف را در جدول دیگری تعریف کنیم و سپس یک رابطه‌ی یک به یک بین آن دو برقرار می‌کنیم.
به  طور مثال این کار را بر روی جدول Posts ، به شکل زیر انجام شده است:
 
namespace SplittingTableSample.DomainClasses
{
    public class Post
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual DateTime CreatedDate { get; set; }
        public virtual PostMetaData PostMetaData { get; set; }
    }
}
namespace SplittingTableSample.DomainClasses
{
    public class PostMetaData
    {
        public virtual int PostId { get; set; }
        public virtual string Body { get; set; }
        public virtual byte[] Image { get; set; }
        public virtual Post Post { get; set; }
    }
}
همان طور که می‌بینید، خواص حجیم به جدول دیگری به نام PostMetaData منتقل شده و با تعریف خواص راهبری ارجاعی در هر دو کلاس،رابطه‌ی یک به یک بین آن‌ها برقرار شده است.جز الزامات تعریف روابط یک به یک این است که، با استفاده از Fluent API یا Data Annotations ، طرف‌های Depenedent و Principal، صریحا به EF معرفی شوند.

namespace SplittingTableSample.DomainClasses
{
    public class PostMetaDataConfig : EntityTypeConfiguration<PostMetaData>
    {
        public PostMetaDataConfig()
        {
            HasKey(x => x.PostId);
            HasRequired(x => x.Post).WithRequiredDependent(x => x.PostMetaData);
        }
    }
}
اولین نکته ای که باید به آن توجه شود، این است که در کلاس PostMetaData، قوانین پیش فرض EF برای تعیین کلید اصلی نقض شده است و به همین دلیل، صراحتا با استفاده از متد HasKey ، کلید اصلی به EF معرفی شده است. نکته‌ی مهم دیگری که به آن باید توجه شود این است که هر دو سر رابطه به صورت Required تعریف شده است. دلیل این موضوع هم با توجه به مطلبی که قرار است گفته شود،کمی جلوتر خواهید فهمید. حال اگر تعاریف DbSet‌ها را نیز اصلاح کنیم و دستور زیر را اجرا کنیم:

var post = context.Posts.Find(1);
خروجی sql زیر را مشاهده خواهید کرد:

exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1
خیلی خوب! دیگر خبری از  فیلدهای اضافی Body و Image نیست. دلیل اینکه در اینجا join  بین دو جدول مشاهده نمی‌شود، قابلیت lazy loading است، که با virtual تعریف کردن خواص راهبری حاصل شده است. پس lazy loading در اینجا واقعا مفید است.
اما راه حل ذکر شده نیز کاملا بدون ایراد نیست. مشکل اساسی آن تعدد تعداد جداول آن است. آیا جدول Post ، واقعا احتیاج به چنین سطح نرمال سازی و تبدیل آن به دو جدول مجزا را داشت؟ مطمئنا خیر. آیا واقعا راه حلی وجود دارد که ما در سمت کد‌های خود با دو موجودیت مجزا کار کنیم، در صورتی که در دیتابیس این دو موجودیت، ساختار یک جدول را تشکیل دهند. در اینجا روشی مطرح می‌شود به نام تقسیم جدول (Table Splitting).
برای انجام این کار فقط چند تنظیم ساده لازم است:
1) فیلد‌های موجودیت مورد نظر را به موجودیت‌های کوچکتر، نگاشت می‌کنیم.
2) بین موجودیت‌های کوچک تر، رابطه‌ی یک به یک که هر دو سر رابطه Required هستند، رابطه برقرار می‌کنم.
3) با استفاده از Fluent API یا DataAnnotations، تمامی موجودیت‌ها را به یک نام در دیتابیس نگاشت می‌کنیم.
برای مثال، تنظیمات Fluent برای کلاس Post و PostMetaData که رابطه‌ی بین آن‌ها یک به یک است را مشاهده می‌کنید:
 
namespace SplittingTableSample.DomainClasses
{
    public class PostConfig : EntityTypeConfiguration<Post>
    {
        public PostConfig()
        {
            ToTable("Posts");
        }
    }
}
namespace SplittingTableSample.DomainClasses
{
    public class PostMetaDataConfig : EntityTypeConfiguration<PostMetaData>
    {
        public PostMetaDataConfig()
        {
            ToTable("Posts");
            HasKey(x => x.PostId);
            HasRequired(x => x.Post).WithRequiredDependent(x => x.PostMetaData);
        }
    }
}
نکته مهم این است که در هر دو کلاس(حتی کلاس Post) باید با استفاده از متد ToTable، کلاس‌ها را به یک نام در دیتابیس نگاشت کنیم. در نتیجه با استفاده از متد ToTable در هر دو موجودیت، آنها در دیتابیس به جدولی به نام  Posts نگاشت خواهند شد. تصویر زیر پس از اجرای برنامه، بیان گر این موضوع خواهد بود.

اگر دستورات زیر را اجرا کنید:


var post = context.Posts.Find(1);
Console.WriteLine(post.PostMetaData.Body);
خروجی زیر را در  SQL Server Profiler مشاهده خواهید کرد:
برای متد Find خروجی زیر:
 
exec sp_executesql N'SELECT TOP (2) 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @p0',N'@p0 int',@p0=1
و برای post.PostMetaData.Body دستور sql زیر را مشاهده می‌کنید:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Body] AS [Body], 
[Extent1].[Image] AS [Image]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
دلیل این که در اینجا ،دو دستور sql به دیتابیس ارسال شده است، فعال بودن ویژگی lazy loading ،به دلیل virtual  تعریف کردن خواص راهبری موجودیت‌ها است.
حال اگر بخواهیم با یک رفت و آمد به دیتابیس کلیه اطلاعات را واکشی کنیم، می‌توانیم از Eager Loading استفاده کنیم:
 
var post = context.Posts.Include(x => x.PostMetaData).SingleOrDefault(x => x.Id == 1);
که خروجی sql آن نیز به شکل زیر است:

SELECT 
[Limit1].[Id] AS [Id], 
[Limit1].[Title] AS [Title], 
[Limit1].[CreatedDate] AS [CreatedDate], 
[Extent2].[Id] AS [Id1], 
[Extent2].[Body] AS [Body], 
[Extent2].[Image] AS [Image]
FROM   (SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], [Extent1].[CreatedDate] AS [CreatedDate]
FROM [dbo].[Posts] AS [Extent1]
WHERE 1 = [Extent1].[Id] ) AS [Limit1]
LEFT OUTER JOIN [dbo].[Posts] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Id]
در نتیجه با کمک این تکنیک توانستیم، با چند موجودیت، در قالب یک جدول رفتار کنیم و از مزیت‌های آن همچون lazy loading، نیز بهره مند شویم.

دریافت کد‌های این بخش: SplittingTable-Sample.rar 
مطالب دوره‌ها
استفاده از XQuery - قسمت اول
XQuery زبانی است که در ترکیب با T-SQL، جهت کار با نوع داده‌ای XML در SQL Server مورد استفاده قرار می‌گیرد. XQuery یک زبان declarative است. عموما زبان‌های برنامه نویسی یا declarative هست و یا imperative. در زبان‌های imperative مانند سی‌شارپ، در هر بار، یک سطر به پردازشگر برای توضیح اعمالی که باید انجام شوند، معرفی خواهد شد. در زبان‌های declarative، توسط زبانی سطح بالا، به پردازشگر عنوان می‌کنیم که قرار است جواب چه چیزی باشد. در این حالت پردازشگر سعی می‌کند تا بهینه‌ترین روش را برای یافتن پاسخ بیابد. SQL و XQuery، هر دو جزو زبان‌های declarative هستند.
XQuery پیاده سازی شده در SQL Server با استانداردهای XQuery 1.0 و XPath 2.0 سازگار است. XQuery برای کار با نودهای مختلف یک سند XML، از XPath استفاده می‌کند. همچنین باید دقت داشت که این زبان به بزرگی و کوچکی حروف حساس است. در آن تمام واژه‌های کلیدی lowercase هستند و تمام متغیرها با علامت $ شروع می‌شوند.


ورودی و خروجی در XQuery

استاندارد XQuery از یک سری توابع ورودی مانند doc برای کار با یک سند و collection برای پردازش چندین سند کمک می‌گیرد. SQL Server از هیچکدام از این توابع پشتیبانی نمی‌کند. در اینجا از XQuery، به کمک متدهای نوع داده‌ای XML استفاده خواهد شد. این متدها شامل موارد ذیل هستند:
- query : یک xml را به عنوان ورودی گرفته و نهایتا یک خروجی XML دیگر را بر می‌گرداند.
- exist : خروجی bit دارد؛ true یا false.
- value : یک خروجی SQL Type را ارائه می‌دهد.
- nodes : خروجی جدولی دارد.
- modify : برای تغییر اطلاعات بکار می‌رود.

این موارد را در طی مثال‌هایی بررسی خواهیم کرد. بنابراین در ادامه نیاز است یک سند XML را که در طی مثال‌های این قسمت مورد استفاده قرار خواهد گرفت، به شرح ذیل مدنظر داشته باشیم:
DECLARE @data XML 

SET @data = 
'<people>
 <person>
  <name>
<givenName>name1</givenName>
<familyName>lname1</familyName>
  </name>
  <age>33</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name2</givenName>
<familyName>lname2</familyName>
  </name>
  <age>40</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name3</givenName>
<familyName>lname3</familyName>
  </name>
  <age>30</age>
  <height>medium</height>
 </person>
</people>'
در اینجا people در ریشه سند قرار گرفته و سپس سه شخص به مجموعه نودهای آن اضافه شده‌اند.
همانطور که در قسمت قبل نیز ذکر شد، اگر اطلاعات شما در یک فایل XML قرار دارند، نحوه‌ی خواندن آن به شکل یک فیلد XML با کمک openrowset مطابق دستورات زیر خواهد بود:
 declare @data xml
set @data = (select * from openrowset(bulk 'c:\path\data.xml', single_blob) as x)


بررسی متد query

متد query یک XQuery متنی را دریافت کرده، آن‌را بر روی XML ورودی اجرا نموده و سپس یک خروجی XML دیگر را ارائه خواهد داد.
اگر به کتاب‌های استاندارد XQuery مراجعه کنید، به یک چنین کوئری‌هایی خواهید رسید:
  for $p in doc("data.xml")/people/person
 where $p/age > 30
 return $p/name/givenName/text()
همانطور که عنوان شد، متد doc در SQL Server پیاده سازی نشده‌است. بجای آن حداقل از دو روشی که برای مقدار دهی متغیر data عنوان شد، می‌توان استفاده کرد. پس از آن معادل کوئری فوق در SQL Server به نحو ذیل توسط متد query نوشته می‌شود:
 SELECT @data.query('
 for $p in /people/person
 where $p/age > 30
 return $p/name/givenName/text()
 ')
این کوئری givenName تمام اشخاص بالای 30 سال را از سند XML مطرح شده در ابتدای بحث، استخراج می‌کند. خروجی آن نیز یک XML  است و اگر آن‌را در SQL Server managment studio اجرا کنید، یک خط آبی زیر نتیجه‌ی آن کشیده می‌شود که بیانگر لینکی است، به محتوای XML حاصل.



بررسی متد value

در ادامه متد value را بررسی خواهیم کرد. در اینجا قصد داریم مقدار سن اولین شخص را نمایش دهیم:
 SELECT @data.value('/people/person/age', 'int')
پارامتر اول متد value یک XQuery است و پارامتر دوم آن، نوع داده‌ای که قرار است بازگشت داده شود. در اینجا اگر اطلاعاتی یافت نشود، نال بازگشت داده خواهد شد.
اگر کوئری فوق را اجرا کنیم با خطای ذیل مواجه خواهیم شد:
 XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
در اینجا چون از XML Schema استفاده نشده، به untyped Atomic اشاره شده‌است و * پس از آن به zero to many اشاره دارد که برخلاف خروجی zero to one متد value است. این متد، صفر یا حداکثر یک مقدار را باید بازگشت دهد.
برای رفع این مشکل و اشاره به اولین شخص، می‌توان از روش ذیل استفاده کرد:
 SELECT @data.value('(/people/person/age)[1]', 'int')



تولید schema برای سند XML بحث جاری

با استفاده از برنامه Infer.exe مایکروسافت به سادگی می‌توان برای یک سند XML، فایل Schema ایجاد کرد. این برنامه را از اینجا می‌توانید دریافت کنید. پس از آن، اگر فرض کنیم اطلاعات سند XML مثال فوق در فایلی به نام people.xml ذخیره شده‌است، می‌توان schema آن‌را توسط دستور ذیل تولید کرد:
 Infer.exe people.xml -o schema.xsd
people.xml و people.xsd

که نهایتا چنین شکلی را خواهد داشت:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="people">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="person">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="name">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="givenName" type="xs:string" />
                    <xs:element name="familyName" type="xs:string" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
              <xs:element name="age" type="xs:unsignedByte" />
              <xs:element name="height" type="xs:string" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
البته این فایل تولید شده به صورت خودکار، نوع age را unsignedByte تشخیص داده است که در صورت نیاز می‌توان آن‌را به int تبدیل کرد. ولی در کل خروجی آن بسیار با کیفیت و نزدیک به واقعیت است.
این خروجی را که اکنون به صورت یک فایل xsd، در کنار فایل xml معرفی شده به آن می‌توان یافت، با استفاده از openrowset قابل بارگذاری است:
 declare @schema xml
set @schema = (select * from openrowset(bulk 'c:\path\schema_1.xsd', single_blob) as x)
و یا حتی می‌توان یک متغیر از نوع XML را تعریف و سپس محتوای آن را به صورت رشته‌ای در همانجا مقدار دهی کرد.
سپس از این متغیر برای تعریف یک اسکیما کالکشن جدید استفاده خواهیم کرد:
 CREATE XML SCHEMA COLLECTION poeple_xsd AS @schema
در ادامه می‌توان متغیر data را که جهت مقدار دهی سند XML در ابتدای بحث تعریف کردیم، به صورت strongly typed تعریف کنیم:
 DECLARE @data XML(poeple_xsd)
SET @data = 'مانند قبل با همان محتوایی که در ابتدای بحث عنوان شد'
اینبار اگر کوئری ذیل را برای یافتن سن اولین شخص اجرا کنیم:
 SELECT @data.value('/people/person[1]/age', 'int')
خطای واضح‌تری را دریافت خواهیم کرد:
 XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xs:unsignedByte *'
در اینجا xs:unsignedByte بجای xdt:untypedAtomic پیشین گزارش شده‌است.
مشکل کوئری نوشته در اینجا این است که زمانیکه نوع XML تعریف می‌شود، پیش فرض آن content است. یعنی در این حالت چندین root elemnt مجاز هستند. بنابراین person 1 درخواستی، می‌تواند چندین خروجی داشته باشد که در متد value مجاز نیست. این متد، پیش از اجرای کوئری، توسط parser تعیین اعتبار می‌شود و الزاما نیازی نیست تا حتما اجرا شده و سپس مشخص شود که چندین خروجی حاصل آن است.
 اینبار تنها کاری که باید برای رفع مشکل گزارش شده انجام شود، تغییر content پیش فرض به document است:
 DECLARE @data XML(document poeple_xsd)
تغییر دیگری نیاز نیست. حتی نیاز نیست از پرانتزها برای مشخص کردن اولین age استفاده کنیم. چون به کمک schema دقیقا مشخص شده‌است که این سند، چه ساختاری دارد و همانند مثال ابتدای بحث، دیگر یک untyped xml نیست.



sequences در XQuery

Sequences بسیار شبیه به آرایه‌ای از آیتم‌ها هستند و منظور مجموعه‌ای از نودها یا مقادیر آن‌ها است. برای مثال به ورودی کوئری‌های XQuery به شکل توالی از یک سند و به خروجی آن‌ها همانند توالی صفر تا چند نود نگاه کنید.
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
1,2
(: 1,2 :)
')
در مثال فوق یک توالی اصطلاحا دو atomic value را ایجاد کرده‌ایم. این آیتم‌ها با کاما از یکدیگر جدا می‌شوند. همچنین x، پیش از بکارگیری مقدار دهی شده‌است تا null نباشد. عبارتی که بین (: :) قرار می‌گیرد، یک کامنت تفسیر خواهد شد.

همچنین باید دقت داشت که این توالی خطی تفسیر می‌شود.
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
for $x in (1,2,3)
for $y in (4,5)
return ($x,$y)
')
در اینجا یک جوین کارتزین نوشته شده است، که در آن یک x با یک y جوین خواهد شد. شاید تصور کنید که خروجی آن مجموعه‌ای است با سه عضو که هر عضو آن با دو عضو دیگر جوین می‌شود. اما اگر کوئری فوق را اجرا کنید، یک خروجی خطی را مشاهده خواهید کرد.

به علاوه در SQL Server امکان تعریف Heterogeneous sequences وجود ندارد؛ به عبارتی توالی بین مقادیر و نودها مجاز نیست. برای مثال اگر کوئری زیر را اجرا کنید:
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
1, <node/>
')
با خطای ذیل مواجه خواهید شد:
 XQuery [query()]: Heterogeneous sequences are not allowed: found 'xs:integer' and 'element(node,xdt:untyped)'