مطالب
بررسی ابزار SQL Server Profiler

مقدمه

Profiler یک ابزار گرافیکی برای ردیابی و نظارت بر کارآئی SQL Server است. امکان ردیابی اطلاعاتی در خصوص رویدادهای مختلف و ثبت این داده‌ها در یک فایل (با پسوند trc) یا جدول برای تحلیل‌های آتی نیز وجود دارد. برای اجرای این ابزار مراحل زیر را انجام دهید:

Start > Programs> Microsoft SQL Server > Performance Tools> SQL Server Profiler
و یا در محیط  Management Studio از منوی Tools گزینه SQL Server Profiler را انتخاب نمائید.


1- اصطلاحات

1-1- رویداد (Event):

یک رویداد، کاری است که توسط موتور بانک اطلاعاتی (Database Engine) انجام می‌شود. برای مثال هر یک از موارد زیر یک رویداد هستند.
-  متصل شدن کاربران (login connections) قطع شدن ارتباط یک login
-  اجرای دستورات T-SQL، شروع و پایان اجرای یک رویه، شروع و پایان یک دستور در طول اجرای یک رویه، اجرای رویه‌های دور Remote Procedure Call
-  باز شدن یک Cursor
-  بررسی و کنترل مجوزهای امنیتی

1-2- کلاس رویداد (Event Class):

برای بکارگیری رویدادها در Profiler، از یک Event Class استفاده می‌کنیم. یک Event Class رویدادی است که قابلیت ردیابی دارد. برای مثال بررسی ورود و اتصال کاربران با استفاده از کلاس Audit Login قابل پیاده سازی است. هر یک از موارد زیر یک Event Class هستند.
-  SQL:BatchCompleted
-  Audit Login
-  Audit Logout
-  Lock: Acquired
-  Lock: Released

1-3- گروه رویداد (Event Category):

یک گروه رویداد شامل رویدادهایی است که به صورت مفهومی دسته بندی شده اند. برای مثال، کلیه رویدادهای مربوط به قفل‌ها از جمله Lock: Acquired (بدست آوردن قفل) و Lock: Released (رها کردن قفل) در گروه رویداد Locks قرار  دارند.

1-4- ستون داده ای (Data Column):

یک ستون داده ای، خصوصیت و جزئیات یک رویداد را شامل می‌شود. برای مثال در یک Trace که رویدادهای Lock: Acquired را نظارت می‌کند، ستون Binary Data شامل شناسه (ID) یک صفحه و یا یک سطر قفل شده است و یا اینکه ستون Duration مدت زمان اجرای یک رویه را نمایش می‌دهد.

1-5- الگو (Template):

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

1-6- ردیاب (Trace):

یک Trace داده‌ها را براساس رویدادهای انتخاب شده، جمع آوری می‌کند. امکان اجرای بلافاصله یک Trace برای جمع آوری اطلاعات با توجه به رویدادهای انتخاب شده و ذخیره کردن آن برای اجرای آتی وجود دارد.

1-7- فیلتر (Filter):

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


2- انتخاب الگو (Profiler Trace Templates)

از آنجائیکه اصولاً انتخاب Eventهای مناسب، کار سخت و تخصصی می‌باشد برای راحتی کار تعدادی Template‌های آماده وجود دارد، برای مثال TSQL_Duration تاکیدش روی مدت انجام کار است و یا SP_Counts در مواردی که بخواهیم رویه‌های ذخیره شده را بهینه کنیم استفاده می‌شود در جدول زیر به شرح هر یک پرداخته شده است:
 الگو  هدف 
 Blank   ایجاد یک Trace کلی 
 SP_Counts   ثبت اجرای هر رویه ذخیره شده برای تشخیص اینکه هر رویه چند بار اجرا شده است 
 Standard   ثبت آمارهای کارائی برای هر رویه ذخیره شده و Query‌های عادی SQL که اجرا می‌شوند و عملیات ورود و خروج هر Login (پیش فرض) 
 TSQL   ثبت یک لیست از همه رویه‌های ذخیره شده و Query‌های عادی SQL که اجرا می‌شوند ولی آمارهای کارائی را شامل نمی‌شود 
 TSQL_Duration   ثبت مدت زمان اجرای هر رویه ذخیره شده و هر Query عادی SQL 
 TSQL_Grouped   ثبت تمام  login‌ها و logout‌ها در طول اجرای رویه‌های ذخیره شده و هر Query عادی SQL، شامل اطلاعاتی برای شناسائی برنامه و کاربری که درخواست را اجرا می‌کند 
 TSQL_Locks   ثبت اطلاعات انسداد (blocking) و بن بست (deadlock) از قبیل blocked processes، deadlock chains، deadlock graphs,... . این الگو همچنین درخواست‌های تمام رویه‌های ذخیره شده و تمامی دستورات هر رویه و  هر Query عادی SQL را دریافت می‌کند 
 TSQL_Replay   ثبت اجرای رویه‌های ذخیره شده و Query‌های SQL در یک SQL Instance و  مهیا کردن امکان اجرای دوباره عملیات در سیستمی دیگر 
 TSQL_SPs   ثبت کارائی برای Query‌های SQL، رویه‌های ذخیره شده و تمامی دستورات درون یک رویه ذخیره شده و نیز عملیات ورود و خروج هر Login 
 Tuning   ثبت اطلاعات کارائی برای Query‌های عادی SQL و رویه‌های ذخیره شده و یا تمامی دستورات درون یک رویه ذخیره شده 

