- در نسخه بعدی تغییراتی مثل محدود کردن انتخاب تاریخ و انتخاب سادهتر سال و ماه در نظر گرفته شده است.
نظرات مطالب
Cookie - قسمت اول
البته هنوز مطالب زیادی درباره کوکیها مونده که در قسمتهای بعدی به اونا پرداخته میشه.
بازخوردهای پروژهها
درخواست
با سلام
نسخه بعدی کی آماده میشه
البته با تشکر از زحمت هایی که میکشید
نسخه بعدی کی آماده میشه
البته با تشکر از زحمت هایی که میکشید
یک نکتهی تکمیلی: MemoryCache در ASP.NET Core 3.0
از نگارش 2.0، به تنظیمات MemoryCache خاصیت SizeLimit اضافه شدهاست تا اگر به این حد رسید، شروع به حذف کردن آیتمهای موجود کند:
public class MyMemoryCache { public MemoryCache Cache { get; set; } public MyMemoryCache() { Cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1024 }); } }
Exception: Cache entry must specify a value for Size when SizeLimit is set. at Microsoft.Extensions.Caching.Memory.MemoryCache.SetEntry(CacheEntry entry)
var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSize(1) .SetSlidingExpiration(TimeSpan.FromSeconds(3));
- اولین تجربه با BugNET | (Afshar Mohebbi) | blog.afsharm.com
- پایگاه علمی 'الزویر' تحریم شد | www.irna.ir
- چگونه با مهلت های نامعقول برای انجام کار در محیط کار کنار بیاییم؟ | (بهروز راد) | www.codepro.ir
- دیدگاه کاربرها و برنامه نویسان نسبت به هم! | خدادادی طاقانکی | nodiran.com
- مروری بر فریمورک Qt | سروش | linuxreview.ir
- معرفی سایت - جایی برای کپی پیست در اینترنت | (مجتبی بنائی) | www.banaie.ir
- SQL Server Denali CTP3 Demo VHD | www.microsoft.com
- از ارسال کوکیهای اضافی در ASP.NET جلوگیری کنید | omaralzabir.com
- بهینه سازی برنامههای ASP.NET | madskristensen.net
- توضیحاتی در مورد پروژه Roslyn یا همان «کامپایلر به عنوان سرویس» | www.microsoft.com
- دریافت نگارش آزمایشی پروژه Roslyn یا همان «کامپایلر به عنوان سرویس» | www.microsoft.com
- کمی در مورد جزئیات تاریخ منقضی شدن Redgate's Reflector | picturoku.blogspot.com
- مثالهایی در مورد پروژه Roslyn یا همان «کامپایلر به عنوان سرویس» | www.microsoft.com
در مطلب پیشین برای نگهداری حالت شیء یا همان ویژگیهای آن Propertyها را در کلاس معرفی کردیم و پس از ایجاد شیء مقدار مناسبی را به پروپرتیها اختصاص دادیم.
اگرچه ایجاد شیء و مقداردهی به ویژگیهای آن ما را به هدفمان میرساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازندهها (Constructors) حل میشود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا میشود. وظیفه سازنده مقداردهی به ویژگیهای عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامهنویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی میکنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی میکنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی میکنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
چون در بخشی از یک پروژه نیاز پیدا کردیم با یک سری مثلث کار کنیم، کلاس بالا را طراحی کرده ایم. به نکات زیر توجه نمایید.
• در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگیها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز میتوان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت میکنیم.
• در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.
وقتی شیء ای از یک کلاس ایجاد میشود، بلافاصله سازنده آن فراخوانی میگردد. سازندهها هم نام کلاسی هستند که در آن تعریف میشوند و معمولاً اعضای داده ای شیء جدید را مقداردهی میکند. همانطور که میدانید وهله سازی از یک کلاس با عملگر new انجام میشود. سازنده کلاس بلافاصله پس از آنکه حافظه برای شیء در حال تولید اختصاص داده شد، توسط عملگر new فراخوانی میشود.
اگر برای کلاسی که طراحی میکنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتیها را با توجه به نوع آنها تنظیم مینماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتیها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای میسازد که وضعیت صحیحی ندارد و نمیتواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.
در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف میکنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
در این سازنده مقدار ۱ را برای متغیر خصوصی پشت (backing field یا backing store) هر یک از دو ویژگی قاعده و ارتفاع تنظیم نموده ایم.
کد بالا مقدار ۱ را برای قاعده و ارتفاع و مقدار ۰.۵ را برای مساحت چاپ مینماید. بنابراین مشخص است که سازنده اجرا شده و مقادیر مناسب را برای شیء تنظیم نموده به طوری که شیء از بدو تولید در وضعیت مناسبی است.
با توجه به اینکه مقادیر ارسالی به این سازنده توسط کد مشتری در نظر گرفته میشود و ممکن است در محدوده مجاز نباشد، به جای انتساب مستقیم این مقادیر به فیلد خصوصی پشت ویژگی قاعده و ارتفاع یعنی baseLength_ و height_ آنها را به پروپرتیها منتسب کردیم تا قانون اعتبارسنجی موجود در اکسسور set پروپرتیها از انتساب مقادیر غیر مجاز جلوگیری کند.
سازنده اخیر را میتوان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومانهای مورد نظر مورد استفاده قرار داد.
مقادیر چاپ شده برابر ۵ برای ارتفاع، ۸ برای قاعده و ۲۰ برای مساحت خواهد بود که نشان از اجرای صحیح سازنده دارد.
در مطالب بالا چندین بار از سازندهها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه میکنیم. این دو نکته را به خاطر داشته باشید:
در بخشهای بعدی مطالب بیشتری در مورد سازندهها و سایر اعضای کلاس خواهید آموخت.
اگرچه ایجاد شیء و مقداردهی به ویژگیهای آن ما را به هدفمان میرساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازندهها (Constructors) حل میشود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا میشود. وظیفه سازنده مقداردهی به ویژگیهای عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامهنویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی میکنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی میکنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی میکنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
public class Triangle { private int _height; private int _baseLength; public int Height { get { return _height; } set { if (value < 1 || value > 100) { // تولید خطا } _height = value; } } public int BaseLength { get { return _baseLength; } set { if (value < 1 || value > 100) { // تولید خطا } _baseLength = value; } } public double Area { get { return _height * _baseLength * 0.5; } } }
• در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگیها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز میتوان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت میکنیم.
• در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.
وقتی شیء ای از یک کلاس ایجاد میشود، بلافاصله سازنده آن فراخوانی میگردد. سازندهها هم نام کلاسی هستند که در آن تعریف میشوند و معمولاً اعضای داده ای شیء جدید را مقداردهی میکند. همانطور که میدانید وهله سازی از یک کلاس با عملگر new انجام میشود. سازنده کلاس بلافاصله پس از آنکه حافظه برای شیء در حال تولید اختصاص داده شد، توسط عملگر new فراخوانی میشود.
سازنده پیش فرض
سازندهها مانند متدهای دیگر میتوانند پارامتر دریافت کنند. سازنده ای که هیچ پارامتری دریافت نمیکند سازنده پیش فرض (Default constructor) نامیده میشود. سازنده پیش فرض زمانی اجرا میشود که با استفاده از عملگر new شیء ای ایجاد میکنید اما هیچ آرگومانی را برای این عملگر در نظر نگرفته اید.اگر برای کلاسی که طراحی میکنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتیها را با توجه به نوع آنها تنظیم مینماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتیها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای میسازد که وضعیت صحیحی ندارد و نمیتواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.
جایگزینی سازنده پیش فرض ساخته شده توسط کامپایلر
افزودن یک سازنده صریح به کلاس بسیار شبیه به تعریف یک متد در کلاس است. با این تفاوت که:- سازنده هم نام کلاس است.
- برای سازنده نوع خروجی در نظر گرفته نمیشود.
در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف میکنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
public Triangle() { _height = _baseLength = 1; }
اجرای سازنده
همانطور که گفته شد سازنده اضافه شده به کلاس جایگزین سازنده پیش فرض کامپایلر شده و در هنگام ایجاد یک شیء جدید از کلاس مثلث توسط عملگر new اجرا میشود. برای بررسی اجرا شدن سازنده به سادگی میتوان کدی مشابه مثال زیر را نوشت.Triangle triangle = new Triangle(); Console.WriteLine(triangle.Height); Console.WriteLine(triangle.BaseLength); Console.WriteLine(triangle.Area);
سازندههای پارامتر دار
در مثال قبل یک سازنده بدون پارامتر را به کلاس اضافه کردیم. این سازنده تنها مقادیر پیش فرض مناسبی را تنظیم میکند. بدیهی است پس از ایجاد شیء در صورت نیاز میتوان مقادیر مورد نظر دیگر را برای قاعده و ارتفاع تنظیم نمود. اما برای اینکه سازنده بهتر بتواند فرآیند وهله سازی را کنترل نماید میتوان پارامترهایی را به آن افزود. افزودن پارامتر به سازنده مانند افزودن پارامتر به متدهای دیگر صورت میگیرد. در مثال زیر سازنده دیگری تعریف میکنیم که دارای دو پارامتر است. یکی قاعده و دیگری ارتفاع. به این ترتیب در حین فرآیند وهله سازی میتوان مقادیر مورد نظر را منتسب نمود.public Triangle(int height, int baseLength) { Height = height; BaseLength = baseLength; }
سازنده اخیر را میتوان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومانهای مورد نظر مورد استفاده قرار داد.
Triangle triangle = new Triangle(5, 8); Console.WriteLine(triangle.Height); Console.WriteLine(triangle.BaseLength); Console.WriteLine(triangle.Area);
در مطالب بالا چندین بار از سازندهها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه میکنیم. این دو نکته را به خاطر داشته باشید:
- یک کلاس میتواند دارای چندین سازنده باشد که بر اساس آرگومانهای فراهم شده هنگام وهله سازی، سازنده مورد نظر انتخاب و اجرا میشود.
- الزامی به تعریف یک سازنده پیش فرض (به معنای بدون پارامتر) نیست. یعنی یک کلاس میتواند هیچ سازنده بی پارامتری نداشته باشد.
در بخشهای بعدی مطالب بیشتری در مورد سازندهها و سایر اعضای کلاس خواهید آموخت.
مدتی هست که در حال تهیه کتابخانهی گزارش سازی مبتنی بر iTextSharp هستم و عمدهی استفاده از آن برای من تاکنون، تهیه گزارشات باکیفیت PDF فارسی تحت وب بوده؛ هر چند این کتابخانه وابستگی خاصی به فناوری مورد استفاده ندارد و با WinForms، WPF، مشتقات ASP.NET ، سرویسهای WCF و کلا هرجایی که دات نت فریم ورک کامل در دسترس باشد، قابل استفاده است. همچنین به منبع داده خاصی گره نخورده است و حتی میتوانید از یک anonymously typed list عدم متصل به بانک اطلاعاتی خاصی نیز به عنوان منبع داده آن استفاده کنید.
کتابخانه PdfReport به عمد جهت دات نت 3.5 تهیه شده است تا بازه وسیعی از سیستم عاملها را پوشش دهد.
این کتابخانه علاوه بر تبدیل اطلاعات شما به گزارشات مبتنی بر PDF، امکان تهیه خروجی خودکار اکسل (2007 به بعد) را نیز دارد. فایل خروجی آن، به صورت پیوست درون فایل PDF تهیه شده قرار میگیرد و جزئی از آن میشود.
بدیهی است اینبار با کتابخانه گزارش سازی روبرو هستید که با راست به چپ مشکلی ندارد!
کتابخانه PdfReport بر پایه کتابخانههای معروف سورس باز iTextSharp و EPPlus تهیه شده است. حداقل مزیت استفاده از آن، صرفه جویی در وقت شما جهت آموختن ریزه کاریهای مرتبط با هر کدام از کتابخانههای یاده شده است. برای نمونه جهت فراگیری کار با iTextSharp نیاز است یک کتاب 600 صفحهای به نام iText in action را مطالعه و تمرین کنید. این مورد منهای مسایل و نکات متعدد مرتبط با زبان فارسی است که در این کتاب به آنها اشارهای نشده است.
برای تهیه آن، مشکلات متداولی که کاربران مدام در انجمنهای برنامه نویسی مطرح میکنند و ابزارهای موجود عاجز از ارائه راه حلی ساده برای حل آنها هستند، مد نظر بوده و امید است نگارش یک این کتابخانه بتواند بسیاری از این دردها را کاهش دهد.
کار با این کتابخانه صرفا با کدنویسی میسر است (code first) و همین مساله انعطاف پذیری قابل توجهی را ایجاد کرده که در طی روزهای آینده با جزئیات بیشتر آن آشنا خواهید شد.
اما چرا PDF؟
استفاده از قالب PDF برای تهیه گزارشات، این مزایا را به همراه دارد:
- دقیقا همان چیزی که مشاهده میشود، در هر مکانی قابل چاپ است. با همان کیفیت، همان اندازه صفحه، همان فونت و غیره. به این ترتیب به صفحه بندی بسیار مناسب و بهینهای میتوان رسید و مشکلات گزارشات HTML ایی وب را ندارد.
- امکان استفاد از فونتهای شکیلتر در آن بدون مشکل و بدون نیاز به نصب بر روی کامپیوتری میسر است؛ چون فونت را میتوان در فایل PDF نیز قرار داد.
- این فایل در تمام سیستم عاملها پشتیبانی میشود. خصوصا اینکه فایل نهایی در تمام کامپیوترها و در انواع و اقسام سیستم عاملها به یک شکل و اندازه نمایش داده خواهد شد. برای مثال اینطور نیست که در ویندوز XP ،اعداد آن فارسی نمایش داده شوند و در ویندوز 7 با تنظیمات محلی خاصی، دیگر اینطور نباشد. حتی تحت لینوکس هم اعداد آن فارسی نمایش داده خواهد شد چون فونت مخصوص بکار رفته، در خود فایل PDF قابل قرار دادن است.
- برنامه معروف و رایگان Adobe reader برای خواندن و مشاهده آن کفایت میکند و البته کلاینت یکبار باید این برنامه را نصب کند. همچنین از این نوع برنامههای رایگان برای مشاهده فایلهای PDF زیاد است.
- تمام صفحات گزارش را در یک فایل میتوان داشت و به یکباره تمام آن نیز به سادگی قابل چاپ است. این مشکلی است که با گزارشات تحت وب وجود دارد که نمیشود مثلا یک گزارش 100 صفحهای را به یکباره به چاپگر ارسال کرد. به همین جهت عموما کاربران درخواست میدهند تا کل گزارش را در یک صفحه HTML نمایش دهید تا ما راحت آنرا چاپ کنیم یا راحت از آن خروجی بگیریم. اما زمانیکه فایل PDF تهیه میشود این مشکلات وجود نخواهد داشت و جهت Print بسیار بهینه سازی شده است. تا حدی که الان فرمت برگزیده تهیه کتابهای الکترونیکی نیز PDF است. مثلا سایت معروف آمازون امکان فروش نسخه PDF کتابها را هم پیش بینی کرده است.
-امکان صفحه بندی دقیق به همراه مشخص سازی landscape یا portrait بودن صفحه نهایی میسر است. چیزی که در گزارشات صفحات وب آنچنان معنایی ندارد.
- امکان رمزنگاری اطلاعات در آن پیش بینی شده است. همچنین میتوان به فایلهای PDF امضای دیجیتال نیز اضافه کرد. به این ترتیب هرگونه تغییری در محتوای فایل توسط برنامههای PDF خوان معتبر گزارش داده شده و میتوان از صحت اطلاعات ارائه شده توسط آن اطمینان حاصل کرد.
- از فشرده سازی مطالب، فایلها و تصاویر قرار داده شده در آن پشتیبانی میکند.
- از گرافیک برداری پشتیبانی میکند.
مجوز استفاده از این کتابخانه:
کار من مبتنی بر LGPL است. به این معنا که به صورت باینری (فایل dll) در هر نوع پروژهای قابل استفاده است.
اما ... PdfReport از دو کتابخانه دیگر نیز استفاده میکند:
- کتابخانه iTextSharp که دارای مجوز AGPL است. این مجوز رایگان نیست.
- کتابخانه EPPlus برای تولید فایلهای اکسل با کیفیت. مجوز استفاده از این کتابخانه LGPL است و تا زمانیکه به صورت باینری از آن استفاده میکنید، محدودیتی را برای شما ایجاد نخواهد کرد.
کتابخانه PdfReport به صورت سورس باز در CodePlex قرار گرفته ؛ اما جهت پرسیدن سؤالات، پیشنهادات، ارائه بهبود و غیره میتوانید (و بهتر است) از قسمت مدیریت پروژه مرتبط در سایت جاری نیز استفاده کنید.
نحوه تهیه اولین گزارش، با کتابخانه PdfReport
الف) یک پروژه Class library جدید را شروع کنید. از این جهت که گزارشات PdfReport در انواع و اقسام پروژههای VS.NET قابل استفاده است، میتوان از این پروژه Class library به عنوان کلاسهای پایه قابل استفاده در انواع و اقسام پروژههای مختلف، بدون نیاز به تغییری در کدهای آن استفاده کرد.
ب) آخرین نگارش فایلهای مرتبط با PdfReport را از اینجا دریافت کنید و سپس ارجاعاتی را به اسمبلیهای موجود در بسته آن به پروژه خود اضافه نمائید (ارجاعاتی به PdfReport، iTextSharp و EPPlus). فایل XML راهنمای کتابخانه نیز به همراه بسته آن میباشد که در حین استفاده از متدها و خواص PdfReport کمک بزرگی خواهد بود.
ج) کلاسهای زیر را به آن اضافه کنید:
از این کلاس برای مشخص سازی محل ذخیره سازی فایلهای نهایی PDF تولیدی استفاده خواهیم کرد.
همانطور که مشاهده میکنید ارجاعاتی را به System.Windows.Forms.dll و System.Web.dll نیاز دارد.
در ادامه کلاس User را جهت ساخت یک منبع داده درون حافظهای تعریف خواهیم کرد:
اکنون کلاس اصلی گزارش ما به صورت زیر خواهد بود:
و برای استفاده از آن:
برای نمونه، جهت مشاهده نمایش این خروجی در یک برنامه ویندوزی، به مثالهای همراه سورس پروژه در مخزن کد آن مراجعه نمائید.
توضیحات بیشتر:
- در قسمت DocumentPreferences، جهت راست به چپ (PdfRunDirection)، اندازه صفحه (PdfPageSize)، جهت صفحه (PageOrientation) و امثال آن تنظیم میشوند.
- سپس نیاز است قلمهای مورد استفاده در گزارش مشخص شوند. در متد DefaultFonts باید دو قلم را معرفی کنید. قلم اول، قلم پیش فرض خواهد بود و قلم دوم برای رفع نواقص قلم اول مورد استفاده قرار میگیرد. برای مثال اگر قلم اول فاقد حروف انگلیسی است، به صورت خودکار به قلم دوم رجوع خواهد شد.
- در ادامه در متد PagesFooter، تاریخ درج شده در پایین تمام صفحات مشخص میشود. در مورد ساخت Footer سفارشی در قسمتهای بعدی بحث خواهد شد.
- در متد PagesHeader، متن و تصویر قرار گرفته در Header تمام صفحات گزارش قابل تنظیم است. این مورد نیز قابل سفارشی سازی است که در قسمتهای بعد به آن خواهیم پرداخت.
- توسط MainTableTemplate، قالب ظاهری ردیفهای گزارش مشخص میشود. یک سری قالب پیش فرض در کتابخانه PdfReport موجود است که توسط متد BasicTemplate آن قابل دسترسی است. در مورد نحوه تعریف قالبهای سفارشی به مرور در قسمتهای بعد، بحث خواهد شد.
- در قسمت MainTablePreferences تنظیمات جدول اصلی گزارش تعیین میشود. برای مثال چه تعداد ردیف در صفحه نمایش داده شود. اگر این مورد را تنظیم نکنید، به صورت خودکار محاسبه خواهد شد. نحوه تعیین عرض ستونهای گزارش به کمک متد ColumnsWidthsType مشخص میشود که در اینجا حالت نسبی درنظر گرفته شده است.
- منبع داده مورد استفاده توسط متد MainTableDataSource مشخص میشود که در اینجا یک لیست جنریک تعیین شده و سپس توسط متد StronglyTypedList در اختیار گزارش ساز جاری قرار میگیرد. تعدادی منبع داده پیش فرض در PdfReport وجود دارند که هر کدام را در قسمتهای بعدی بررسی خواهیم کرد. همچنین امکان تعریف منابع داده سفارشی نیز وجود دارد.
- با کمک متد MainTableSummarySettings، برچسبهای جمعهای پایین صفحات مشخص میشود.
- در قسمت MainTableColumns، ستونهایی را که علاقمندیم در گزارش ظاهر شوند، قید میکنیم. هر ستون باید با یک فیلد یا خاصیت منبع داده متناظر باشد. همچنین همانطور که مشاهده میکنید امکان تعیین Visibility، عرض و غیره آن نیز مهیا است (قابلیت ساخت گزارشاتی که به انتخاب کاربر، ستونهای آن ظاهر یا مخفی شوند). در اینجا توسط callbackهایی که در متد ColumnItemsTemplate قابل دسترسی هستند، میتوان اطلاعات را پیش از نمایش فرمت کرد. برای مثال سه رقم جدا کننده به اعداد اضافه کرد (برای نمونه در خاصیت موجودی فوق) و یا توسط متد AggregateFunction، میتوان متد تجمعی مناسبی را جهت ستون جاری مشخص کرد.
- توسط متد MainTableEvents به بسیاری از رخدادهای داخلی PdfReport دسترسی خواهیم یافت. برای مثال اگر در اینجا رکوردی موجود نباشد، رخداد DataSourceIsEmpty صادر خواهد شد.
- به کمک متد Export، خروجیهای دلخواه مورد نظر را میتوان مشخص کرد. تعدادی خروجی، مانند اکسل، XML و CSV در این کتابخانه موجود است. امکان سفارشی سازی آنها نیز پیش بینی شده است.
- و نهایتا توسط متد Generate مشخص خواهیم کرد که فایل گزارش کجا ذخیره شود.
لطفا برای طرح مشکلات و سؤالات خود در رابطه با کتابخانه PdfReport از این قسمت سایت استفاده کنید.
کتابخانه PdfReport به عمد جهت دات نت 3.5 تهیه شده است تا بازه وسیعی از سیستم عاملها را پوشش دهد.
این کتابخانه علاوه بر تبدیل اطلاعات شما به گزارشات مبتنی بر PDF، امکان تهیه خروجی خودکار اکسل (2007 به بعد) را نیز دارد. فایل خروجی آن، به صورت پیوست درون فایل PDF تهیه شده قرار میگیرد و جزئی از آن میشود.
بدیهی است اینبار با کتابخانه گزارش سازی روبرو هستید که با راست به چپ مشکلی ندارد!
کتابخانه PdfReport بر پایه کتابخانههای معروف سورس باز iTextSharp و EPPlus تهیه شده است. حداقل مزیت استفاده از آن، صرفه جویی در وقت شما جهت آموختن ریزه کاریهای مرتبط با هر کدام از کتابخانههای یاده شده است. برای نمونه جهت فراگیری کار با iTextSharp نیاز است یک کتاب 600 صفحهای به نام iText in action را مطالعه و تمرین کنید. این مورد منهای مسایل و نکات متعدد مرتبط با زبان فارسی است که در این کتاب به آنها اشارهای نشده است.
برای تهیه آن، مشکلات متداولی که کاربران مدام در انجمنهای برنامه نویسی مطرح میکنند و ابزارهای موجود عاجز از ارائه راه حلی ساده برای حل آنها هستند، مد نظر بوده و امید است نگارش یک این کتابخانه بتواند بسیاری از این دردها را کاهش دهد.
کار با این کتابخانه صرفا با کدنویسی میسر است (code first) و همین مساله انعطاف پذیری قابل توجهی را ایجاد کرده که در طی روزهای آینده با جزئیات بیشتر آن آشنا خواهید شد.
اما چرا PDF؟
استفاده از قالب PDF برای تهیه گزارشات، این مزایا را به همراه دارد:
- دقیقا همان چیزی که مشاهده میشود، در هر مکانی قابل چاپ است. با همان کیفیت، همان اندازه صفحه، همان فونت و غیره. به این ترتیب به صفحه بندی بسیار مناسب و بهینهای میتوان رسید و مشکلات گزارشات HTML ایی وب را ندارد.
- امکان استفاد از فونتهای شکیلتر در آن بدون مشکل و بدون نیاز به نصب بر روی کامپیوتری میسر است؛ چون فونت را میتوان در فایل PDF نیز قرار داد.
- این فایل در تمام سیستم عاملها پشتیبانی میشود. خصوصا اینکه فایل نهایی در تمام کامپیوترها و در انواع و اقسام سیستم عاملها به یک شکل و اندازه نمایش داده خواهد شد. برای مثال اینطور نیست که در ویندوز XP ،اعداد آن فارسی نمایش داده شوند و در ویندوز 7 با تنظیمات محلی خاصی، دیگر اینطور نباشد. حتی تحت لینوکس هم اعداد آن فارسی نمایش داده خواهد شد چون فونت مخصوص بکار رفته، در خود فایل PDF قابل قرار دادن است.
- برنامه معروف و رایگان Adobe reader برای خواندن و مشاهده آن کفایت میکند و البته کلاینت یکبار باید این برنامه را نصب کند. همچنین از این نوع برنامههای رایگان برای مشاهده فایلهای PDF زیاد است.
- تمام صفحات گزارش را در یک فایل میتوان داشت و به یکباره تمام آن نیز به سادگی قابل چاپ است. این مشکلی است که با گزارشات تحت وب وجود دارد که نمیشود مثلا یک گزارش 100 صفحهای را به یکباره به چاپگر ارسال کرد. به همین جهت عموما کاربران درخواست میدهند تا کل گزارش را در یک صفحه HTML نمایش دهید تا ما راحت آنرا چاپ کنیم یا راحت از آن خروجی بگیریم. اما زمانیکه فایل PDF تهیه میشود این مشکلات وجود نخواهد داشت و جهت Print بسیار بهینه سازی شده است. تا حدی که الان فرمت برگزیده تهیه کتابهای الکترونیکی نیز PDF است. مثلا سایت معروف آمازون امکان فروش نسخه PDF کتابها را هم پیش بینی کرده است.
-امکان صفحه بندی دقیق به همراه مشخص سازی landscape یا portrait بودن صفحه نهایی میسر است. چیزی که در گزارشات صفحات وب آنچنان معنایی ندارد.
- امکان رمزنگاری اطلاعات در آن پیش بینی شده است. همچنین میتوان به فایلهای PDF امضای دیجیتال نیز اضافه کرد. به این ترتیب هرگونه تغییری در محتوای فایل توسط برنامههای PDF خوان معتبر گزارش داده شده و میتوان از صحت اطلاعات ارائه شده توسط آن اطمینان حاصل کرد.
- از فشرده سازی مطالب، فایلها و تصاویر قرار داده شده در آن پشتیبانی میکند.
- از گرافیک برداری پشتیبانی میکند.
مجوز استفاده از این کتابخانه:
کار من مبتنی بر LGPL است. به این معنا که به صورت باینری (فایل dll) در هر نوع پروژهای قابل استفاده است.
اما ... PdfReport از دو کتابخانه دیگر نیز استفاده میکند:
- کتابخانه iTextSharp که دارای مجوز AGPL است. این مجوز رایگان نیست.
- کتابخانه EPPlus برای تولید فایلهای اکسل با کیفیت. مجوز استفاده از این کتابخانه LGPL است و تا زمانیکه به صورت باینری از آن استفاده میکنید، محدودیتی را برای شما ایجاد نخواهد کرد.
کتابخانه PdfReport به صورت سورس باز در CodePlex قرار گرفته ؛ اما جهت پرسیدن سؤالات، پیشنهادات، ارائه بهبود و غیره میتوانید (و بهتر است) از قسمت مدیریت پروژه مرتبط در سایت جاری نیز استفاده کنید.
نحوه تهیه اولین گزارش، با کتابخانه PdfReport
الف) یک پروژه Class library جدید را شروع کنید. از این جهت که گزارشات PdfReport در انواع و اقسام پروژههای VS.NET قابل استفاده است، میتوان از این پروژه Class library به عنوان کلاسهای پایه قابل استفاده در انواع و اقسام پروژههای مختلف، بدون نیاز به تغییری در کدهای آن استفاده کرد.
ب) آخرین نگارش فایلهای مرتبط با PdfReport را از اینجا دریافت کنید و سپس ارجاعاتی را به اسمبلیهای موجود در بسته آن به پروژه خود اضافه نمائید (ارجاعاتی به PdfReport، iTextSharp و EPPlus). فایل XML راهنمای کتابخانه نیز به همراه بسته آن میباشد که در حین استفاده از متدها و خواص PdfReport کمک بزرگی خواهد بود.
ج) کلاسهای زیر را به آن اضافه کنید:
using System.Web; using System.Windows.Forms; namespace PdfReportSamples { public static class AppPath { public static string ApplicationPath { get { if (isInWeb) return HttpRuntime.AppDomainAppPath; return Application.StartupPath; } } private static bool isInWeb { get { return HttpContext.Current != null; } } } }
همانطور که مشاهده میکنید ارجاعاتی را به System.Windows.Forms.dll و System.Web.dll نیاز دارد.
در ادامه کلاس User را جهت ساخت یک منبع داده درون حافظهای تعریف خواهیم کرد:
using System; namespace PdfReportSamples.IList { public class User { public int Id { set; get; } public string Name { set; get; } public string LastName { set; get; } public long Balance { set; get; } public DateTime RegisterDate { set; get; } } }
using System; using System.Collections.Generic; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.IList { public class IListPdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); 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\\tahoma.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.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("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.ClassicTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); table.NumberOfDataRowsPerPage(5); }) .MainTableDataSource(dataSource => { var listOfRows = new List<User>(); for (int i = 0; i < 200; i++) { listOfRows.Add(new User { Id = i, LastName = "نام خانوادگی " + i, Name = "نام " + i, Balance = i + 1000 }); } dataSource.StronglyTypedList<User>(listOfRows); }) .MainTableSummarySettings(summarySettings => { summarySettings.OverallSummarySettings("جمع کل"); summarySettings.PerviousPageSummarySettings("نقل از صفحه قبل"); summarySettings.PageSummarySettings("جمع صفحه"); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName("rowNo"); column.IsRowNumber(true); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.Width(1); column.HeaderCell("#"); }); columns.AddColumn(column => { column.PropertyName<User>(x => x.Id); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.Width(2); column.HeaderCell("شماره"); }); columns.AddColumn(column => { column.PropertyName<User>(x => x.Name); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.Width(3); column.HeaderCell("نام"); }); columns.AddColumn(column => { column.PropertyName<User>(x => x.LastName); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.Width(3); column.HeaderCell("نام خانوادگی"); }); columns.AddColumn(column => { column.PropertyName<User>(x => x.Balance); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(4); column.Width(2); column.HeaderCell("موجودی"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); column.AggregateFunction(aggregateFunction => { aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum); aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "رکوردی یافت نشد."); }) .Export(export => { export.ToExcel(); export.ToCsv(); export.ToXml(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptIListSample.pdf")); } } }
var rpt = new IListPdfReport().CreatePdfReport(); // rpt.FileName
برای نمونه، جهت مشاهده نمایش این خروجی در یک برنامه ویندوزی، به مثالهای همراه سورس پروژه در مخزن کد آن مراجعه نمائید.
توضیحات بیشتر:
- در قسمت DocumentPreferences، جهت راست به چپ (PdfRunDirection)، اندازه صفحه (PdfPageSize)، جهت صفحه (PageOrientation) و امثال آن تنظیم میشوند.
- سپس نیاز است قلمهای مورد استفاده در گزارش مشخص شوند. در متد DefaultFonts باید دو قلم را معرفی کنید. قلم اول، قلم پیش فرض خواهد بود و قلم دوم برای رفع نواقص قلم اول مورد استفاده قرار میگیرد. برای مثال اگر قلم اول فاقد حروف انگلیسی است، به صورت خودکار به قلم دوم رجوع خواهد شد.
- در ادامه در متد PagesFooter، تاریخ درج شده در پایین تمام صفحات مشخص میشود. در مورد ساخت Footer سفارشی در قسمتهای بعدی بحث خواهد شد.
- در متد PagesHeader، متن و تصویر قرار گرفته در Header تمام صفحات گزارش قابل تنظیم است. این مورد نیز قابل سفارشی سازی است که در قسمتهای بعد به آن خواهیم پرداخت.
- توسط MainTableTemplate، قالب ظاهری ردیفهای گزارش مشخص میشود. یک سری قالب پیش فرض در کتابخانه PdfReport موجود است که توسط متد BasicTemplate آن قابل دسترسی است. در مورد نحوه تعریف قالبهای سفارشی به مرور در قسمتهای بعد، بحث خواهد شد.
- در قسمت MainTablePreferences تنظیمات جدول اصلی گزارش تعیین میشود. برای مثال چه تعداد ردیف در صفحه نمایش داده شود. اگر این مورد را تنظیم نکنید، به صورت خودکار محاسبه خواهد شد. نحوه تعیین عرض ستونهای گزارش به کمک متد ColumnsWidthsType مشخص میشود که در اینجا حالت نسبی درنظر گرفته شده است.
- منبع داده مورد استفاده توسط متد MainTableDataSource مشخص میشود که در اینجا یک لیست جنریک تعیین شده و سپس توسط متد StronglyTypedList در اختیار گزارش ساز جاری قرار میگیرد. تعدادی منبع داده پیش فرض در PdfReport وجود دارند که هر کدام را در قسمتهای بعدی بررسی خواهیم کرد. همچنین امکان تعریف منابع داده سفارشی نیز وجود دارد.
- با کمک متد MainTableSummarySettings، برچسبهای جمعهای پایین صفحات مشخص میشود.
- در قسمت MainTableColumns، ستونهایی را که علاقمندیم در گزارش ظاهر شوند، قید میکنیم. هر ستون باید با یک فیلد یا خاصیت منبع داده متناظر باشد. همچنین همانطور که مشاهده میکنید امکان تعیین Visibility، عرض و غیره آن نیز مهیا است (قابلیت ساخت گزارشاتی که به انتخاب کاربر، ستونهای آن ظاهر یا مخفی شوند). در اینجا توسط callbackهایی که در متد ColumnItemsTemplate قابل دسترسی هستند، میتوان اطلاعات را پیش از نمایش فرمت کرد. برای مثال سه رقم جدا کننده به اعداد اضافه کرد (برای نمونه در خاصیت موجودی فوق) و یا توسط متد AggregateFunction، میتوان متد تجمعی مناسبی را جهت ستون جاری مشخص کرد.
- توسط متد MainTableEvents به بسیاری از رخدادهای داخلی PdfReport دسترسی خواهیم یافت. برای مثال اگر در اینجا رکوردی موجود نباشد، رخداد DataSourceIsEmpty صادر خواهد شد.
- به کمک متد Export، خروجیهای دلخواه مورد نظر را میتوان مشخص کرد. تعدادی خروجی، مانند اکسل، XML و CSV در این کتابخانه موجود است. امکان سفارشی سازی آنها نیز پیش بینی شده است.
- و نهایتا توسط متد Generate مشخص خواهیم کرد که فایل گزارش کجا ذخیره شود.
لطفا برای طرح مشکلات و سؤالات خود در رابطه با کتابخانه PdfReport از این قسمت سایت استفاده کنید.
در ادامهی مباحث پشتیبانی از XML در SQL Server، به کارآیی فیلدهای XML ایی و نحوهی ایندکس گذاری بر روی آنها خواهیم پرداخت. این مساله در تولید برنامههایی سریع و مقیاس پذیر، بسیار حائز اهمیت است.
در SQL Server، کوئریهای انجام شده بر روی فیلدهای XML، توسط همان پردازشگر کوئریهای رابطهای متداول آن، خوانده و اجرا خواهند شد و امکان تعریف یک XQuery خارج از یک عبارت SQL و یا T-SQL وجود ندارد. متدهای XQuery بسیار شبیه به system defined functions بوده و Query Plan یکپارچهای را با سایر قسمتهای رابطهای یک عبارت SQL دارند.
مفهوم Node table
دادههای XML ایی برای اینکه توسط SQL Server قابل استفاده باشند، به صورت درونی تبدیل به یک node table میشوند. به این معنا که نودهای یک سند XML، به یک جدول رابطهای به صورت خودکار تجزیه میشوند. این جدول درونی در صورت بکارگیری XML Indexes در جدول سیستمی sys.internal_tables قابل مشاهده خواهد بود. SQL Server برای انجام اینکار از یک XmlReader خاص خودش استفاده میکند. در مورد XMLهای ایندکس نشده، این تجزیه در زمان اجرا صورت میگیرد؛ پس از اینکه Query Plan آن تشکیل شد.
بررسی Query Plan فیلدهای XML ایی
جهت فراهم کردن مقدمات آزمایش، ابتدا جدول xmlInvoice را با یک فیلد XML ایی untyped درنظر بگیرید:
سپس 6 ردیف را به آن اضافه میکنیم:
همچنین برای مقایسه، دقیقا جدول مشابهی را اینبار با یک XML Schema مشخص ایجاد میکنیم.
سپس مجددا همان 6 رکورد قبلی را در این جدول جدید نیز insert خواهیم کرد.
در این جدول دوم، حالت پیش فرض content قبلی، به document تغییر کردهاست. با توجه به اینکه میدانیم اسناد ما چه فرمتی دارند و بیش از یک root element نخواهیم داشت، انتخاب document سبب خواهد شد تا Query Plan بهتری حاصل شود.
در ادامه برای مشاهدهی بهتر نتایج، کش Query Plan و اطلاعات آماری جدول xmlInvoice را حذف و به روز میکنیم:
به علاوه در management studio بهتر است از منوی Query، گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده نمود. برای خواندن یک Query Plan عموما از بالا به پایین و از راست به چپ باید عمل کرد. در آن نهایتا باید به عدد estimated subtree cost کوئری، دقت داشت.
کوئریهایی را که در این قسمت بررسی خواهیم کرد، در ادامه ملاحظه میکنید. بار اول این کوئریها را بر روی xmlInvoice و بار دوم، بر روی نگارش دوم دارای اسکیمای آن اجرا خواهیم کرد:
کوئری 1
همانطور که عنوان شد، از منوی Query گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده کرد.
در کوئری 1، با استفاده از متد exist به دنبال رکوردهایی هستیم که دارای ویژگی InvoiceId مساوی 1003 هستند. پس از اجرای کوئری، تصویر Query Plan آن به شکل زیر خواهد بود:
برای خواندن این تصویر، از بالا به پایین و چپ به راست باید عمل شود. هزینهی انجام کوئری را نیز با نگه داشتن کرسر ماوس بر روی select نهایی سمت چپ تصویر میتوان مشاهده کرد. البته باید درنظر داشت که این اعداد از دیدگاه Query Processor مفهوم پیدا میکنند. پردازشگر کوئری، بر اساس اطلاعاتی که در اختیار دارد، سعی میکند بهترین روش پردازش کوئری دریافتی را پیدا کند. برای اندازه گیری کارآیی، باید اندازه گیری زمان اجرای کوئری، مستقلا انجام شود.
در این کوئری، مطابق تصویر اول، ابتدا قسمت SQL آن (چپ بالای تصویر) پردازش میشود و سپس قسمت XML آن. قسمت XQuery این عبارت در دو قسمت سمت چپ، پایین تصویر مشخص شدهاند. Table valued functionها جاهایی هستند که node table ابتدای بحث جاری در آنها ساخته میشوند. در اینجا دو مرحلهی تولید Table valued functionها مشاهده میشود. اگر به جمع درصدهای آنها دقت کنید، هزینهی این دو قسمت، 98 درصد کل Query plan است.
سؤال: چرا دو مرحلهی تولید Table valued functionها در اینجا قابل مشاهده است؟ یک مرحلهی آن مربوط است به انتخاب نود Invoice و مرحلهی دوم مربوط است به فیلتر داخل [] ذکر شد برای یافتن ویژگیهای مساوی 1003.
در اینجا و در کوئریهای بعدی، هر Query Plan ایی که تعداد مراحل تولید Table valued function کمتری داشته باشد، بهینهتر است.
کوئری 5
اگر کوئری پلن شماره 5 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید. یک XML Reader برای خارج از [] (اصطلاحا به آن predicate گفته میشود) و دو مورد برای داخل [] تشکیل شدهاست؛ یکی برای انتخاب نود متنی و دیگری برای تساوی.
کوئری 7
اگر کوئری پلن شماره 7 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید که بسیار شبیه است به مورد 5. بنابراین در اینجا عمق بررسی و سلسله مراتب اهمیتی ندارد.
کوئری 9
کوئری 9 دقیقا معادل است با کوئری 1 نوشته شده؛ با این تفاوت که از روش FLOWR استفاده کردهاست. نکتهی جالب آن، وجود تنها یک XML reader در Query plan آن است که باید آنرا بخاطر داشت.
کوئری 2
کوئری 3
کوئری 4
کوئری 6
کوئری 8
اگر به این 5 کوئری یاد شده دقت کنید، از یک دات به معنای self استفاده کردهاند (یعنی پردازش بیشتری را انجام نده و از همین نود جاری برای پردازش نهایی استفاده کن). با توجه به بکارگیری متد exist، معنای کوئریهای یک و دو، یکیاست. اما در کوئری شماره 2، تنها یک XML Reader در Query plan نهایی وجود دارد (همانند عبارت FLOWR کوئری شماره 9).
یک نکته: اگر میخواهید بدانید بین کوئریهای 1 و 2 کدامیک بهتر عمل میکنند، از بین تمام کوئریهای موجود، دو کوئری یاد شده را انتخاب کرده و سپس با فرض روش بودن نمایش Query plan، هر دو کوئری را با هم اجرا کنید.
در این حالت، کوئری پلنهای هر دو کوئری را با هم یکجا میتوان مشاهده کرد؛ به علاوهی هزینهی نسبی آنها را در کل عملیات صورت گرفته. در حالت استفاده از دات و وجود تنها یک XML Reader، این هزینه تنها 6 درصد است، در مقابل هزینهی 94 درصدی کوئری شماره یک.
بنابراین از دیدگاه پردازشگر کوئریهای SQL Server، کوئری شماره 2، بسیار بهتر است از کوئری شماره 1.
در کوئریهای 3 و 4، شماره نود مدنظر را دقیقا مشخص کردهایم. این مورد در حالت سوم تفاوت محسوسی را از لحاظ کارآیی ایجاد نمیکند و حتی کارآیی را به علت اضافه کردن یک XML Reader دیگر برای پردازش عدد نود وارد شده، کاهش میدهد. اما کوئری 4 که عدد اولین نود را خارج از پرانتز قرار دادهاست، تنها در کل یک XML Reader را به همراه خواهد داشت.
سؤال: بین کوئریهای 2، 3 و 4 کدامیک بهینهتر است؟
بله. اگر هر سه کوئری را با هم انتخاب کرده و اجرا کنیم، میتوان در قسمت کوئری پلنها، هزینهی هر کدام را نسبت به کل مشاهده کرد. در این حالت کوئری 4 بهتر است از کوئری 2 و تنها یک درصد هزینهی کل را تشکیل میدهد.
کوئری 10
کوئری 10 اندکی متفاوت است نسبت به کوئریهای دیگر. در اینجا بجای متد exist از متد value استفاده شدهاست. یعنی ابتدا صریحا مقدار ویژگی InvoiceId استخراج شده و با 1003 مقایسه میشود.
اگر کوئری پلن آنرا با کوئری 4 که بهترین کوئری سری exist است مقایسه کنیم، کوئری 10، هزینهی 70 درصدی کل عملیات را به خود اختصاص خواهد داد، در مقابل 30 درصد هزینهی کوئری 4. بنابراین در این موارد، استفاده از متد exist بسیار بهینهتر است از متد value.
استفاده از Schema collection و تاثیر آن بر کارآیی
تمام مراحلی را که در اینجا ملاحظه کردید، صرفا با تغییر نام xmlInvoice به xmlInvoice2، تکرار کنید. xmlInvoice2 دارای ساختاری مشخص است، به همراه ذکر صریح document حین تعریف ستون XML ایی آن.
تمام پاسخهایی را که دریافت خواهید کرد با حالت بدون Schema collection یکی است.
برای مقایسه بهتر، یکبار نیز سعی کنید کوئری 1 جدول xmlInvoice را با کوئری 1 جدول xmlInvoice2 با هم در طی یک اجرا مقایسه کنید، تا بهتر بتوان Query plan نسبی آنها را بررسی کرد.
پس از این بررسی و مقایسه، به این نتیجه خواهید رسید که تفاوت محسوسی در اینجا و بین این دو حالت، قابل ملاحظه نیست. در SQL Server از Schema collection بیشتر برای اعتبارسنجی ورودیها استفاده میشود تا بهبود کارآیی کوئریها.
بنابراین به صورت خلاصه
- متد exist را به value ترجیح دهید.
- اصطلاحا ordinal (همان مشخص کردن نود 1 در اینجا) را در آخر قرار دهید (نه در بین نودها).
- مراحل اجرایی را با معرفی دات (استفاده از نود جاری) تا حد ممکن کاهش دهید.
و ... کوئری 4 در این سری، بهترین کارآیی را ارائه میدهد.
در SQL Server، کوئریهای انجام شده بر روی فیلدهای XML، توسط همان پردازشگر کوئریهای رابطهای متداول آن، خوانده و اجرا خواهند شد و امکان تعریف یک XQuery خارج از یک عبارت SQL و یا T-SQL وجود ندارد. متدهای XQuery بسیار شبیه به system defined functions بوده و Query Plan یکپارچهای را با سایر قسمتهای رابطهای یک عبارت SQL دارند.
مفهوم Node table
دادههای XML ایی برای اینکه توسط SQL Server قابل استفاده باشند، به صورت درونی تبدیل به یک node table میشوند. به این معنا که نودهای یک سند XML، به یک جدول رابطهای به صورت خودکار تجزیه میشوند. این جدول درونی در صورت بکارگیری XML Indexes در جدول سیستمی sys.internal_tables قابل مشاهده خواهد بود. SQL Server برای انجام اینکار از یک XmlReader خاص خودش استفاده میکند. در مورد XMLهای ایندکس نشده، این تجزیه در زمان اجرا صورت میگیرد؛ پس از اینکه Query Plan آن تشکیل شد.
بررسی Query Plan فیلدهای XML ایی
جهت فراهم کردن مقدمات آزمایش، ابتدا جدول xmlInvoice را با یک فیلد XML ایی untyped درنظر بگیرید:
CREATE TABLE xmlInvoice ( invoiceId INT IDENTITY PRIMARY KEY, invoice XML )
INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1000" dept="hardware"> <CustomerName>Vahid</CustomerName> <LineItems> <LineItem><Description>Gear</Description><Price>9.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1002" dept="garden"> <CustomerName>Mehdi</CustomerName> <LineItems> <LineItem><Description>Shovel</Description><Price>19.2</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1003" dept="garden"> <CustomerName>Mohsen</CustomerName> <LineItems> <LineItem><Description>Trellis</Description><Price>8.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1004" dept="hardware"> <CustomerName>Hamid</CustomerName> <LineItems> <LineItem><Description>Pen</Description><Price>1.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1005" dept="IT"> <CustomerName>Ali</CustomerName> <LineItems> <LineItem><Description>Book</Description><Price>3.2</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1006" dept="hardware"> <CustomerName>Reza</CustomerName> <LineItems> <LineItem><Description>M.Board</Description><Price>19.5</Price></LineItem> </LineItems> </Invoice> ')
CREATE XML SCHEMA COLLECTION invoice_xsd AS ' <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Invoice"> <xs:complexType> <xs:sequence> <xs:element name="CustomerName" type="xs:string" /> <xs:element name="LineItems"> <xs:complexType> <xs:sequence> <xs:element name="LineItem"> <xs:complexType> <xs:sequence> <xs:element name="Description" type="xs:string" /> <xs:element name="Price" type="xs:decimal" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="InvoiceId" type="xs:unsignedShort" use="required" /> <xs:attribute name="dept" type="xs:string" use="required" /> </xs:complexType> </xs:element> </xs:schema>' Go CREATE TABLE xmlInvoice2 ( invoiceId INT IDENTITY PRIMARY KEY, invoice XML(document invoice_xsd) ) Go
در این جدول دوم، حالت پیش فرض content قبلی، به document تغییر کردهاست. با توجه به اینکه میدانیم اسناد ما چه فرمتی دارند و بیش از یک root element نخواهیم داشت، انتخاب document سبب خواهد شد تا Query Plan بهتری حاصل شود.
در ادامه برای مشاهدهی بهتر نتایج، کش Query Plan و اطلاعات آماری جدول xmlInvoice را حذف و به روز میکنیم:
UPDATE STATISTICS xmlInvoice DBCC FREEPROCCACHE
کوئریهایی را که در این قسمت بررسی خواهیم کرد، در ادامه ملاحظه میکنید. بار اول این کوئریها را بر روی xmlInvoice و بار دوم، بر روی نگارش دوم دارای اسکیمای آن اجرا خواهیم کرد:
-- query 1 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1 -- query 2 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/@InvoiceId[. = "1003"]') = 1 -- query 3 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[1]/@InvoiceId[. = "1003"]') = 1 -- query 4 SELECT * FROM xmlInvoice WHERE invoice.exist('(/Invoice/@InvoiceId)[1][. = "1003"]') = 1 -- query 5 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[CustomerName = "Vahid"]') = 1 -- query 6 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/CustomerName [.= "Vahid"]') = 1 -- query 7 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/LineItems/LineItem[Description = "Trellis"]') = 1 -- query 8 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/LineItems/LineItem/Description [.= "Trellis"]') = 1 -- query 9 SELECT * FROM xmlInvoice WHERE invoice.exist(' for $x in /Invoice/@InvoiceId where $x = 1003 return $x ') = 1 -- query 10 SELECT * FROM xmlInvoice WHERE invoice.value('(/Invoice/@InvoiceId)[1]', 'VARCHAR(10)') = '1003' -- یکبار هم با جدول شماره 2 که اسکیما دارد تمام این موارد تکرار شود UPDATE STATISTICS xmlInvoice DBCC FREEPROCCACHE GO
کوئری 1
همانطور که عنوان شد، از منوی Query گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده کرد.
در کوئری 1، با استفاده از متد exist به دنبال رکوردهایی هستیم که دارای ویژگی InvoiceId مساوی 1003 هستند. پس از اجرای کوئری، تصویر Query Plan آن به شکل زیر خواهد بود:
برای خواندن این تصویر، از بالا به پایین و چپ به راست باید عمل شود. هزینهی انجام کوئری را نیز با نگه داشتن کرسر ماوس بر روی select نهایی سمت چپ تصویر میتوان مشاهده کرد. البته باید درنظر داشت که این اعداد از دیدگاه Query Processor مفهوم پیدا میکنند. پردازشگر کوئری، بر اساس اطلاعاتی که در اختیار دارد، سعی میکند بهترین روش پردازش کوئری دریافتی را پیدا کند. برای اندازه گیری کارآیی، باید اندازه گیری زمان اجرای کوئری، مستقلا انجام شود.
در این کوئری، مطابق تصویر اول، ابتدا قسمت SQL آن (چپ بالای تصویر) پردازش میشود و سپس قسمت XML آن. قسمت XQuery این عبارت در دو قسمت سمت چپ، پایین تصویر مشخص شدهاند. Table valued functionها جاهایی هستند که node table ابتدای بحث جاری در آنها ساخته میشوند. در اینجا دو مرحلهی تولید Table valued functionها مشاهده میشود. اگر به جمع درصدهای آنها دقت کنید، هزینهی این دو قسمت، 98 درصد کل Query plan است.
سؤال: چرا دو مرحلهی تولید Table valued functionها در اینجا قابل مشاهده است؟ یک مرحلهی آن مربوط است به انتخاب نود Invoice و مرحلهی دوم مربوط است به فیلتر داخل [] ذکر شد برای یافتن ویژگیهای مساوی 1003.
در اینجا و در کوئریهای بعدی، هر Query Plan ایی که تعداد مراحل تولید Table valued function کمتری داشته باشد، بهینهتر است.
کوئری 5
اگر کوئری پلن شماره 5 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید. یک XML Reader برای خارج از [] (اصطلاحا به آن predicate گفته میشود) و دو مورد برای داخل [] تشکیل شدهاست؛ یکی برای انتخاب نود متنی و دیگری برای تساوی.
کوئری 7
اگر کوئری پلن شماره 7 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید که بسیار شبیه است به مورد 5. بنابراین در اینجا عمق بررسی و سلسله مراتب اهمیتی ندارد.
کوئری 9
کوئری 9 دقیقا معادل است با کوئری 1 نوشته شده؛ با این تفاوت که از روش FLOWR استفاده کردهاست. نکتهی جالب آن، وجود تنها یک XML reader در Query plan آن است که باید آنرا بخاطر داشت.
کوئری 2
کوئری 3
کوئری 4
کوئری 6
کوئری 8
اگر به این 5 کوئری یاد شده دقت کنید، از یک دات به معنای self استفاده کردهاند (یعنی پردازش بیشتری را انجام نده و از همین نود جاری برای پردازش نهایی استفاده کن). با توجه به بکارگیری متد exist، معنای کوئریهای یک و دو، یکیاست. اما در کوئری شماره 2، تنها یک XML Reader در Query plan نهایی وجود دارد (همانند عبارت FLOWR کوئری شماره 9).
یک نکته: اگر میخواهید بدانید بین کوئریهای 1 و 2 کدامیک بهتر عمل میکنند، از بین تمام کوئریهای موجود، دو کوئری یاد شده را انتخاب کرده و سپس با فرض روش بودن نمایش Query plan، هر دو کوئری را با هم اجرا کنید.
در این حالت، کوئری پلنهای هر دو کوئری را با هم یکجا میتوان مشاهده کرد؛ به علاوهی هزینهی نسبی آنها را در کل عملیات صورت گرفته. در حالت استفاده از دات و وجود تنها یک XML Reader، این هزینه تنها 6 درصد است، در مقابل هزینهی 94 درصدی کوئری شماره یک.
بنابراین از دیدگاه پردازشگر کوئریهای SQL Server، کوئری شماره 2، بسیار بهتر است از کوئری شماره 1.
در کوئریهای 3 و 4، شماره نود مدنظر را دقیقا مشخص کردهایم. این مورد در حالت سوم تفاوت محسوسی را از لحاظ کارآیی ایجاد نمیکند و حتی کارآیی را به علت اضافه کردن یک XML Reader دیگر برای پردازش عدد نود وارد شده، کاهش میدهد. اما کوئری 4 که عدد اولین نود را خارج از پرانتز قرار دادهاست، تنها در کل یک XML Reader را به همراه خواهد داشت.
سؤال: بین کوئریهای 2، 3 و 4 کدامیک بهینهتر است؟
بله. اگر هر سه کوئری را با هم انتخاب کرده و اجرا کنیم، میتوان در قسمت کوئری پلنها، هزینهی هر کدام را نسبت به کل مشاهده کرد. در این حالت کوئری 4 بهتر است از کوئری 2 و تنها یک درصد هزینهی کل را تشکیل میدهد.
کوئری 10
کوئری 10 اندکی متفاوت است نسبت به کوئریهای دیگر. در اینجا بجای متد exist از متد value استفاده شدهاست. یعنی ابتدا صریحا مقدار ویژگی InvoiceId استخراج شده و با 1003 مقایسه میشود.
اگر کوئری پلن آنرا با کوئری 4 که بهترین کوئری سری exist است مقایسه کنیم، کوئری 10، هزینهی 70 درصدی کل عملیات را به خود اختصاص خواهد داد، در مقابل 30 درصد هزینهی کوئری 4. بنابراین در این موارد، استفاده از متد exist بسیار بهینهتر است از متد value.
استفاده از Schema collection و تاثیر آن بر کارآیی
تمام مراحلی را که در اینجا ملاحظه کردید، صرفا با تغییر نام xmlInvoice به xmlInvoice2، تکرار کنید. xmlInvoice2 دارای ساختاری مشخص است، به همراه ذکر صریح document حین تعریف ستون XML ایی آن.
تمام پاسخهایی را که دریافت خواهید کرد با حالت بدون Schema collection یکی است.
برای مقایسه بهتر، یکبار نیز سعی کنید کوئری 1 جدول xmlInvoice را با کوئری 1 جدول xmlInvoice2 با هم در طی یک اجرا مقایسه کنید، تا بهتر بتوان Query plan نسبی آنها را بررسی کرد.
پس از این بررسی و مقایسه، به این نتیجه خواهید رسید که تفاوت محسوسی در اینجا و بین این دو حالت، قابل ملاحظه نیست. در SQL Server از Schema collection بیشتر برای اعتبارسنجی ورودیها استفاده میشود تا بهبود کارآیی کوئریها.
بنابراین به صورت خلاصه
- متد exist را به value ترجیح دهید.
- اصطلاحا ordinal (همان مشخص کردن نود 1 در اینجا) را در آخر قرار دهید (نه در بین نودها).
- مراحل اجرایی را با معرفی دات (استفاده از نود جاری) تا حد ممکن کاهش دهید.
و ... کوئری 4 در این سری، بهترین کارآیی را ارائه میدهد.
اصلاحیه: کارآیی spread operator بیشتر نیست!
در متن در مورد spread operator عنوان شده «که ... نگارش C# 12 آن کارآیی بیشتری دارد». این مورد بدون توجه به #Low-Level C تولیدی، نوشته شد و ... متاسفانه نادرست است!
برای مثال فرض کنید، چنین متدی را دارید که با استفاده از spread operator، کار بازکردن یک آرایه را انجام میدهد:
public int[] WithSpread() { int[] data = new int[10_000]; int[] results = [..data]; return results; }
public int[] WithSpread() { int[] numArray1 = new int[10000]; int index1 = 0; int[] numArray2 = new int[numArray1.Length]; int[] numArray3 = numArray1; for (int index2 = 0; index2 < numArray3.Length; ++index2) { int num = numArray3[index2]; numArray2[index1] = num; ++index1; } return numArray2; }
برای آزمایش شخصی آن، از کلاس زیر استفاده کنید:
using BenchmarkDotNet.Attributes; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SpreadBenchmark { [MemoryDiagnoser] public class Tests { private readonly int[] _myData = new int[10_000]; [Benchmark(Baseline = true)] public int[] WithToArray() { int[] results = _myData.ToArray(); return results; } [Benchmark] public int[] WithCopyTo() { int[] results = new int[_myData.Length]; _myData.CopyTo(results, 0); return results; } [Benchmark] public int[] WithSpread() { int[] results = [.._myData]; return results; } } }
using BenchmarkDotNet.Running; using SpreadBenchmark; BenchmarkRunner.Run<Tests>();
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.13.10" /> </ItemGroup> </Project>
یک نکتهی تکمیلی: تاثیر منفی VS 2017 به روز رسانی نشده بر روی افزونهی #C مخصوص VSCode در ویندوز
اگر VS 2017 خود را به روز رسانی نکرده باشید، ممکن است با باز کردن یک پروژهی NET Core. در VSCode یک چنین خطاهایی را مشاهده کنید:
علت اینجا است که افزونهی #C مخصوص VSCode، در صورت نصب بودن VS 2017، سعی میکند از موتور MSBuild آن استفاده کند؛ بجای نمونهای که به همراه خودش نصب میشود:
همانطور که در این لاگ نیز مشخص است، Registered MSBuild را از نمونهی قدیمی نصب شدهی VS 2017 انتخاب کردهاست (و نه از پوشهی omnisharp\msbuild\15.0\Bin مخصوص خودش).
بنابراین اگر VS 2017 خود را به روز رسانی نکرده باشید، این موتور قدیمی MSBuild، سبب تداخل خواهد شد و خطاهایی مانند عدم یافت شدن نوع void ،int و امثال آنرا مشاهده میکنید. در اینجا یا باید VS 2017 خود را به روز رسانی کنید و یا کلا آنرا از سیستم حذف کنید. البته قرار است این تداخل در نگارش بعدی این افزونه برطرف شود.
روش مشاهدهی لاگ فوق نیز به صورت ذیل است:
اگر VS 2017 خود را به روز رسانی نکرده باشید، ممکن است با باز کردن یک پروژهی NET Core. در VSCode یک چنین خطاهایی را مشاهده کنید:
Predefined type 'System.Void' is not defined or imported #1855
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator Located 2 MSBuild instance(s) 1: Visual Studio Enterprise 2017 15.0.26228.4 - "e:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin" 2: StandAlone 15.0 - "C:\Users\xyz\.vscode\extensions\ms-vscode.csharp-1.13.0\.omnisharp\msbuild\15.0\Bin" [info]: OmniSharp.MSBuild.Discovery.MSBuildLocator Registered MSBuild instance: Visual Studio Enterprise 2017 15.0.26228.4 - "e:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin"
بنابراین اگر VS 2017 خود را به روز رسانی نکرده باشید، این موتور قدیمی MSBuild، سبب تداخل خواهد شد و خطاهایی مانند عدم یافت شدن نوع void ،int و امثال آنرا مشاهده میکنید. در اینجا یا باید VS 2017 خود را به روز رسانی کنید و یا کلا آنرا از سیستم حذف کنید. البته قرار است این تداخل در نگارش بعدی این افزونه برطرف شود.
روش مشاهدهی لاگ فوق نیز به صورت ذیل است:
VS Code -> select View->Output -> select "OmniSharp Log"
این مشکل در نگارش 1.13.1 برطرف شدهاست.