ویرایشگر قدرتمند در OneDrive
در این بلاگ میتوان:
- یک مطلب جدید را ارسال کرد.
- مطالب قابل ویرایش و یا حذف هستند.
- مطالب بلاگ قسمت ارسال نظرات دارند.
- امکان گزارشگیری از آخرین نظرات ارسالی وجود دارد.
- سایت صفحات درباره و تماس با ما را نیز دارا است.
ساختار پوشههای برنامه
در تصویر ذیل، ساختار پوشههای برنامه بلاگ را ملاحظه میکنید. چون قسمت سمت کلاینت این برنامه کاملا جاوا اسکریپتی است، پوشههای App، Controllers، Libs، Models، Routes و Templates آن در پوشهی Scripts تعریف شدهاند و به این ترتیب میتوان تفکیک بهتری را بین اجزای تشکیل دهندهی یک برنامهی تک صفحهای وب Emeber.js پدید آورد.
فایل CSS بوت استرپ نیز به پوشهی Content اضافه شدهاست.
دریافت پیشنیازهای سمت کاربر برنامه
در ساختار پوشههای فوق، از پوشهی Libs برای قرار دادن کتابخانههای پایه برنامه مانند jQuery و Ember.js استفاده خواهیم کرد. به این ترتیب:
- نیاز به آخرین نگارشهای Ember.js و همچنین افزونهی Ember-Data آن برای کار سادهتر با دادهها و سرور وجود دارد. این فایلها را از آدرس ذیل میتوانید دریافت کنید (نسخههای نیوگت به دلیل قدیمی بودن و به روز نشدن مداوم آنها توصیه نمیشوند):
http://emberjs.com/builds/#/beta
برای حالت آزمایش برنامه، استفاده از فایلهای دیباگ آن توصیه میشوند (فایلهایی با نام اصلی و بدون پسوند prod یا min). زیرا این فایلها خطاها و اطلاعات بسیار مفصلی را از اشکالات رخ داده، در کنسول وب مرورگرها، فایرباگ و یا Developer tools آنها نمایش میدهند. نسخهی min برای حالت ارائهی نهایی برنامه است. نسخهی prod همان نسخهی دیباگ است با حذف اطلاعات دیباگ (نسخهی production فشرده نشده). نسخهی فشرده شدهی prod آن، فایل min نهایی را تشکیل میدهد.
- دریافت جیکوئری
- آخرین نگارش handlebars.js را از سایت رسمی آن دریافت کنید: http://handlebarsjs.com
- Ember Handlebars Loader: https://github.com/michaelrkn/ember-handlebars-loader
- Ember Data Local Storage Adapter: https://github.com/kurko/ember-localstorage-adapter
ترتیب تعریف و قرارگیری این فایلها را پس از دریافت، در فایل index.html قرار گرفته در ریشهی سایت، در کدهای ذیل مشاهده میکنید:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Ember Blog</title> <link href="Content/bootstrap.css" rel="stylesheet" /> <link href="Content/bootstrap-theme.css" rel="stylesheet" /> <link href="Content/styles.css" rel="stylesheet" /> <script src="Scripts/Libs/jquery-2.1.1.min.js" type="text/javascript"></script> <script src="Scripts/Libs/bootstrap.min.js" type="text/javascript"></script> <script src="Scripts/Libs/handlebars-v2.0.0.js" type="text/javascript"></script> <script src="Scripts/Libs/ember.js" type="text/javascript"></script> <script src="Scripts/Libs/ember-handlebars-loader-0.0.1.js" type="text/javascript"></script> <script src="Scripts/Libs/ember-data.js" type="text/javascript"></script> <script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script> </head> <body> </body> </html>
اصلاح فایل ember-handlebars-loader-0.0.1.js
اگر به فایل ember-handlebars-loader-0.0.1.js مراجعه کنید، مسیر فایلهای قالب handlebars قسمتهای مختلف برنامه را از پوشهی templates واقع در ریشهی سایت میخواند. با توجه به تصویر ساختار پوشهی پروژهی جاری، پوشهی template به داخل پوشهی Scripts منتقل شدهاست و نیاز به یک چنین اصلاحی دارد:
url: "Scripts/Templates/" + name + ".hbs",
<system.webServer> <staticContent> <mimeMap fileExtension=".hbs" mimeType="text/x-handlebars-template" /> </staticContent> </system.webServer>
مزیت استفاده از نسخهی دیباگ ember.js در حین توسعهی برنامه
نسخهی دیباگ ember.js علاوه بر به همراه داشتن خطاهای بسیار جامع و توضیح علل مشکلات، مواردی را که در آینده منسوخ خواهند شد نیز توضیح میدهد:
برای مثال در اینجا عنوان شدهاست که دیگر از linkTo استفاده نکنید و آنرا به link-to تغییر دهید.
همچنین اگر از مرورگر کروم استفاده میکنید، افزونهی Ember Inspector را نیز میتوانید نصب کنید تا اطلاعات بیشتری را از جزئیات مسیریابیهای تعریف شده و قالبهای Ember.js بتوان مشاهده کرد. این افزونه به صورت یک برگهی جدید در Developer tools آن ظاهر میشود.
ایجاد شیء Application
همانطور که در قسمتهای پیشین نیز عنوان شد (^ و ^ )، یک برنامهی Ember.js با تعریف وهلهای از شیء Application آن آغاز میشود. برای این منظور به پوشهی App مراجعه کرده و فایل جدید Scripts\App\blogger.js را اضافه کنید؛ با این محتوا:
Blogger = Ember.Application.create();
<script src="Scripts/App/blogger.js" type="text/javascript"></script>
تعاریف مسیریابی و قالبها
اکنون در ادامه قصد داریم لیستی از عناوین مطالب ارسالی را نمایش دهیم. در ابتدا این عناوین را از یک آرایهی ثابت جاوا اسکریپتی دریافت خواهیم کرد و پس از آن از یک Web API کنترلر، جهت دریافت اطلاعات از سرور کمک خواهیم گرفت.
کار router در Ember.js، نگاشت آدرس درخواستی توسط کاربر، به یک route یا مسیریابی تعریف شدهاست. به این ترتیب مدل، کنترلر و قالب آن route به صورت خودکار بارگذاری و پردازش خواهند.
با مراجعه به ریشهی سایت، فایل index.html بارگذاری میشود.
در اینجا تصویری از صفحهی آغازین بلاگ را مشاهده میکنید. در آن صفحهی posts همان ریشهی سایت نیز میباشد. بنابراین نیاز است ابتدا مسیریابی آنرا تعریف کرد. برای این منظور، فایل جدید Scripts\App\router.js را به پوشهی App اضافه کنید؛ با این محتوا:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); });
همچنین مدخل آنرا نیز در فایل index.html تعریف نمائید:
<script src="Scripts/App/blogger.js" type="text/javascript"></script> <script src="Scripts/App/router.js" type="text/javascript"></script>
افزودن مسیریابی و قالب posts
در ادامه فایل جدید Scripts\Templates\posts.hbs را اضافه کنید. به این ترتیب قالب خالی مطالب به پوشهی templates اضافه میشود. محتوای این فایل را به نحو ذیل تنظیم کنید:
<div class="container"> <h1>Emeber.js blog</h1> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div>
برای بارگذاری این قالب نیاز است آنرا به template loader توضیح داده شده در ابتدای بحث، در فایل index.html اضافه کنیم:
<script> EmberHandlebarsLoader.loadTemplates([ 'posts' ]); </script>
افزودن مسیریابی و قالب about
در ادامه قصد داریم صفحهی about را اضافه کنیم. مجددا با افزودن مسیریابی آن به فایل Scripts\App\router.js شروع میکنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); });
<h1>About Ember Blog</h1> <p>Bla bla bla!</p>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about' ]); </script>
<script type="text/x-handlebars" data-template-name="about"> </script>
برای آزمایش این مسیر و قالب جدید آن، آدرس http://localhost/#/about را بررسی کنید.
اضافه کردن منوی ثابت بالای سایت
روش اول این است که به ابتدای هر دو قالب about.hbs و posts.hbs، تعاریف منو را اضافه کنیم. مشکل اینکار، تکرار کدها و پایین آمدن قابلیت نگهداری برنامه است. روش بهتر، افزودن کدهای مشترک بین صفحات، در قالب application برنامه است. نمونهی آنرا در مثال قسمت قبل مشاهده کردهاید. در اینجا چون قصد نداریم به صورت دستی قالبها را به صفحه اضافه کنیم و همچنین برای ساده شدن نگهداری برنامه، قالبها را در فایلهای مجزایی قرار دادهایم، تنها کافی است فایل جدید Scripts\Templates\application.hbs را به پوشهی قالبهای برنامه اضافه کنیم؛ با این محتوا:
<div class='container'> <nav class='navbar navbar-default' role='navigation'> <ul class='nav navbar-nav'> <li>{{#link-to 'posts'}}Posts{{/link-to}}</li> <li>{{#link-to 'about'}}About{{/link-to}}</li> </ul> </nav> {{outlet}} </div>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application' ]); </script>
افزودن مسیریابی و قالب contact
برای افزودن صفحهی تماس با مای سایت، ابتدا مسیریابی آنرا در فایل Scripts\App\router.js تعریف میکنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact'); });
<h1>Contact</h1> <ul> <li>Phone: ...</li> <li>Email: ...</li> </ul>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact' ]); </script>
<div class='container'> <nav class='navbar navbar-default' role='navigation'> <ul class='nav navbar-nav'> <li>{{#link-to 'posts'}}Posts{{/link-to}}</li> <li>{{#link-to 'about'}}About{{/link-to}}</li> <li>{{#link-to 'contact'}}Contact{{/link-to}}</li> </ul> </nav> {{outlet}} </div>
تعریف مسیریابی تو در تو در صفحهی contact
در حال حاضر صفحهی Contact، ایمیل و شماره تماس را در همان بار اول نمایش میدهد. میخواهیم این دو را تبدیل به دو لینک جدید کنیم که با کلیک بر روی هر کدام، محتوای مرتبط، در قسمتی از همان صفحه بارگذاری شده و نمایش داده شود.
برای اینکار نیاز است مسیریابی را تو در تو تعریف کنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact', function () { this.resource('email'); this.resource('phone'); }); });
پس از آن دو فایل قالب جدید Scripts\Templates\email.hbs را با محتوای:
<h2>Email</h2> <p> <span></span> Email name@site.com. </p>
<h2>Phone</h2> <p> <span></span> Call 12345678. </p>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone' ]); </script>
<h1>Contact</h1> <div class="row"> <div class="col-md-6"> <p> Want to get in touch? <ul> <li>{{#link-to 'phone'}}Phone{{/link-to}}</li> <li>{{#link-to 'email'}}Email{{/link-to}}</li> </ul> </p> </div> <div class="col-md-6"> {{outlet}} </div> </div>
در اینجا نحوهی پردازش مسیریابی contact را ملاحظه میکنید. ابتدا درخواستی جهت مشاهدهی آدرس http://localhost/#/contact دریافت میشود. سپس router این درخواست را به مسیریابی همنامی منتقل میکند. این مسیریابی ابتدا قالب عمومی application را رندر کرده و سپس قالب اصلی و همنام مسیریابی جاری یا همان contact.hbs را رندر میکند. در این صفحه چون مسیریابی تو در تویی تعریف شدهاست، اگر درخواستی برای مشاهدهی http://localhost/#/contact/phone دریافت شود، محتوای آنرا در {{outlet}} قالب contact.hbs جاری رندر میکند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_01.zip
قبل از پاسخگویی به سؤال بالا، به یک سری مقدمات نیاز است:
وقتی یک کوئری به اس کیو ال ارسال میشود، چه اتفاقی رخ میدهد؟
وقتی یک کوئری ارسال میشود، تعدادی از پروسسها بر روی کوئری شروع به فعالیتهایی مانند مهیا نمودن دادههای بازگشتی، یا ذخیره سازی و ... میکنند.
پروسسها به دو دسته زیر تقسیم میشوند:
- پروسسهایی که در relational engine رخ میدهند
- پروسسهایی که در storage engine رخ میدهند
در
relational engine، هر کوئری pars شده و سپس بوسیله query optimizer
پردازش و پلن اجرایی (execution plan) آن که بفرمت باینری است، ایجاد میشود و
به storage engine ارسال میگردد. در storage engine پروسسهایی مانند قفل
گذاری، نگهداری ایندکسها و تراکنشها رخ میدهد. هنگامیکه اس کیو ال
سرور کوئری را دریافت مینمایند، آن را بلافاصله به relational engine ارسال
میکند. سپس نحو (syntax) آن بررسی میشود؛ این عمل query parsing نامیده
میشود. خروجی عملیات پارسر، یک ساختار درختی (query tree) است. این ساختار
درختی مشخص کننده مراحل لازم جهت اجرای کوئری ارائه شده میباشد.
اگر یک کوئری شامل DML نباشد (مانند ساخت جدول)، علمیات بهبود برروی آن صورت
نخواهد گرفت. ولی در صورتیکه کوئری ارسالی، DML باشد، درخت اشاره شده در
بالا به algebrizer فرستاده میشود که وظیفه آن تفسیر و بررسی کلیه نام
اشیاء، جداول و ستونهای اشاره شده در متن کوئری است. فرآیند algebrizer
بسیار مهم و حیاتی است؛ بدلیل اینکه در کوئری ممکن است اشاره کنندههایی به
اشیایی باشند که در بانک اطلاعاتی موجود نیست. خروجی algebrizer یک query
processor tree باینری است که به بهبود دهنده کوئری ارسال میگردد.
معرفی Query Optimizer (بهبود دهنده پرس و جو)
بهبود
دهنده، بهترین مسیر اجرای کوئری را مشخص میکند. این بهبود دهنده است که مشخص
میکند که اطلاعات بوسیله ایندکس دریافت شوند، یا اینکه از چه اتصالی استفاده
شود و الی آخر. این تصمیمات براساس محاسبات هزینههای (میزان پردازش لازم
cpu و I/O) پلن اجرایی صورت خواهد پذیرفت. بهمین دلیل به پلن cost-based نیز شناخته میشود.
هنگامیکه کوئری سادهای مانند دریافت اطلاعات از یک جدول، که بر روی آن
ایندکس گذاری انجام نشدهاست، ارسال شود، بهبود دهنده بجای مشخص نمودن یک
پلن مناسب بهینه، از یک پلن ساده (trivial) استفاده میکند. ولی برعکس در
صورتیکه کوئری trivial نباشد (یعنی مثلا کوئری به گونهای باشد که از
ایندکسها به شکل صحیحی استفاده شده باشند)، بهبود دهنده یک پلن مناسب را
براساس اطلاعات آماری مهیا شده در اس کیو ال سرور، تولید و انتخاب مینماید.
اطلاعات
آماری از ستونها و ایندکسها جمع آوری میشود. این اطلاعات شامل نحوه
توزیع داده، یکتایی و انتخاب شوندگی است. این اطلاعات توسط یک histogram
ارائه میشود. اگر اطلاعات آماری برای یک ستون و یا ایندکس وجود داشته
باشد، بهبود دهنده از آنها برای محاسبات خود استفاده خواهد کرد. اطلاعات
آماری بصورت خودکار برای تمام ایندکسها و یا هر ستونی که بشود بر روی آنها
where یا join نوشت، فراهم خواهد شد.
بهبود دهنده با مقایسه پلنها براساس بررسی تفاوتهای انواع joinها، چیدمان
مجدد ترتیب join و بررسی ایندکسهای مختلف و سایر فعالیتهای دیگر، پلن
مناسب را انتخاب و از آن استفاده میکند. در طی هر کدام از فعالیتهای
اشاره شده، زمان اجرای آنها نیز تخمین زده (estimated cost) خواهد شد و در
پایان، زمان کل تخمینی بدست خواهد آمد و بهبود دهنده از این زمان برای انتخاب
پلن مناسب بهره خواهد برد. باید توجه داشت که این زمان تقریبی است. زمانیکه بهبود دهنده پلن اجرایی انتخاب میکند، یک actual plan را ایجاد و در حافظه ذخیره
میشود؛ بنام plan cache. البته درصورتیکه پلن مشابه و بهینهتری وجود
نداشته باشد.
استفاده مجدد از پلن ها
تولید پلن هزینه بر است. بههمین دلیل اس کیوال سرور اقدام به ذخیره سازی و نگهداری آنها میکند تا بتواند از آنها مجددا استفاده نماید؛ البته تا جایی که مقدور باشد. هنگامیکه آنها تولید میشوند، در قسمتی از حافظه بنام plan cache ذخیره میشوند. به این عمل procedure cache نیز گفته میشود.
هنگامیکه کوئری به سرور ارسال میشود، بوسیله بهبود دهنده، یک estimated plan ایجاد
خواهد شد و قبل از اینکه به storage engine ارسال شود، بهبود دهنده estimated
plan را با actual execution planهای موجود در plan cache مقایسه میکند.
در صورتیکه یک actual plan را مطابق با estimated plan پیدا نماید، از آن مجدد
استفاده خواهد کرد. این استفاده مجدد به عدم تحمیل سربار اضافهای به سرور جهت
کوئریهای بزرگ و پیچیده که در زمان واحد، هزاران بار اجرا خواهند شد، منجر میشود.
هر پلن فقط یکبار در حافظه ذخیره خواهد شد. ولی در مواقعی با تشخیص
بهبود دهنده و هزینه پلن، یک کوئری میتواند پلن دیگری نیز داشته باشد.
بنابراین پلن دوم نیز با مجموعه عملیاتی متفاوت، جهت اجرای موازی (parallel
execution) برای یک کوئری ایجاد و در حافظه ذخیره میشود.
پلنهای اجرایی برای همیشه در حافظه باقی نخواهند ماند. پلنهای اجرایی دارای
طول عمری طبق فرمول حاصل ضرب هزینه، در تعداد دفعات میباشند. مثلاً پلنی با
هزینه 10 و تعداد دفعات اجرای 5، طول عمر 50 را خواهد داشت. پروسس lazywriter
که یک پروسس داخلی است وظیفه آزاد سازی تمام انواع کشها، از جمله پلن کش را دارد. این پروسس در بازههای مشخص، تمام اشیاء درون حافظه را بررسی کرده
و یک واحد از طول عمر آنها میکاهد.
در موارد زیر، یک پلن از حافظه پاک خواهد شد:
1. به حافظه بیشتری نیاز باشد
2. طول عمر پلن صفر شده باشد
جمله آخر، معمولا باعث ایجاد مشکل میشوند.
اگر optimizer تکست کوئری مشابهی را مشاهده نماید، ولی با پارامترهای متفاوت، به کش پلن مراجعه کرده و اگر در آن جا قرار داشت، از آن مجددا استفاده مینماید. این استفاده مجدد خوب است؛ اما درصورتیکه پارامتر ارسالی نال باشد چه اتفاقی رخ میدهد؟ جدول سفارشات محصول بسیار حجیم است و متاسفانه از پلنی که برای بازگشت 40 رکورد قبلا ایجاد شده، برای بازگشت این حجم بالای از رکوردها استفاده میشود که این کشنده است.
هیچ تضمینی وجود ندارد که از وقوع این اتفاق جلوگیری نمایید؛ اما میتوانید در هنگام توسعه، پروسیجر را شناسایی و نسبت به رفع آنها اقدام نمایید. ابتدا کش پلن را خالی نمایید و سپس پروسیجر را با مقادیر متفاوت، اجرا نمایید. در صورتیکه پلنهای متفاوتی مشاهده نمودید، این یک علامت هشدار است و میبایست نسبت به رفع آنها اقدام فوری نمایید.
Side Panels
در پنل HTML درکنار ارائه امکاناتی برای مشاهده و کار با تگهای صفحه ، اطلاعات و امکانات دیگری هم برای تگ انتخاب شده در قسمت NodeView وجود دارد.
در این تب استایل هایی که در حال حاظر بروی تگ انتخاب شده اعمال شده اند ، نمایش داده میشود.
در صورتی که موس را بروی مقادیر استایل هایی که جلوهی بصری دارند بگیرید ، یک پاپآپ کوچک نمایان میشود که مقدار را نمایش میدهد.
Options Menu
هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب Style هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر میشود.
- Only Show Applied Styles
در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده میشوند. (استایلهای Overwrite شده نمایش داده نمیشوند.)
(این گزینه قابلیت خوبی است ، اما چندبار برای بنده پیش آمده که این مورد به اشتباه استایلی که اعمال شده بود را هم Overwrite شده در نظر گرفته بود. پس در هین طراحی استایل و کار با CSS اگر احیانا یکی از استایل هایتان وجود نداشت و از وجود آن اطمینان داشتید ، غیرفعال کردن این گزینه را امتحان کنید.)
- Show User Agent CSS
با فعال کردن این گزینه ، استایل هایی که توسط مرورگر اعمال شده اند هم نمایش داده میشوند.
- Expand Shorthand Properties
با فعال کردن این گزینه ، استایل هایی که بصورت کوتاه شده تعریف شده اند را بصورت گسترده و باز شده نمایش میدهد.
برای مثال ، دستور margin را بصورت margin-top , margin-right , margin-bottom , margin-left نمایش میدهد.
- سه گزینه ی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کنندهی فرمت نمایش رنگها هستند.
- سه گزینه ی :active ، :hover و :focus هم برای تعیین کلاس کاذب برای تگ جاری کاربرد دارند.
برای مثال شما میخواهید استایلی که یک لینک زمان موس برویش قرار دارد را بررسی کنید ، لینک را در NodeView انتخاب میکنید و سپس از گزینهی :hover را فعال میکنید.
Panel
- Element styles
استایل هایی که بصورت inline (در خود تگ) تعریف شده اند هم در این قسمت نمایش داده میشود و نام rule آن element.style است.
- Source Links
در بالا-راست هر بخش ، یک لینک قرار دارد که لینک فایل استایلی است که در همان قسمت وجود دارد و عددی که در پرانتز قرار دارد ، شماره خط استایل در همان فایل است.
اگر نام فایل با نام صفحهی جاری برابر باشد ، به معنی وجود استایل در تگ <style> در صفحهی جاری است و شمارهی بعد از # هم ایندکس تگ <style> است.
(با کلیک بروی لینک فایل ، فایل در خط مورد نظر در پنل CSS نمایش داده میشود.)
- Inherited rules
ruleهای به ارث رسیده هم در قسمتهای جداگانه به همراه استایلهای به ارث رسیده نمایش داده میشود و تگی والد که استایلها از آن به ارث رسیده اند هم در قسمت عنوان همان استایلها نمایش قرار داده شده است. (با کلیک بروی آن ، در قسمت Nodeview انتخاب میشود.)
- User agent rules
استایل هایی که توسط مرورگر اعمال شده اند (User agent rules) ، با عبارت <System> در زیر لینک منبع استایل ، مشخص شده اند.
- Overwritten styles
استایلهای overwrite شده ، با یک خط برویشان مشخص شده اند.
- Inline editing
استایلهای نمایش داده شده در این پنل را براحتی و با کلیک کردن بروی نام یا مقدار هر یک از دستورات میتوانید تغییر دهید.
برای نوشتن دستورات و مقادیر آنها میتوانید از پیشنهادهای فایرباگ هم کمک بگیرید و با دکمههای Arrow Up و Arrow Down هم بین مقادیر مجاز حرکت کنید.
دستورات یا مقادیر نا صحیح در هین تایپ ، با رنگ قرمز و مقادیر صحیح با رنگ سبز مشخص میشوند.
(این امکان خیلی مفید است ، برای مثال میخواهید فونتهای مختلف را برای یک استایل امتحان کنید ، دستور font-family را مینویسید و بعد از زدن Enter ، با دکمه های Arrow Up و Arrow Down در لحظه نتیجهی اعمال فونتهای مختلف و دردسترس را مشاهده میکنید و بهترین را بر میگزینید.
یا برای یافتن بهترین مقدار margin ، بعد از دستور margin ، زدن کلید Enter ، وارد کردن یک عدد برای شروع ، میتوان باز هم با دکمه های Arrow Up و Arrow Down به سرعت تغییر را در صفحه مشاهده کرد.)
- Rendered font highlighted
برای دستور font ، فایرباگ هوشمندانه عمل کرده و فونتی که در حال استفاده است را پررنگ میکند.
این امکان برای یافتن خطاهای متداول هنگام تعریف فونتهای غیر سیستمی ، بسیار مفید است.
Context Menu
این منو زمانی که در پنل راست کلیک کنید ظاهر میشود و نسبت به منطقه (Context)ای که در آن راست کلیک کرده اید ، گزینههای متفاوتی را مشاهده خواهید کرد. در جدول زیر ، گزینهها ، Contextشان و توضیح هر گزینه آمده است.
گزینه | Context | توضیحات |
Copy Rule Declaration | CSS selector | CSS Rule فعلی را به همراه استایل هایش در clipboard کپی میکند |
Copy Style Declaration | CSS selector | استایلهای CSS Rule فعلی را در clipboard کپی میکند |
Copy Location | source link | آدرس فایل تعریف CSS Rule را در clipboard کپی میکند |
Open in New Tab | source link | آدرس فایل تعریف CSS Rule را در تب جدید باز میکند |
Edit Element Style... | everywhere | امکان تعریف استایلهای درون تگ (inline) را محیا میکند |
Add Rule... | everywhere | یک Rule جدید ایجاد میکند CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد میدهد |
Delete "<rule name>" | CSS selector | CSS Rule فعلی را حذف میکند |
New Property... | CSS rule | یک استایل جدید به CSS Rule فعلی اضافه میکند |
Edit "<property name>"... | CSS property | Property فعلی را به حالت ویرایش میبرد راه دیگر ویرایش Property ، کلیک بروی آن است |
Delete "<property name>" | CSS property | Property فعلی را حذف میکند |
Disable "<property name>" | CSS property | Property فعلی را غیر فعال میکند را سریعتر ، کلیک کردن در ناحیهی پشت Property ، بروی علامت قرمز رنگ است |
Refresh | everywhere | محتویات پنل را بروز میکند |
Inspect in DOM Panel |
CSS rule | CSS Rule فعلی را در پنل DOM برای بررسی باز میکند |
Inspect in CSS Panel | CSS rule | CSS Rule فعلی را در پنل CSS برای بررسی باز میکند |
<Default Editor Name> | CSS rule | فایل تعریف استایلها را در ادیتور تعریف شده باز میکند (این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد) |
2 - Computed
دراین تب نتیجهی پردازش استایلهای ارائه شده توسط کاربر ، برای تگ مشخص شده در قسمت NodeView نمایش داده میشود. (مقادیر استایل هایی که در نهایت بروی تگ اعمال شده اند.)
Style Tracing
برای ردیابی استایلها ، استایلها به ترتیب اعمال شدنشان مرتب شده اند و اولین مقدار ، مقداری است که اعمال شده است.
مقادیر Overwrite بصورت خط کشیده شده و استایلهای Overwrite شده بصورت خاکستری-کمرنگ نمایش داده میشوند.
هر استایل هم مانند تب Style ، یک لینک به منبع خود دارد.
Options Menu
- Show User Agent CSS
در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده میشوند.
- Sort alphabetically
در صورت انتخاب ، استایلها به ترتیب الفبا ، و درصورت عدم انتخاب بصورت گروه بندی نمایش داده میشوند.
- Show Mozilla Specific Styles
در صورت انتخاب ، استایلهای مخصوص Mozilla را نمایش میدهد. (استایل هایی با پیشوند -moz-)
- سه گزینهی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کنندهی فرمت نمایش رنگها هستند.
Context Menu
این منو زمانی که در پنل راست کلیک کنید ظاهر میشود و نسبت به منطقه (Context)ای که در آن راست کلیک کرده اید ، گزینههای متفاوتی را مشاهده خواهید کرد. در جدول زیر ، گزینهها ، Contextشان و توضیح هر گزینه آمده است.
گزینه | Context | توضیحات |
Expand All Styles | everywhere | CSS Rule فعلی را به همراه استایل هایش در clipboard کپی میکند |
Collapse All Styles | everywhere | استایلهای CSS Rule فعلی را در clipboard کپی میکند |
Inspect in DOM panel | styles | آدرس فایل تعریف CSS Rule را در تب جدید باز میکند |
Copy Location |
style source link | امکان تعریف استایلهای درون تگ (inline) را محیا میکند |
Open in New Tab | style source link | یک Rule جدید ایجاد میکند CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد میدهد |
Inspect in CSS panel | style source link | CSS Rule فعلی را حذف میکند |
<Default Editor Name> | style source link | فایل تعریف استایلها را در ادیتور تعریف شده باز میکند (این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد) |
3 - Layout
در این تب ، مقادیر Box Model بصورت بصری نمایش میدهد. میتوان با کلیک کردن بروی هریک از مقادیر ، آن را ویرایش کرد. (این تغییر بصورت inline در تگ اعمال میشود.)
با حرکت موس بروی قسمتهای مختلف ، میتوان همان قسمتها را در صفحه بصورت خط کشی شده مشاهده کرد.
(البته ظاهرا در ورژن 1.10.4 که بنده استفاده میکنم ، عملیات ویرایش مقادیر به درستی انجام نمیشود.)
Options Menu
- Show Rulers and Guides
در صورت انتخاب ، خطهای راهنما را هنگام حرکت موس بروی اجزای Box Model در صفحه نمایش میدهد.
4 - DOM
این پنل اطلاعات DOM تگ جاری را نمایش میدهد.
این پنل تمام قابلیتهای پنل DOM اصلی را دارا میباشد.
(در مقالات آینده با تب DOM آشنا خواهیم شد.)
مطلبی چندی قبل در مورد "ذخیره سازی فایلها در دیتابیس یا استفاده از فایل سیستم متداول؟" منتشر گردید، جهت برشمردن فواید ذخیره سازی فایلها در دیتابیس (+). اما معایب این نوع ذخیره سازی بررسی نشدند:
الف) اختصاص یافتن قسمتی از بافر SQL Server به این امر.
ب) با توجه به قرار گرفتن دادههای BLOB در دیتابیس ، transaction log قابل توجهی تولید خواهد شد. (+)
ج) بیش از 2GB را نمیتوان در فیلدهایی از نوع varbinary(max) ذخیره کرد.
د) به روز رسانی BLOB ها سبب ایجاد fragmentation میشود.
مایکروسافت برای رفع این مشکلات در SQL Server 2008 قابلیت جدیدی را ارائه داده است به نام FileStream که در طی مقالاتی به بررسی آن خواهیم پرداخت.
FILESTREAM موتور دیتابیس اس کیوال سرور را با سیستم فایل NTFS یکپارچه میکند؛ به این صورت که دادههای BLOB از نوع varbinary(max) را به صورت فایل بر روی سیستم ذخیره خواهد کرد. سپس با استفاده از دستورات T-SQL میتوان این فایلها را ثبت، حذف، به روز رسانی، جستجو و بک آپ گیری کرد. این قابلیت نیز از فیلدهای varbinary(max) استفاده میکند؛ اما اکنون ویژگی و برچسب FILESTREAM به این نوع فیلدها الصاق خواهد شد. FILESTREAM data باید در FILESTREAM filegroups ذخیره شوند. FILESTREAM filegroups در حقیقت همان پوشههای فایل سیستم میباشند. به آنها data containers نیز گفته میشوند که مرزی هستند بین ذخیره سازی دادهها در فایل سیستم و در دیتابیس.
مزایای سیستم FileStream چیست؟
الف) سیستم transaction مختص به خود را داشته، به همین جهت سبب رشد غیر منطقی حجم فایل transaction log دیتابیس اصلی نمیشوند.
ب) هنگام به روز رسانی فیلدهایی از این دست، صرفا ایجاد یا حذف یک فایل مد نظر است؛ بنابراین fragmentation ایجاد شده در این حالت بسیار کمتر از روش استفاده از فیلدهایی از نوع varbinary(max) میباشد.
ج) استفاده از NT system cache جهت کش کردن اطلاعات که سبب بالا بردن بازدهی بانک اطلاعاتی خواهد شد.
د) از buffer pool اس کیوال سرور در این حالت استفاده نشده (مطابق قسمت ج) و این حافظه جهت امور روزمرهی اس کیوال سرور کاملا مهیا خواهد بود.
ه) محدودیت 2GB فیلدهایی از نوع varbinary(max) با توجه به ذخیره سازی این نوع BLOBs در فایل سیستم، دیگر وجود نخواهد داشت.
چه زمانی بهتر است از FileStream استفاده شود؟
الف) فایلهایی که ذخیره میشوند به طور متوسط بیش از یک مگابایت حجم داشته باشند. (برای کمتر از این مقدار varbinary(max) BLOBs کارآیی بهتری را ارائه میدهند). هر چند این مرز یک مگابایت مطابق اطلاعات books online است اما تجربیات کاری نشان میدهند که این سقف را باید 256 کیلوبایت درنظر گرفت.
ب) قابلیت خواندن سریع اطلاعات فایلها مد نظر باشد (بررسی کارآیی مطابق تصویر زیر از MSDN). سیستم NTFS نسبت به SQL Server در خواندن فایلهای حجیم سریعتر عمل میکند.
ج) اگر از یک معماری middle tier در برنامههای خود در حال استفادهاید.
د) زمانیکه نیاز باشد تا اطلاعات relational و non-relational در یک تراکنش مورد استفاده قرار گیرند.
نکاتی را که باید هنگام ذخیره سازی اطلاعات در FileStream در نظر داشت
الف) هنگامی که یک جدول حاوی فیلدی از نوع FileStream میباشد، باید دارای فیلد ID منحصربفرد نیز باشد.
ب) data containers ایی که پیش از این در مورد آنها صحبت شد، نباید تو در تو باشند.
ج) FILESTREAM filegroups بر روی درایوهای فشرده شده نیز میتوانند قرار داشته باشند.
FileStream از دیدگاه امنیت
امنیت دادههای FileStream در اس کیوال سرور دقیقا همانند امنیت سایر اطلاعات ذخیره شده در دیتابیس است (دسترسی در حد جدول و یا فیلد). اگر کاربری دسترسی به فیلد FileStream در یک جدول داشته باشد، میتواند آن فایل را گشوده و استفاده کند. رمزنگاری بر روی این ستونها پشتیبانی نمیشود. تنها اکانتی که اس کیوال سرور تحت آن در حال اجرا است دسترسی به FILESTREAM container دارد. همچنین توصیه شده است که به هیچ اکانت دیگری این دسترسی داده نشود. زمانیکه یک دیتابیس آغاز و مشغول به کار میشود، اس کیوال سرور دسترسی به FILESTREAM data container را محدود خواهد کرد و دسترسی به این اطلاعات تنها از طریق دستورات T-SQL و یا OpenSqlFilestream API میسر خواهد بود. بدیهی است زمانیکه اس کیوال سرور متوقف شود، این اطلاعات بدون هیچگونه محدودیتی قابل دسترسی بوده و تنها محدودیتهای سیستمی به آنها اعمال خواهند شد (که این مورد باید مد نظر باشد).
نگهداری FileStream
FileStream به صورت فیلدهای varbinary(max) یکپارچه با دیتابیس ذخیره میشود؛ بنابراین نحوهی تهیه پشتیبان از آنها همانند روشهای متداول است بدون هیچگونه تغییری (و این اطلاعات در بک آپ دیتابیس لحاظ میشوند). اگر نیاز بود هنگام تهیه پشتیبان از این نوع دادهها بک آپ گرفته نشود، میتوان از partial backup با پارامترهای مربوطه استفاده کرد.
ادامه دارد ...
- https://github.com/loresoft/EntityFramework.Extended دقیقا شبیه به پیاده سازی مثال جاری هست. فقط cache provider اختصاصی ایجاد کرده (بجای استفاده از HttpRuntime.Cache).
- https://efcache.codeplex.com داخل سیستم Data Reader میشود برای کش کردن. (جهت ایده دادن به تیم EF خوب است)
- https://github.com/osjoberg/LinqCache نمونهی توسعه یافته مثال جاری است.
عموما بر روی سرورهای برنامههای وب، نرم افزار خاصی نصب نمیشود. برای مثال اگر نیاز به تولید فایل اکسل بر روی سرور باشد، سرور دار بعید است که آفیس را برای شما نصب کند و همچنین مایکروسافت هم این یک مورد را اصلا توصیه و پشتیبانی نمیکند (ایجاد چندین وهله از برنامه آفیس (تعامل با اشیاء COM) بر روی سرور توسط یک برنامهی وب چند کاربره).
اگر سایتها را هم جستجو کنید پر است از مقالاتی مانند تبدیل GridView به اکسل ... که تنها هنر آنها انتخاب قسمت table مانند GridView و رندر کردن آن در مرورگر با پسوندی به نام xls یا xlsx است. به عبارتی فایل نهایی تولید شده استاندارد نیست. فقط یک html table است با پسوند xls/xlsx که برنامهی اکسل میداند به چه صورتی باید آنرا باز کند (که گاها در این بین فارسی سازی آن مشکل ساز میشود). این فایل نهایی تولیدی عاری است از امکانات پیشرفته و حرفهای اکسل. برای مثال اضافه کردن فرمول به آن، تبدیل اطلاعات به نمودارهای اکسل به صورت خودکار، داشتن فایلی با چندین work sheet مختلف، اعمال قالبهای مختلف، صفحه بندی بهتر و غیره.
مایکروسافت از سال 2007 تولید فایلهای آفیس را با معرفی استاندارد OpenXML که توسط مؤسسه ایزو هم پذیرفته شده، بسیار سادهتر کرده است. OpenXML SDK در دسترس است و توسط آن میتوان فایلهای اکسل حرفهای را بدون نیاز به نصب مجموعهی آفیس تولید کرد. کار کردن با OpenXML SDK هم در نگاه اول شاید ساده به نظر برسد اما آن هم ریزه کاریهای خاص خودش را دارد که نمونهای از آنرا در مطلب "تولید فایل Word بدون نصب MS Word بر روی سرور" میتوانید مشاهده کنید. به عبارتی این مجموعه جهت نوشتن کتابخانههای ویژهی شما باز است ...
در این بین یکی از حرفهایترین کتابخانههایی که امکانات تولید فایلهای اکسل را به کمک OpenXML SDK سهولت میبخشد، کتابخانهی سورس باز EPPlus است:
مثالی در مورد نحوهی استفاده از آن:
میخواهیم یک DataTable را به یک فایل اکسل واقعی (نه یک html table با پسوند xlsx) تبدیل کنیم با این شرایط که یکی از قالبهای جدید آفیس به آن اعمال شود؛ جمع کل یکی از ستونها توسط اکسل محاسبه گردیده و همچنین عرض دقیق ستونها نیز در برنامه تنظیم گردد. نموداری نیز به صورت خودکار این اطلاعات را نمایش دهد:
using System.Data;
using System.IO;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Style;
using OfficeOpenXml.Table;
namespace EPPlusTest
{
class Program
{
static void Main(string[] args)
{
var newFile = new FileInfo("Test.xlsx");
if (newFile.Exists)
{
newFile.Delete();
}
//ایجاد یک سری اطلاعات دلخواه
var table = createDt();
using (var package = new ExcelPackage(newFile))
{
// اضافه کردن یک ورک شیت جدید
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("مخارج");
//اضافه کردن یک جدول جدید از دیتاتیبل دریافتی
worksheet.Cells["A1"].LoadFromDataTable(table, true, TableStyles.Dark9);
//نمایش جمع ستون هزینههای ماهها
var tbl = worksheet.Tables[0];
//زیر آخرین ردیف یک سطر اضافه میکند
tbl.ShowTotal = true;
//فرمول نحوهی محاسبه جمع ستون انتساب داده میشود
tbl.Columns[1].TotalsRowFunction = RowFunctions.Sum;
//تعیین عرض ستونهای جدول
worksheet.Column(1).Width = 14;
worksheet.Column(2).Width = 12;
//تنظیم متن هدر
worksheet.HeaderFooter.oddHeader.CenteredText = "مثالی از نحوهی استفاده از ایی پی پلاس";
//میخواهیم سرستونها در وسط ستون قرار گیرند
worksheet.Cells["A1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells["B1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
//افزودن یک نمودار جدید به شیت جاری
var chart = worksheet.Drawings.AddChart("chart1", eChartType.Pie3D);
chart.Title.Text = "نمودار هزینههای سال";
chart.SetPosition(Row: 2, RowOffsetPixels: 5, Column: 3, ColumnOffsetPixels: 5);
chart.SetSize(PixelWidth: 320, PixelHeight: 360);
chart.Series.Add("B2:B13", "A2:A13");
chart.Style = eChartStyle.Style26;
//تنظیم یک سری خواص فایل نهایی
package.Workbook.Properties.Title = "مثالی از ایی پی پلاس";
package.Workbook.Properties.Author = "وحید";
package.Workbook.Properties.Subject = "ایجاد فایل اکسل بدون نرم افزار اکسل";
//تنظیم نحوهی نمایش فایل زمانیکه در نرم افزار اکسل گشوده میشود
worksheet.View.PageLayoutView = true;
worksheet.View.RightToLeft = true;
// ذخیر سازی کلیه موارد اعمالی در فایل
package.Save();
}
}
private static DataTable createDt()
{
var table = new DataTable("مخارج");
table.Columns.Add("ماه", typeof(string));
table.Columns.Add("هزینه", typeof(decimal));
table.Rows.Add("فروردین", 100);
table.Rows.Add("اردیبهشت", 250);
table.Rows.Add("خرداد", 80);
table.Rows.Add("تیر", 300);
table.Rows.Add("مرداد", 200);
table.Rows.Add("شهریور", 150);
table.Rows.Add("مهر", 250);
table.Rows.Add("آبان", 200);
table.Rows.Add("آذر", 400);
table.Rows.Add("دی", 100);
table.Rows.Add("بهمن", 130);
table.Rows.Add("اسفند", 80);
return table;
}
}
}
در ادامهی مطلب قبلی، نکاتی دیگر را جهت افزایش کارآیی سیستمهای مبتنی بر EF اشاره خواهیم کرد:
عدم استفاده از کوئریهای کلی
فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر میتواند بر اساس آنها جستجو را انجام دهد.
var searchModel = new Pupil { FirstName = "Ben", LastName = null, City = null, PostalZipCode = null }; List<Pupil> pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList()
USE [EFSchoolSystem] DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben' DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben' DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = '' DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = '' DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = '' DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = '' DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = '' DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = '' -- Executed query SELECT TOP (100) [Extent1].[PupilId] AS [PupilId] , [Extent1].[FirstName] AS [FirstName] , [Extent1].[LastName] AS [LastName] , [Extent1].[Address1] AS [Address1] , [Extent1].[Adderss2] AS [Adderss2] , [Extent1].[PostalZipCode] AS [PostalZipCode] , [Extent1].[City] AS [City] , [Extent1].[PhoneNumber] AS [PhoneNumber] , [Extent1].[SchoolId] AS [SchoolId] , [Extent1].[Picture] AS [Picture] FROM [dbo].[Pupils] AS [Extent1] WHERE (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1) AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3) AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5) AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
مشکلی که در این دسته از کوئریهای عمومی ایجاد میگردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی میکنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffing شناخته میشود و دربارهی Parameter Sniffing در اینجا به تفصیل اشاره شده است.
این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آنها در شهر بوستون زندگی میکنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.
راه حل اول:
برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
if (searchModel.City == null) { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); } else { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); }
راه حل دوم:
کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل میکند. بدین منظور مترجم زیر را ایجاد کنید:
public class RecompileDbCommandInterceptor : IDbCommandInterceptor { public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if(!command.CommandText.EndsWith(" option(recompile)")) { command.CommandText += " option(recompile)"; } } //and implement other interface members }
var interceptor = new RecompileDbCommandInterceptor(); DbInterception.Add(interceptor); var pupils = db.Pupils.Where(p => p.City = city).ToList(); DbInterception.Remove(interceptor);
راه حل سوم:
استفاده از اجرای به تعویق افتاده به شکل زیر است:
var result = db.Pupils.AsQueryable(); if(searchModel.FirstName != null ) result = result.Where(p => p.FirstName == searchModel.FirstName); if(searchModel.LastName != null ) result = result.Where(p => p.LastName == searchModel.LastName); if(searchModel.PostalZipCode != null ) result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode); if(searchModel.City != null ) result = result.Where(p => p.City == searchModel.City);
افزونگی کشِ پلن
استفادهی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفادهی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئریهای پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئریها پارامتریک تولید میکند؛ اما یک استثناء وجود دارد: ()Skip و ()Take
2 متد فوق بیشتر جهت صفحه بندی استفاده میشوند:
var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(model.Page * model.ResultsPerPage) .Take(model.ResultsPerPage) .ToList();
حال اگر قصد دریافت اطلاعات صفحهی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده میشوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلنها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.
نکته: جهت مشاهده پلنهای کش شده در اس کیو ال، دستور زیر اجرا کنید:
SELECT text, query_plan FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_query_plan(plan_handle) CROSS APPLY sys.dm_exec_sql_text(plan_handle)
- صدمه به کارآیی؛ هربار EF یک کوئری و اس کیو ال یک پلن جدید را ایجاد میکنند.
- افزایش اشغال حافظه؛ کش شدن کوئریها توسط EF سمت کلاینت و کش شدن پلنها در اس کیو ال سرور (کش بی رویهی پلنها باعث حذف سایر پلنهای مورد استفاده بدلیل محدودیت حافظه میشود که امکان بروز اختلال در کارآیی را بههمراه خواهد داشت.)
علت بروز مشکل:
هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال میشود، EF نمیتواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمیگیرد.
راه حل:
در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق میتوان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئریهای پارامتریک خواهد شد.
int resultsToSkip = model.Page * model.ResultsPerPage; var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(() => resultsToSkip) //must pre-calculate this value .Take(() => model.ResultsPerPage) .ToList();
همانطور که مشاهده میکنید این بار EF کوئری پارامتریک ایجاد و ارسال کرده است.
روشهای مختلف تعریف خاصیت template در یک کامپوننت
در قسمت قبل، روش تعریف inline یک template را مشاهده کردید:
template:` <div><h1>{{pageTitle}}</h1> <div>My First Component</div> </div> `
هر چند این روش تعریف قالبها، مزیت سادگی و امکان مشاهدهی View را به همراه کدهای مرتبط با آن، در یک فایل میسر میکند، اما به دلیل رشتهای بودن، مزیت کار کردن با ادیتورهای وب، مانند داشتن intellisense، فرمت خودکار کدها و بررسی syntax را از دست خواهیم داد و با بیشتر شدن حجم این رشته، این مشکلات بیشتر نمایان خواهند شد.
به همین جهت قابلیت دیگری به نام linked template نیز در اینجا درنظر گرفته شدهاست:
templateUrl: 'product-list.component.html'
ساخت کامپوننت نمایش لیست محصولات
در ادامه میخواهیم کامپوننتی را طراحی کنیم که آرایهای از محصولات را نمایش میدهد. در اینجا مرسوم است هر ویژگی برنامه، در یک پوشهی مجزا قرار گیرد. به همین جهت در ادامهی مثال قسمت قبل که پوشهی app را به ریشهی پروژه اضافه کردیم و سپس main.ts راه انداز و کامپوننت ریشهی سایت یا app.component.ts را در آن تعریف کردیم، در داخل همین پوشهی app، پوشهی جدیدی را به نام products اضافه میکنیم. سپس به این پوشهی جدید محصولات، فایل جدیدی را به نام product-list.component.html اضافه کنید. از این فایل جهت تعریف قالب کامپوننت لیست محصولات استفاده خواهیم کرد. در اینجا نیز مرسوم است نام قالب یک Component را به صورت نام ویژگی ختم شدهی به کلمهی Component، با پسوند html تعریف کنیم.
پس از اضافه شدن فایل product-list.component.html، محتوای آنرا به نحو ذیل تغییر دهید:
<div class='panel panel-default'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-body'> <div class='row'> <div class='col-md-2'>Filter by:</div> <div class='col-md-4'> <input type='text' /> </div> </div> <div class='row'> <div class='col-md-6'> <h3>Filtered by: </h3> </div> </div> <div class='table-responsive'> <table class='table'> <thead> <tr> <th> <button class='btn btn-primary'> Show Image </button> </th> <th>Product</th> <th>Code</th> <th>Available</th> <th>Price</th> <th>5 Star Rating</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> </div>
تنها نکتهی AngularJS 2.0 قالب فوق، اتصال به pageTitle است که نمونهای از آنرا در قسمت قبل با معرفی اولین کامپوننت مشاهده کردهاید.
در ادامه نیاز است برای این قالب و view، یک کامپوننت را طراحی کنیم که متشکل است از یک کلاس TypeScript ایی مزین شده به Component. بنابراین فایل ts جدیدی را به نام product-list.component.ts به پوشهی App\products اضافه کنید؛ با این محتوا:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html' }) export class ProductListComponent { pageTitle: string = 'Product List'; }
با جزئیات نحوهی تعریف یک کامپوننت در قسمت قبل در حین معرفی کامپوننتها آشنا شدیم. در اینجا کلاس ProductListComponent با واژهی کلیدی export همراه است تا توسط module loader برنامه قابلیت بارگذاری را پیدا کند. همچنین خاصیت عمومی pageTitle نیز در آن تعریف شدهاست تا در قالب مرتبط مورد استفاده قرار گیرد.
سپس این کلاس، با decorator ویژهای به نام Component مزین شدهاست تا AngularJS 2.0 بداند که هدف از تعریف آن، ایجاد یک کامپوننت جدید است. مقدار selector آن که تشکیل دهندهی یک تگ HTML سفارشی متناظر با آن خواهد شد، به pm-products تنظیم شدهاست و اینبار بجای تعریف inline قالب آن به صورت یک رشته، از خاصیت templateUrl جهت معرفی مسیر فایل html قالبی که پیشتر آماده کردیم، کمک گرفته شدهاست.
نمایش کامپوننت لیست محصولات در صفحهی اصلی سایت
خوب، تا اینجا یک کامپوننت جدید را به نام لیست محصولات، ایجاد کردیم؛ اما چگونه باید آنرا نمایش دهیم؟
در قسمت قبل که کامپوننت ریشهی برنامه یا AppComponent را تعریف کردیم، نام selector آن را pm-app درنظر گرفتیم و در نهایت این directive سفارشی را به نحو ذیل در body صفحهی اصلی سایت نمایش دادیم:
<div> @RenderBody() <pm-app>Loading App...</pm-app> </div>
الف) تگ سفارشی این دایرکتیو جدید را به کامپوننت ریشهی سایت یا همان AppComponent اضافه میکنیم. بنابراین فایل app.component.ts را گشوده و سپس selector کامپوننت لیست محصولات را به قالب آن اضافه کنید:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-app', template:` <div><h1>{{pageTitle}}</h1> <pm-products></pm-products> </div> ` }) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
ب) تا اینجا یک دایرکتیو جدید را به نام pm-products به یک کامپوننت دیگر اضافه کردهایم. اما این کامپوننت نمیداند که اطلاعات آنرا باید از کجا تامین کند. برای این منظور خاصیت جدیدی را به نام directives به لیست خاصیتهای Component ریشهی سایت اضافه میکنیم. این خاصیت، آرایهای از دایرکتیوهای سفارشی را قبول میکند:
directives: [ProductListComponent]
import { Component } from 'angular2/core'; import { ProductListComponent } from './products/product-list.component'; @Component({ selector: 'pm-app', template:` <div><h1>{{pageTitle}}</h1> <pm-products></pm-products> </div> `, directives: [ProductListComponent] }) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
این سه مرحله، مراحلی هستند که جهت افزودن هر دایرکتیو جدید به کامپوننتی مشخص، باید طی شوند.
خوب، اکنون اگر برنامه را اجرا کنیم، چنین خروجی را میتوان مشاهده کرد:
یک نکته
اگر برنامه را اجرا کردید و خروجی را مشاهده نکردید، مطمئن شوید که فایلهای ts شما کامپایل شدهاند. فشردن دکمهی ctrl+s مجدد در این فایلها، سبب کامپایل مجدد آنها میشوند و یا انتخاب گزینهی Build و سپس ReBuild solution نیز همینکار را انجام میدهد.
غنی سازی کامپوننتهای AngularJS 2.0 با data-binding
در AngularJS 2.0 عملیات binding، کار مدیریت ارتباطات بین یک کلاس کامپوننت و قالب آنرا انجام میدهد. نمونهای از آنرا پیشتر با خاصیت pageTitle و سپس نمایش آن در قالب کامپوننت متناظر با آن کلاس، مشاهده کردهاید. همچنین در اینجا یک قالب میتواند متدهای داخل کلاس کامپوننت خود را توسط رخدادها نیز فراخوانی کند.
به نحوهی نمایش {{pageTitle}} اصطلاحا interpolation میگویند. در اینجا خاصیت pageTitle اطلاعات خود را از کلاس کامپوننت دریافت میکند. به این نوع binding، انقیاد یک طرفه یا one-way binding نیز گفته میشوند؛ از خاصیت کلاس شروع شده و به قالب خاتمه مییابد.
ویژگی interpolation فراتر است از صرفا نمایش یک خاصیت و میتواند حاوی محاسبات نیز باشد:
{{'Title: ' + pageTitle}} {{2*20+1}}
{{'Title: ' + getTitle()}}
<h1>{{pageTitle}}</h1>
<h1 innerText={{pageTitle}}></h1>
بنابراین به صورت خلاصه هر زمانیکه نیاز به نمایش اطلاعات فقط خواندنی (one-way binding) داریم، ابتدا خاصیتی را در کلاس کامپوننت تعریف کرده و سپس مقدار این خاصیت را توسط interpolation، در قالب کامپوننت درج میکنیم. حین استفاده از interpolation نیازی به ذکر "" نیست.
در مورد مباحث تکمیلی binding در قسمتهای بعدی بیشتر بحث خواهیم کرد.
افزودن منطقی سفارشی به قالب یک کامپوننت
دایرکتیوها به صورت المانها و یا ویژگیهای سفارشی HTML، قابلیت توسعهی امکانات پیش فرض آنرا دارند. در اینجا میتوان دایرکتیوهای سفارشی خود را تولید کرد (مانند pm-products فوق) و یا از دایرکتیوهای توکار AngularJS 2.0 استفاده کرد. برای مثال ngIf* و ngFor* جزو structural directives توکار AngularJS 2.0 هستند. ستارهای که پیش از نام این دایرکتیوها قرار گرفتهاست، آنها را در گروه structural directives قرار میدهد.
کار دایرکتیوهای ساختاری، تغییر ساختار یا همان view کامپوننتها است؛ با افزودن، حذف و یا تغییر المانهای HTML تعریف شدهی در صفحه.
بررسی ngIf*
فایل قالب product-list.component.html را گشوده و تعریف جدول آنرا به نحو ذیل تغییر دهید:
<table class='table' *ngIf='products && products.length'>
برای نمونه عبارت انتساب داده شدهی به ngIf* در مثال فوق به این معنا است که اگر خاصیت و آرایهی products در کلاس کامپوننت این قالب تعریف شده بود و همچنین دارای اعضایی نیز بود، آنگاه این جدول را نمایش بده.
برای آزمایش آن، فایل product-list.component.ts را گشوده و خاصیت عمومی آرایهی products را به نحو ذیل به آن اضافه کنید:
import { Component } from 'angular2/core'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html' }) export class ProductListComponent { pageTitle: string = 'Product List'; products: any[] = [ { "productId": 2, "productName": "Garden Cart", "productCode": "GDN-0023", "releaseDate": "March 18, 2016", "description": "15 gallon capacity rolling garden cart", "price": 32.99, "starRating": 4.2, "imageUrl": "app/assets/images/garden_cart.png" }, { "productId": 5, "productName": "Hammer", "productCode": "TBX-0048", "releaseDate": "May 21, 2016", "description": "Curved claw steel hammer", "price": 8.9, "starRating": 4.8, "imageUrl": "app/assets/images/rejon_Hammer.png" } ]; }
همچنین فعلا در اینجا اطلاعات را بجای دریافت از سرور، توسط آرایهی مشخصی از اشیاء تعریف کردهایم. این موارد را در قسمتهای بعدی بهبود خواهیم بخشید.
اکنون که خاصیت عمومی products تعریف شدهاست، امکان استفادهی از ngIf* ایی که پیشتر تعریف کردیم، میسر شدهاست. در این حالت اگر برنامه را اجرا کنید، قسمت table header تصویر قبلی نمایش سایت، هنوز نمایان است. یعنی ngIf* تعریف شده کار میکند؛ چون خاصیت products تعریف شدهاست و همچنین دارای اعضایی است.
برای آزمایش بیشتر، خاصیت products را کامنت کنید و یکبار نیز فایل ts آنرا ذخیره کنید تا فایل js متناظر با آن کامپایل شود. سپس مجددا برنامه را اجرا کنید. در این حالت دیگر نباید هدر جدول نمایان باشد؛ چون products تعریف نشدهاست.
بررسی ngFor*
تا اینجا بر اساس داشتن لیستی از محصولات یا عدم آن، جدول متناظری را نمایش داده و یا مخفی کردیم. اما این جدول هنوز فاقد ردیفهای نمایش اعضای آرایهی products است.
برای این منظور مجددا فایل قالب product-list.component.html را گشوده و سپس بدنهی جدول را به نحو ذیل تکمیل کنید:
<tbody> <tr *ngFor='#product of products'> <td></td> <td>{{ product.productName }}</td> <td>{{ product.productCode }}</td> <td>{{ product.releaseDate }}</td> <td>{{ product.price }}</td> <td>{{ product.starRating }}</td> </tr> </tbody>
بنابراین ابتدا قسمتی از عناصر HTML را طوری کنار هم قرار میدهیم که جمع آنها یک تک آیتم را تشکیل دهند. سپس با استفاده از ngFor* به AngularJS 2.0 اعلام میکنیم که این قطعه را به ازای عناصر لیست دریافتی، تکرار و رندر کند.
برای نمونه در مثال فوق میخواهیم ردیفهای جدول تکرار شوند. بنابراین هر ردیف را به عنوان یک قطعهی تکرار شوندهی توسط ngFor* مشخص میکنیم. به این ترتیب این ردیف و عناصر فرزند آن، به ازای تک تک محصولات موجود در آرایهی products، تکرار خواهند شد.
علامت # در اینجا (product#) یک متغیر محلی را تعریف میکند که تنها در قالب جاری قابل استفاده خواهد بود و همچنین فقط در فرزندان tr تعریف شده قابل دسترسی هستند.
به علاوه در اینجا بجای in از of استفاده شدهاست. این of از ES 6 گرفته شدهاست. زمانیکه از حلقهی جدید for...of استفاده میشود، متغیر محلی product حاوی یک عنصر از لیست product خواهد بود؛ اما اگر از حلقهی قدیمی for...in استفاده میشد، تنها ایندکس عددی این عناصر در دسترس قرار میگرفتند. به همین جهت است که در این حلقه، اکنون product.productName به نام محصول آن عنصر آرایهی دریافتی اشاره میکند و قابل استفاده است.
تا اینجا اگر برنامه را اجرا کنید، چنین خروجی را مشاهده خواهید کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part3.zip
خلاصهی بحث
از inline templateها جهت معرفی قالبهای کوتاه استفاده میشود. در اینجا از "" برای معرفی قالب یک سطری و یا از back tickهای ES 6، برای تعریف قالبهای چندسطری استفاده خواهد شد. برای قالبهای مفصلتر، بهتر است Linked templateها استفاده شود؛ با پشتیبانی کامل ادیتورهای موجود از لحاظ تکمیل و بررسی کدها.
برای استفاده از یک کامپوننت در کامپوننتی دیگر، نام selector آنرا به صورت یک المان جدید HTML در قالب دیگری ذکر کرده و سپس با استفاده از خاصیت directives، نام کلاس متناظر با آنرا نیز ذکر میکنیم. همچنین کار import ماژول آن نیز باید در ابتدای فایل صورت گیرد.
جهت غنی سازی قالبها و کامپوننتها و نمایش اطلاعات فقط خواندنی میتوان از binding یک طرفهی ویژهای به نام interpolation استفاده کرد. کار آن اتصال یک خاصیت عمومی کلاس کامپوننت، به قالب آن است. interpolation توسط {{}} تعریف میشود و میتواند شامل محاسبات نیز باشد.
همچنین در ادامهی بحث، نحوهی کار با دو دایرکتیو توکار ساختاری AngularJS 2.0 را نیز بررسی کردیم. این دایرکتیوهای ساختاری نیاز است با ستاره شروع شوند و عبارت انتساب داده شدهی به آنها باید داخل "" قرار گیرد (برخلاف interpolation که نیازی به اینکار ندارد). از ngIf* برای حذف یا افزودن یک المان و فرزندان آن از/به DOM استفاده میشود. اگر عبارت منتسب به آن به true ارزیابی شود، این المان از صفحه حذف خواهد شد. از ngFor* برای تکرار المانی مشخص به همراه فرزندان آن به تعداد اعضای لیستی که برای آن تعیین میگردد، استفاده میشود. متغیر محلی این پیمایشگر با # مشخص شده و حلقهی آن با of بجای in تعریف میشود.