3- انتخاب رویداد (SQL Trace Event Groups)

رویداد‌ها در 21 گروه رویداد دسته بندی می‌شوند که در جدول زیر لیست شده اند:
 گروه رویداد  هدف 
 Broker  13 رویداد برای واسطه سرویس (Service Broker) 
 CLR   1 رویداد برای بارگذاری اسمبلی‌های CLR (Common Language Runtime) 
 Cursors   7 رویداد برای ایجاد، دستیابی و در اختیار گرفتن Cursor 
 Database   6 رویداد برای رشد/کاهش  (grow/shrink) فایل های  Data/Log همچنین تغییرات حالت انعکاس (Mirroring) 
 Deprecation   2 رویداد برای آگاه کردن وضعیت نابسامان درون یک SQL Instance 
 Errors and
Warnings 
 16 رویداد برای خطاها، هشدارها و پیغام‌های اطلاعاتی که ثبت شده است 
 Full Text   3  رویداد برای پیگیری یک شاخص متنی کامل 
 Locks   9 رویداد برای بدست آوردن، رها کردن قفل و بن بست (Deadlock) 
 OLEDB   5 رویداد برای درخواست‌های توزیع شده و RPC (اجرای رویه‌های دور) 
 Objects   3 رویداد برای وقتی که یک شی ایجاد، تغییر یا حذف می‌شود 
 Performance   14 رویداد برای ثبت نقشه درخواست‌ها (Query Plan) برای استفاده نقشه راهنما (Plan Guide) به منظور بهینه سازی کارائی درخواست ها،  همچنین این گروه رویداد در خواست‌های متنی کامل (full text) را ثبت می‌کند 
 Progress Report   10 رویداد برای ایجاد Online Index 
 Query
Notifications 
 4 رویداد برای سرویس اطلاع رسان (Notification Service) 
 Scans   2 رویداد برای وقتی که یک جدول یا شاخص، پویش می‌شود 
 Security Audit   44 رویداد برای وقتی که مجوزی استفاده شود، جابجائی هویتی رخ دهد، تنظیمات امنیتی اشیائی تغییر کند،یک  SQL Instance  شروع و متوقف شود و یک  Database جایگزین شود یا از آن پشتیبان گرفته شود 
 Server  3 رویداد برای Mount Tape، تغییر کردن حافظه سرور و بستن یک فایل Trace 
 Sessions   3 رویداد برای وقتی که Connection‌ها موجود هستند و یک Trace فعال می‌شود، همچنین یک Trigger  و یک تابع دسته بندی(classification functions) مربوط به مدیریت منابع(resource governor) رخ دهد 
 Stored Procedures   12 رویداد برای اجرای یک رویه ذخیره شده و دستورات درون آن ، کامپایل مجدد و استفاده از حافظه نهانی (Cache) 
 Transactions   13 رویداد برای شروع، ذخیره ، تائید و لغو یک تراکنش 
 TSQL   9  رویداد برای اجرای Query‌های SQL و جستجوهای XQUERY (در داده‌های XML)  
 User Configurable   10 رویداد که شما می‌توانید پیکربندی کنید 
به طور معمول بیشتر از گروه رویدادهای Locks، Performance، Security Audit، Stored Procedures و TSQL استفاده می‌شود.


4- انتخاب ستون‌های داده ای ( Data Columns)

اگرچه می‌توان همه‌ی 64 ستون داده ای ممکن را برای ردیابی انتخاب کرد ولیکن داده‌های Trace شما زمانی مفید خواهند بود که اطلاعات ضروری را ثبت کرده باشید. برای مثال شماره ترتیب تراکنش‌ها را،  برای یک رویداد RPC:Completed می‌توانید برگردانید، اما همه رویه‌های ذخیره شده مقادیر را تغییر نمی‌دهند بنابراین شماره ترتیب تراکنش‌ها فضای بیهوده ای را مصرف می‌کند. بعلاوه همه ستون‌های داده ای برای تمامی رویداد‌های Trace معتبر نیستند. برای مثال Read ، Write ،CPU و Duration برای رویداد‌های RPC:Starting و SQL:BatchStarting معتبر نیستند.
ApplicationName، NTUserName، LoginName، ClientProcessID، SPID، HostName، LoginSID، NTDomainName و SessionLoginName ، مشخص می‌کنند چه کسی و از چه منشاء دستور را اجرا کرده است.
ستون SessionLoginName معمولاً نام Login ای که از آن برای متصل شدن به SQL Instance استفاده شده است را نشان می‌دهد. در حالیکه ستون LoginName نام کاربری را که دستور را اجرا می‌کند نشان می‌دهد (EXECUTE AS). ستون ApplicationName خالی است مگر اینکه در ConnectionString برنامه کاربردیمان این خصوصیت (Property) مقداردهی شده باشد. ستون StartTime و EndTime زمان سرحدی برای هر رویداد را ثبت می‌کند این ستون‌ها بویژه در هنگامی که به عملیات Correlate  نیاز دارید مفید هستند.


5- بررسی چند سناریو نمونه

•  یافتن درخواست هائی (Queries) که بدترین کارایی را دارا هستند.

برای ردیابی درخواست‌های ناکارا، از رویداد RPC:Completed از دسته Stored Procedure و رویداد SQL:BatchCompleted از دسته TSQL استفاده می‌شود.

•  نظارت بر کارایی رویه ها

برای ردیابی کارائی رویه ها، از رویدادهای SP:Starting، SP:Completed، SP:StmtCompleted و SP:StmtStaring از کلاس Stored Procedure و رویدادهای SQL:BatchStarting ، SQL:BatchCompleted از کلاس TSQL استفاده می‌شود.

•  نظارت بر اجرای دستورات T-SQL توسط هر کاربر

برای ردیابی دستوراتی که توسط یک کاربر خاص اجرا می‌شود، نیاز به ایجاد یک Trace برای نظارت بر رویدادهای کلاس‌های Sessions، ExistingConnection و TSQL داریم همچنین لازم است نام کاربر در قسمت فیلتر  و با استفاده از DBUserName مشخص شود.

•  اجرا دوباره ردیاب (Trace Replay)

این الگو  معمولاً برای debugging استفاده می‌شود برای این منظور  از الگوی Replay استفاده می‌شود. در ضمن امکان اجرای دوباره عملیات در سیستمی دیگر با استفاده از این الگو مهیا می‌شود.

•  ابزار Tuning Advisor (راهنمای تنظیم کارائی)

این ابزاری برای تحلیل کارائی یک یا چند بانک اطلاعاتی و تاثیر عملکرد آنها بر بار کاری (Workload) سرویس دهنده است. یک بار کاری مجموعه ای از دستورات T-SQL است که روی بانک اطلاعاتی اجرا می‌شود. بعد از تحلیل تاثیر بارکاری بر بانک اطلاعاتی، Tuning Advisor توصیه هائی برای اضافه کردن، حذف و یا تغییر طراحی فیزیکی ساختار بانک اطلاعاتی ارائه می‌دهد این تغییرات ساختاری شامل پیشنهاد برای تغییر ساختاری موارد Clustered Indexes، Nonclustered Indexes، Indexed View و Partitioning است.
برای ایجاد بارکاری می‌توان از یک ردیاب تهیه شده در SQL Profiler استفاده کرد برای این منظور از الگوی Tuning استفاده می‌شود و یا رویدادهای RPC:Completed، SQL:BatchCompleted و SP:StmtCompleted را ردیابی نمائید.

•  ترکیب ابزارهای نظارتی (Correlating Performance and Monitoring Data)

یک Trace برای ثبت اطلاعاتی که در یک SQL Instance رخ می‌دهد، استفاده می‌شود. System Monitor  برای ثبت شمارنده‌های کارائی(performance counters) استفاده می‌شود و همچنین از منابع سخت افزاری و اجزای دیگر که روی سرور اجرا می‌شوند، تصاویری فراهم می‌کند. توجه شود که در مورد  Correlating یک فایل ردیاب (trace file) و یک Counter Log (ابزار Performance )، ستون داده ای StartTime و EndTime باید انتخاب شود، برای این کار از منوی File گزینه Import Performance Data انتخاب می‌شود.

•  جستجوی علت رخ دادن یک بن بست

برای ردیابی علت رخ دادن یک بن بست، از رویدادهای RPC:Starting، SQLBatchStarting از دسته Stored Procedure و رویدادهای Deadlock graph، Lock:Deadlock و Lock:Deadlock Chain از دسته Locks استفاده می‌شود. ( در صورتی که نیاز به یک ارائه گرافیکی دارید از  Deadlock graph استفاده نمائید، خروجی مطابق تصویر زیر می‌شود).


5-1- ایجاد یک Trace

1-  Profiler را اجرا کنید از منوی File گزینه New Trace را انتخاب کنید و به SQL Instance مورد نظرتان متصل شوید.
2-  مطابق تصویر زیر برای Trace یک نام و الگو و تنظیمات ذخیره سازی فایل را مشخص کنید.


3-  بر روی قسمت Events Selection کلیک نمائید.
4-  مطابق تصویر زیر رویداد‌ها و کلاس رویداد‌ها را انتخاب کنید، ستون‌های TextData، NTUserName، LoginName، CPU،Reads،Writes، Duration، SPID، StartTime، EndTime، BinaryData، DataBaseName، ServerName و ObjectName را انتخاب کنید.

5-  روی Column Filters کلیک کنید و مطابق تصویر زیر برای DatabaseName فیلتری تنظیم کنید.


6-  روی Run کلیک کنید. تعدادی Query و رویه ذخیره شده مرتبط با پایگاه داده AdventureWorks اجرا کنید .


5-2- ایجاد یک Counter Log

برای ایجاد یک Counter Log  مراحل زیر  را انجام دهید:
1-  ابزار Performance را اجرا کنید (برای این کار عبارتPerfMon را در قسمت Run بنویسید).
2-  در قسمت Counter Logs یک log ایجاد کنید.
3-  روی Add Counters کلیک کرده و مطابق تصویر موارد زیر را انتخاب کنید.
Select counters from list 
Performance Object 
 Output Queue Length  Network Interface 
 % Processor Time  Processor 
 Processor Queue Length  System 
 Buffer Manager:Page life expectancy  SQLServer 
 
4-  روی Ok کلیک کنید تا Counter Log ذخیره شود سپس روی آن راست کلیک کرده و آنرا Start کنید.


5-3- ترکیب ابزارهای نظارتی (Correlating SQL Trace and System Monitor Data)

1-  Profiler را اجرا کنید از منوی File گزینه Open و سپس Trace File را انتخاب کنید فایل trc را که در گام اول ایجاد کردید، باز نمائید.
2-  از منوی File گزینه Import Performance Data را انتخاب کنید و فایل counter log  را که در مرحله قبل ایجاد کردید  انتخاب کنید.



نکته: اطلاعات فایل trc را می‌توان درون یک جدول وارد کرد، بدین ترتیب می‌توان آنالیز بیشتری داشت به عنوان مثال دستورات زیر این عمل را انجام می‌دهند.


 SELECT * INTO dbo.BaselineTrace
FROM fn_trace_gettable(' c:\performance baseline.trc ', default);
با اجرای دستور زیر جدولی با نام  BaselineTrace ایجاد و محتویات Trace مان (performance baseline.trc) در آن درج می‌گردد.
 
مطالب
روش‌های مختلف انجام چند کار به صورت همزمان در C# .NET - قسمت اول
آیا تا به حال لیستی از دیتا داشته‌اید که بخواهید بر روی آنها کاری را انجام دهید؟ مثلا لیستی از مشتریان که باید برای تک تک آنها Pdf ای را بسازید، یا لیستی از مشتریان که باید برای تک تک آنها بیمه نامه صادر کنید، یا مثلا لیست اطلاعات بلیط‌های قابل فروش را گرفته‌اید و برای تک تک آنها می‌خواهید کمیسیون حساب کنید و ...

در اکثر مواقعی کاری که برای تک تک آیتم‌ها قرار است انجام شود، ساده است و با استفاده از یک حلقه foreach کار تمام میشود. اما در بعضی مواقع کار زمانبر است؛ حال یا به علت وجود کاری CPU bound مثل درست کردن Pdf و محاسبات، یا کار IO Bound است مثل ارسال یک HTTP Request به ازای هر مشتری، یا ذخیره کردن چیزی در دیتابیس که هم CPU bound است و هم IO bound و ترکیبی از مواردی که گفتیم را دارد.

فرض کنیم صد مشتری داریم و برای تک تک آنها میخواهیم کاری انجام دهیم. اگر از یک foreach ساده استفاده کنیم و هر عمل یک ثانیه طول بکشد، کل روال 100 ثانیه طول می‌کشد که جالب نیست.
public async Task Sample()
{
    var customers = await GetCustomersFromSomeWhere();

    foreach (var customer in customers)
    {
        await DoSomethingWithCustomer(customer);
    }
}
با اندکی جستجو در اینترنت به Task.WhenAll می‌رسیم و مشکلی که دارد این است که هر 100 کار را با هم شروع می‌کند که میتواند اثرات مخربی روی کلیت عملکرد سرور بگذارد:
public async Task Sample()
{
    var customers = await GetCustomersFromSomeWhere();
    await Task.WhenAll(customers.Select(c => DoSomethingWithCustomer(c)));
}
اگر چه می‌توانیم خودمان آیتم‌ها را دسته بندی کنیم و مثلا هر 25 تا 25 آنها را با هم پردازش کنیم، ولی این دسته بندی خیلی معقول نیست، چون RX اینجاست!
public async Task Sample()
{
    var customers = await GetCustomersFromSomeWhere();

    await customers.Select(c => Observable.FromAsync(() => DoSomethingWithCustomer(c))).Merge(maxConcurrent: 25);
}
به خاطر وجود maxConcurrent: 25 در دستور فوق، مشتری‌ها بسته به وضعیت کلی سرور، حداکثر 25 تا 25 تا پردازش می‌شوند، ولی ممکن است مثلا 10 تا 10 پردازش شوند. البته انتظار هوشمندی خیلی زیادی از آن نداشته باشید.

استفاده از Rx وقتی که دستورات داخل DoSomethingWithCustomer به صورت IO bound باشند (اتصال به دیتابیس و ارسال Http Request و ...) به خوبی جواب می‌دهد. ولی اگر دستورات داخل DoSomethingWithCustomer به صورت CPU bound باشند، مثل محاسبات یا ساختن Pdf و ... دیگر این روش جواب نمی‌دهد و اینجاست که باید از Task Parallel Library استفاده کنیم ( البته Task Parallel Libraray یا به اختصار TPL هم برای IO Bound و هم برای CPU Bound مناسب است).
برای استفاده از TPL داریم:
public async Task Sample()
{
    var customers = await GetCustomersFromSomeWhere();

    ActionBlock<Customer> action = new ActionBlock<Customer>(c => DoSomethingWithCustomer(c), new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = 25
    }); 

    foreach (var customer in customers)
    {
        action.Post(customer);
    }

    action.Complete();

    await action.Completion;
}
همانطور که می‌بینید، بحث 25 تا 25 تا اجرا کردن در اینجا نیز وجود دارد، با این تفاوت که بسیار هوشمندانه‌تر کارها را به صورتی پیش می‌برد که از منابع سرور به بهینه‌ترین شکل ممکن استفاده شود و همین TPL را هم برای اعمال IO bound و هم اعمال CPU bound مناسب می‌کند.

سایر گزینه هایی که داریم شامل Parallel Linq و Parallel.ForEach است که عموما برای کارهای CPU bound مناسبند.
گزینه‌هایی از قدیم نیز وجود دارند، مانند استفاده از Thread و Semaphore و ... که ابدا استفاده مستقیم از آنها توصیه نمیشود.
البته با TPL و RX میشود کارهای خیلی بیشتری نیز انجام داد و این دو فقط برای این سناریو ساخته نشده‌اند و همه جا جایگزین یکدیگر نیستند و هر دو دنیای وسیعی هستند که توصیه می‌کنم به هر دو نگاهی بیاندازید. همچنین TPL تا جایی که می‌دانم جزو مواردی است که در بیرون از دنیای NET. وجود ندارد و یکی از ارزشمندترین ویژگی‌های Unique در NET. است که به این سادگی چنین مسئله‌ای با این کیفیت حل شود.

توجه داشته باشید که اگر فرآیندی که برای تک تک Customer‌ها در مثال فوق قرار است انجام شود، خود یک روال سنگین و زمان بر باشد، بهتر است از روش‌های دیگری مبتنی بر Event processing و ابزارهایی چون Azure Service Bus یا Mass Transit استفاده کنیم که کمک می‌کنند اگر مثلا سه سرور داریم، بار پردازش آن 100 مشتری مثال ما، بین سه سرور هم پخش شود که این مورد پیچیدگی‌های خود را دارد و در اینجا که فرض بر این است که سناریو خیلی پیچیده و میزان بار خیلی زیاد نیست و همچنین نیازی هم به استفاده از این موارد و اضافه کردن پیچیدگی‌های بیشتر به برنامه نیست. در واقع TPL علیرغم کار بسیار ارزشمندی که می‌کند، در نهایت یک Nuget Package است که در یک دستگاه موبایل Android و با Xamarin نیز قابل استفاده است.

البته این همه داستان نیست. برای مثال در صورتی که فرآیندی بخواهد به صورت Concurrent / Parallel انجام شود و در انجام آن از Entity Framework Db Context استفاده شده باشد، کد به مشکل بر میخورد و خطا می‌دهد، چون یک Instance از DbContext مناسب انجام چند کار همزمان نیست. به واقع تمامی Objectهایی که Thread Safe نباشند، در روش‌های فوق به مشکل بر میخوردند. همچنین بحث مدیریت کردن Transaction در صورتی که بخواهید با دیتابیس هم کار کنید نیز خود مسئله‌ای است که باید حل شود.
حل مسائلی که گفته شد و ادغام کردن روش‌های فوق با بحث Dependency Injection و ... موضوع بحث قسمت بعدی این مطلب است.
راهنماهای پروژه‌ها
سؤالات متداول
  • می‌خواهم برنامه‌ام را بر روی کامپیوتر مشتری بدون دردسر نصب کنم؛ با حداقل حجم و دردسر توزیع. به علاوه بدون نیاز به وصله پینه کردن اسمبلی‌های تجاری دریافت شده.

پاسخ: PdfReport از چند اسمبلی دات نتی تشکیل شده است که نیاز به نصب خاصی ندارد. همچنین حجم فشرده شده آن نیز زیر 2 مگابایت است. بنابراین از جهت توزیع مشکل خاصی نخواهید داشت. همچنین کل این مجموعه سورس باز است و مشکلات متداول همراه با گزارش سازهای تجاری را به همراه ندارد.

  • می‌خواهم فایل گزارش، به همراه برنامه و فایل exe آن و نه جدای از آن توزیع شود.

پاسخ: روش کار با PdfReport اصطلاحا code-first است. یعنی یک یا چند کلاس تهیه شده توسط شما، کار تهیه گزارش را انجام می‌دهند و تمام این‌ها کامپایل شده و به همراه فایل اجرایی یا اسمبلی‌های خاصی که برای آن درنظر می‌گیرید، کامپایل و توزیع خواهند شد. همچنین با توجه به code-first بودن آن و عدم وابستگی به فناوری خاصی، این گزارشات در برنامه‌های وب و ویندوز نیز می‌توانند بدون نیاز به تغییری در کدهای شما مورد استفاده قرار گیرند.

  • برای کار با PdfReport از کجا باید شروع کرد؟

پاسخ: لطفا برچسب PdfReport را در سایت جاری دنبال نمائید. نحوه استفاده از قابلیت‌های آن قدم به قدم توضیح داده شده‌اند.

  • می‌خواهم برای صرفه جویی در کاغذ چاپی، گزارش چند ستونه‌ای را تهیه کنم.

پاسخ: لطفا مراجعه کنید به مثال ایجاد قالب‌های سفارشی ستون‌ها.

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

پاسخ: در PdfReport ارتفاع هر سطر به صورت خودکار بر مبنای حجم وارد شده محاسبه و تنظیم می‌گردد. به عبارتی این تنظیم ثابت نیست و سبب حذف محتوای ارائه شده در آن‌ها یا محو شدن ردیف‌های دیگر نمی‌گردد.

  • نیاز به گزارش سازی دارم که بدون مشکل با ORMها کار کند. برای مثال در حین کار با Entity framework مستقیما بتواند با اشیاء و لیست‌های حاصل از آن کار کند.

پاسخ: یکی از انواع منابع داده تعریف شده در PdfReport جهت کار با ORMها طراحی شده است که مثالی از نحوه استفاده از آن‌را در مثال EF Code first همراه با مجموعه مثال‌های این کتابخانه می‌توانید ملاحظه نمائید.

  • آیا این کتابخانه می‌تواند از فایل سیستم (و نه صرفا بانک اطلاعاتی) هم تصاویر را دریافت و در گزارشات قرار دهد؟

پاسخ: بلی. لطفا به مثال ImageFilePath مراجعه نمائید.

  • می‌خواهم یک گزارش ساز پویا در برنامه داشته باشم. فقط کوئری غیر مشخصی را به آن بدهم و حاصل آن یک گزارش باشد.

پاسخ: امکان تهیه گزارش‌های پویا نیز در PdfReport پیش بینی شده است. توضیحات بیشتر همچنین این امکان در حین کار با ORMها نیز وجود دارد.

  • می‌خواهم بدون استفاده از بانک اطلاعاتی نیز بتوانم گزارشی را تهیه کنم. برای مثال یک لیست جنریک تشکیل شده در حافظه دارم.

پاسخ: برای این منظور تنها کافی است از منبع داده صحیحی استفاده نمائید. برای اطلاعات بیشتر به مثال IList مراجعه کنید.

  • می‌خواهم از بانک‌های اطلاعاتی دیگری بجز SQL Server استفاده کنم.

پاسخ: می‌توانید یک نمونه مثال استفاده از PdfReport را با بانک اطلاعاتی SQLite، در اینجا مشاهده کنید.

نظرات مطالب
سیلورلایت 5 و تاریخ شمسی
موضوع بحث کاملا مشخص است. تاریخ شمسی و سیلورلایت 5.
یافتن Max با LINQ یک نکته انحرافی هم دارد. زمانیکه شما OrderByDescending را فراخوانی کنید و سپس Take اولین المان لیست، به همان Max می‌‌رسید.
var max = query.OrderByDescending(...) .Take(1);

مسیرراه‌ها
Entity framework code-first
شروع به کار با EF Code first

برای تکمیل بحث نیاز است تغییرات انجام شده از نگارش 4 به 6 را نیز مد نظر داشته باشید:


آشنایی با مباحث Migrations



آشنایی با تنظیمات نگاشت‌ها به دو روش استفاده از ویژگی‌ها و Fluent API



اعتبارسنجی و بررسی استثناءها



ردیابی تغییرات



استفاده از SQL خام و بانک‌های اطلاعاتی متفاوت

      نکات مهم کوئری نویسی در EF



      استفاده از EF در WPF


      لایه بندی پروژه‌های EF Code first



      پروژ‌ه‌های انجام شده با EF Code first

       
      نظرات مطالب
      کمپین ضد IF !
      من شباهتی بین مطلب این مقاله و Dependency Injection نمی بینم.
      مطلب بالا دقیقا پیاده سازی الگوی طراحی Strategy هست. جایی که رفتارها (عملیات محاسبه Aggregate) از رفتار کننده (محاسبه گر، ماشین حساب) جدا شده و در کلاسهای خودشان که یک اینترفیس مشترک را پیاده سازی می کنند، تعریف می شوند.
      مطالب دوره‌ها
      خلاصه‌ای از اعمال متداول با AutoMapper و Entity Framework
      فرض کنید کلاس‌های مدل برنامه از سه کلاس مشتری، سفارشات مشتری‌ها و اقلام هر سفارش تشکیل شده‌است:
      public class Customer
      {
          public int Id { set; get; }
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public string Bio { get; set; }
       
          public virtual ICollection<Order> Orders { get; set; }
       
          [Computed]
          [NotMapped]
          public string FullName
          {
              get { return FirstName + ' ' + LastName; }
          }
      }
      
      public class Order
      {
          public int Id { set; get; }
          public string OrderNo { get; set; }
          public DateTime PurchaseDate { get; set; }
          public bool ShipToHomeAddress { get; set; }
       
          public virtual ICollection<OrderItem> OrderItems { get; set; }
       
          [ForeignKey("CustomerId")]
          public virtual Customer Customer { get; set; }
          public int CustomerId { get; set; }
       
          [Computed]
          [NotMapped]
          public decimal Total
          {
              get { return OrderItems.Sum(x => x.TotalPrice); }
          }
      }
      
      public class OrderItem
      {
          public int Id { get; set; }
          public decimal Price { get; set; }
          public string Name { get; set; }
          public int Quantity { get; set; }
       
          [ForeignKey("OrderId")]
          public virtual Order Order { get; set; }
          public int OrderId { get; set; }
       
          [Computed]
          [NotMapped]
          public decimal TotalPrice
          {
              get { return Price * Quantity; }
          }
      }
      در اینجا برای پیاده سازی خواص محاسباتی، از نکته‌ی مطرح شده‌ی در مطلب «نگاشت خواص محاسبه شده به کمک AutoMapper و DelegateDecompiler» استفاده شده‌است.
      در ادامه می‌خواهیم اطلاعات حاصل از این کلاس‌ها را با شرایط خاصی به ViewModelهای مشخصی جهت نمایش در برنامه نگاشت کنیم.


      نمایش اطلاعات مشتری‌ها

      می‌خواهیم اطلاعات مشتری‌ها را مطابق فرمت کلاس ذیل بازگشت دهیم:
      public class CustomerViewModel
      {
          public string Bio { get; set; }
          public string CustomerName { get; set; }
      }
      با این شرایط که
      - اگر Bio نال بود، بجای آن N/A نمایش داده شود.
      - CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.

      برای حل این مساله، نیاز است نگاشت زیر را تهیه کنیم:
      this.CreateMap<Customer, CustomerViewModel>()
         .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(entity => entity.FullName))
         .ForMember(dest => dest.Bio, opt => opt.MapFrom(entity => entity.Bio ?? "N/A"));
      AutoMapper برای جایگزین کردن خواص با مقدار نال، با یک مقدار مشخص، از متدی به نام NullSubstitute استفاده می‌کند. اما در این حالت خاص که قصد داریم از Project To استفاده کنیم، این روش پاسخ نمی‌دهد و محدودیت‌هایی دارد. به همین جهت از روش map from و بررسی مقدار خاصیت، استفاده شده‌است.
      همچنین در اینجا مطابق نگاشت فوق، خاصیت CustomerName از خاصیت FullName کلاس مشتری دریافت می‌شود.

      کوئری نهایی استفاده کننده‌ی از این اطلاعات به شکل زیر خواهد بود:
      using (var context = new MyContext())
      {
          var viewCustomers = context.Customers
              .Project()
              .To<CustomerViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewCustomers = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerViewModel>>(dbCustomers);
          foreach (var customer in viewCustomers)
          {
              Console.WriteLine("{0} - {1}", customer.CustomerName, customer.Bio);
          }
      }
      در اینجا از متدهای Project To و همچنین Decompile استفاده شده‌است (جهت پردازش خاصیت محاسباتی).


      نمایش اطلاعات سفارش‌های مشتری‌ها

      در ادامه قصد داریم اطلاعات سفارش‌ها را با فرمت ViewModel ذیل نمایش دهیم:
      public class OrderViewModel
      {
          public string CustomerName { get; set; }
          public decimal Total { get; set; }
          public string OrderNumber { get; set; }
          public IEnumerable<OrderItemsViewModel> OrderItems { get; set; }
      }
      
      public class OrderItemsViewModel
      {
          public string Name { get; set; }
          public int Quantity { get; set; }
          public decimal Price { get; set; }
      }
      با این شرایط که
      - CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.
      - خاصیت OrderNumber آن از خاصیت OrderNo تهیه گردد.

      به همین جهت کار را با تهیه‌ی نگاشت ذیل ادامه می‌دهیم:
      this.CreateMap<Order, OrderViewModel>()
        .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo))
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      بر این اساس کوئری مورد استفاده نیز به نحو ذیل تشکیل می‌شود:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total);
          }
      }
      در اینجا چون از خاصیت OrderItems کلاس ViewModel صرفنظر نشده‌است، اطلاعات آن نیز به همراه viewOrders موجود است. یعنی می‌توان چنین کوئری را نیز جهت نمایش اطلاعات تو در توی اقلام هر سفارش نیز نوشت:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total);
              foreach (var item in order.OrderItems)
              {
                  Console.WriteLine("({0}) {1} - {2}", item.Quantity, item.Name, item.Price);
              }
          }
      }
      اگر می‌خواهید OrderItems به صورت خودکار واکشی نشود، نیاز است در نگاشت تهیه شده، توسط متد Ignore از آن صرفنظر کنید:
      this.CreateMap<Order, OrderViewModel>()
        .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo))
        .ForMember(dest => dest.OrderItems, opt => opt.Ignore())
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));


      نمایش اطلاعات یک سفارش، با فرمتی خاص

      تا اینجا نگاشت‌های انجام شده بر روی لیستی از اشیاء صورت گرفتند. در ادامه می‌خواهیم اولین سفارش ثبت شده را با فرمت ذیل نمایش دهیم:
      public class OrderDateViewModel
      {
          public int PurchaseHour { get; set; }
          public int PurchaseMinute { get; set; }
          public string CustomerName { get; set; }
      }
      به همین منظور ابتدا نگاشت ذیل را تهیه می‌کنیم:
      this.CreateMap<Order, OrderDateViewModel>()
        .ForMember(dest => dest.PurchaseHour, opt => opt.MapFrom(src => src.PurchaseDate.Hour))
        .ForMember(dest => dest.PurchaseMinute, opt => opt.MapFrom(src => src.PurchaseDate.Minute))
        .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      در اینجا ساعت و دقیقه‌ی خرید، از خاصیت PurchaseDate استخراج شده‌اند. همچنین CustomerName نیز از خاصیت FullName کلاس مشتری دریافت گردیده‌است.
      پس از این تنظیمات، کوئری نهایی به شکل ذیل خواهد بود:
      using (var context = new MyContext())
      {
          var viewOrder = context.Orders
              .Project()
              .To<OrderDateViewModel>()
              .Decompile()
              .FirstOrDefault();
          // don't use
          // var viewOrder = Mapper.Map<Order, OrderDateViewModel>(dbOrder);
       
          if (viewOrder != null)
          {
              Console.WriteLine("{0}, {1}:{2}", viewOrder.CustomerName, viewOrder.PurchaseHour, viewOrder.PurchaseMinute);
          }
      }


      فرمت کردن سفارشی اطلاعات در حین نگاشت‌ها

      در مورد فرمت کننده‌های سفارشی و تبدیلگرها پیشتر بحث کرده‌ایم. اما اغلب آن‌ها را در حالت خاص LINQ to Entities نمی‌توان بکار برد، زیرا قابلیت تبدیل به SQL را ندارند. برای مثال فرض کنید می‌خواهیم خاصیت ShipToHomeAddress کلاس Order را به خاصیت ShipHome کلاس ذیل نگاشت کنیم:
      public class OrderShipViewModel
      {
          public string ShipHome { get; set; }
          public string CustomerName { get; set; }
      }
      با این شرط که اگر مقدار آن True بود، Yes را نمایش دهد. با توجه به ساختار مدنظر، نگاشت ذیل را می‌توان تهیه کرد که در آن فرمت کردن سفارشی، به متد MapFrom واگذار شده‌است:
      this.CreateMap<Order, OrderShipViewModel>()
         .ForMember(dest => dest.ShipHome, opt => opt.MapFrom(src=>src.ShipToHomeAddress? "Yes": "No"))
         .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
      با این کوئری جهت استفاده‌ی از این تنظیمات:
      using (var context = new MyContext())
      {
          var viewOrders = context.Orders
              .Project()
              .To<OrderShipViewModel>()
              .Decompile()
              .ToList();
          // don't use
          // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderShipViewModel>>(dbOrders);
          foreach (var order in viewOrders)
          {
              Console.WriteLine("{0} - {1}", order.CustomerName, order.ShipHome);
          }
      }

      کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
      نظرات مطالب
      بررسی تفصیلی رابطه Many-to-Many در EF Code first
      سلام
      در قسمت :
      //فقط موارد جدید به تگ‌ها و ارتباطات موجود اضافه می‌شوند
      foreach (var tag in data.Tags)
      {
           if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim()))
           {
                  _tag.Add(new Tag { Title = tag.Trim() });
            }
      }
       _uow.SaveChanges(); // ثبت موارد جدید

      فقط تگ‌ها در جدول خودشون ذخیره میشن و ارتباطی با پست مربوطه ایجاد نمیشه.
      آیا نباید به کد زیر تغییر داد؟
      if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim()))
       {
            var tags = new Tag { Title = tag.Trim() };
             _tag.Add(tags);
             posts.Tags.Add(tags);
        }
      نظرات مطالب
      آموزش MDX Query - قسمت ششم – شروع کار با دستورات MDX
      در قسمت اول این مجموعه توضیحات لازم را در خصوص مقدمات کار و مفاهیم اولیه خدمتتان ارایه کردم. برای آشنایی با ساخت Data Warehouse لطفا مراجعه کنید به قسمت (مدل داده‌ای رابطه‌ای (Relational) وچند بعدی (Multidimensional) ) در سری اول.
      در خصوص ایجاد ابعاد ی مانند بعد تاریخ و بعد سن باید عرض کنم که این ابعاد امکان ارایه انواع گزارشات مختلف را به شما خواهند داد و مطلقا OLAP را از حالت دینامیک خارج نمیکند.
      در خصوص نحوه‌ی تحلیل و طراحی DW ، امیدوارم به زودی بتوانم مقاله ای را انتشار بدهم.
      در خصوص عملگر‌های تقسیم و ... و توابع جمعی به زودی در قسمت‌های بعدی اطلاعات کاملی را خدمتتان ارایه خواهم کرد. به طور خلاصه اینکه OLAP برای ایجاد گزارشات مدیریتی می‌باشد و قطعا این موارد شما در ادامه پوشش  داده خواهد شد.(شما به راحتی می‌توانید سرجمع یک بازه‌ی تاریخی یا سنی را برای یک Measure خاص بدست آورید.)