پیشنیازهای نصب Docker بر روی ویندوز
مطابق مستندات آن، برای نصب داکر بر روی ویندوز به حداقلهای زیر نیاز است:
- استفاده از ویندوز 10 نگارش enterprise، که شماره نگارش آن حداقل 1607 باشد (حداقل Anniversary Update باشد).
- مجازی سازی در BIOS فعال شده باشد.
البته مجازی سازی عموما به صورت پیشفرض فعال است. برای بررسی آن، taskmanager ویندوز را اجرا کرده و در برگهی Performance آن، جائیکه مشخصات CPU را نمایش میدهد، یک سطر به Virtualization اختصاص دارد که مقدار آن باید enabled باشد (تصویر زیر) و اگر نیست، برای فعال کردن آن باید به تنظیمات BIOS سیستم خود مراجعه کنید:
روش دیگر دریافت این اطلاعات، اجرای دستور systeminfo در خط فرمان، با دسترسی مدیریتی است. در خروجی آن، عبارت «Virtualization Enabled In Firmware» را جستجو کنید که باید مقدار آن yes باشد.
- داشتن CPU با قابلیت SLAT یا Second Level Address Translation.
برای یافتن این موضوع، برنامهی coreinfo را دریافت کرده و آنرا به صورت coreinfo -v اجرا کنید. خروجی آن سه سطر مرتبط با مجازی سازی را به همراه دارد. اگر قابلیتی موجود نباشد، جلوی آن یک خط تیره و اگر قابلیتی موجود باشد، روبروی آن یک ستاره را مشاهده خواهید کرد.
روش دیگر بررسی آن، اجرای دستور msinfo32 در قسمت run ویندوز و سپس enter است. در قسمت system summary، اطلاعات Second Level Address Translation قابل مشاهده هستند (اگر No باشد، امکان اجرای containerهای لینوکسی را بر روی ویندوز نخواهید داشت):
- داشتن حداقل 4 گیگابایت RAM.
- فعال بودن Hyper-V نیز برای اجرای Linux Containers بر روی ویندوز، ضروری است (نصاب Docker، اینکار را به صورت خودکار انجام میدهد).
دریافت نصاب Docker for Windows
برای دریافت نصاب داکر مخصوص ویندوز، به آدرس زیر مراجعه کنید:
https://store.docker.com/editions/community/docker-ce-desktop-windows
که بلافاصله با تصویر کریه زیر مواجه خواهید شد:
برای رفع این مشکل، میتوان از روش مطرح شدهی در مطلب «یک روش ساده برای دور زدن تحریمها!» استفاده کرد؛ یعنی تنظیم DNS به 178.22.122.100 به صورت زیر:
پس از این تغییر، چون IP قابل مشاهدهی سیستم شما توسط سایت داکر تغییر میکند، اینبار صفحهی دریافت Docker Community Edition for Windows به صورت زیر ظاهر میشود:
همانطور که مشاهده میکنید، عنوان کردهاست که لطفا لاگین کنید تا بتوانید این برنامه را دریافت کنید. به همین جهت بر روی لینک آن کلیک کرده، یک اکانت جدید را در سایت docker ایجاد کنید (با یک ایمیل واقعی که تائیدیه آنرا دریافت خواهید کرد). پس از آن، با این اکانت جدید به سایت داکر وارد شوید تا لینک دریافت فایل exe نصاب آنرا دریافت کنید.
در این حالت مرورگر و یا حتی دانلودمنیجر شما بدون مشکل میتوانند این فایل را دریافت کنند و همان تنظیم DNS فوق، مشکل عدم دسترسی را برطرف میکند.
نصب Docker for Windows
پس از اجرای نصاب آن و پایان عملیات نصب (که تنها کافی است در صفحهی ابتدایی آن تیک مربوط به Windows Containers را نیز قرار دهید)، نیاز دارد تا شما را یکبار از سیستم Logout و login کند. پس از ورود به سیستم، تنظیمات ابتدایی آن به صورت خودکار صورت گرفته و در صورت فعال نبودن Hyper-V، پیام زیر را مشاهده خواهید کرد:
بر روی OK کلیک کنید تا اینکار با موفقیت به پایان برسد. البته پس از آن، منتظر حداقل یکبار ریاستارت شدن خودکار سیستم، بدون اطلاع قبلی نیز باشید.
یک نکته: کاری که در قسمت فعالسازی Hyper-V به صورت خودکار انجام میشود، شامل اجرای سه دستور زیر، در کنسول پاور شل، با دسترسی مدیریتی و سپس ری استارت سیستم است:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -Verbose Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -Verbose bcdedit /set hypervisorlaunchtype Auto
C:\Users\Vahid>docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 18.06.1-ce OSType: windows
بررسی تنظیمات سوئیچ کردن بین Linux Containers و Windows Containers
پس از اتمام ریاستارتها، برای آزمایش فعال بودن Hyper-V، در قسمت Run ویندوز، عبارت Virtmgmt.msc را نوشته و enter کنید. اگر تصویر زیر را مشاهده نمیکنید:
یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینهی switch to Linux containers را انتخاب کنید تا پس از مدتی، آیکن MobyLinuxVM در قسمت virtual machines (تصویر فوق) ظاهر شود.
اگر پس از انتخاب این گزینه، پیام زیر را دریافت کردید:
و یا اگر بر روی این ماشین مجازی کلیک راست کردید و گزینهی Start آنرا انتخاب کردید و پیام زیر ظاهر شد:
قسمت «پیشنیازهای نصب Docker بر روی ویندوز» را با دقت بررسی کنید (خصوصا قسمت BIOS و SLAT). نبود یکی از موارد ذکر شده، سبب بروز این مشکل میشود.
برای مثال اجرای دستور coreinfo -v بر روی سیستم من چنین خروجی را به همراه دارد:
E:\>coreinfo -v AuthenticAMD Microcode signature: 00000000 HYPERVISOR - Hypervisor is present SVM * Supports AMD hardware-assisted virtualization NP - Supports AMD nested page tables (SLAT)
همانطور که مشاهده میکنید، قابلیت SLAT در CPU این سیستم وجود ندارد. به همین جهت نمیتوان به Linux containers سوئیچ کرد. هرچند windows containers آن کار میکند.
روش دیگر مشاهدهی این خطا، مراجعهی به event viewer ویندوز است. در قسمت خطاهای سیستم، ممکن است خطای زیر را مشاهده کنید:
Hypervisor launch failed; Second Level Address Translation is required to launch the hypervisor.
آزمایش Docker نصب شده
پس از نصب docker، خط فرمان ویندوز را گشوده و دستور زیر را صادر کنید:
docker run hello-world
یک نکته: این image، یک image لینوکسی است. به همین جهت پیش از اجرای این دستور، همانطور که پیشتر نیز عنوان شد، یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینهی switch to Linux containers را انتخاب کنید. سپس دستور docker run hello-world را اجرا نمائید.
و یا در همین حال دستور docker run -p 80:80 nginx را صادر کنید تا وب سرور لینوکسی nginx را بتوانید تحت ویندوز اجرا کنید. پس از خاتمهی عملیات دریافت و اجرای وب سرور، با توجه به تنظیم p 80:80-، پورت 80 میزبان (اولین عدد)، به پورت 80 کانتینر نگاشت شدهاست. به همین جهت تنها با اجرای دستور http://localhost، خروجی این وب سرور را میتوانید در مرورگر سیستم خود مشاهده کنید.
همانطور که مشاهده میکنید، با استفاده از داکر، پیش از آنکه بدانیم چگونه باید یک نرم افزار را نصب کرد، میتوان از آن استفاده کرد!
روش متوقف کردن Containers در حال اجرا
اگر دستور docker ps را در خط فرمان ویندوز اجرا کنید، لیست پروسههای اجرا شدهی توسط آن قابل مشاهده هستند. در این لیست container id در حال اجرا نیز مشخص است. برای خاتمهی کار آن، تنها کافی است دستور docker stop id را اجرا کنید.
یک نکته: ضرورتی به ذکر کامل id نیست. برای مثال ذکر سه حرف اول آن نیز کفایت میکند.
روش اجرای مجدد یک Container
فرض کنید میخواهیم سرور nginx را مجددا اجرا کنیم. یک روش آن، اجرای مجدد دستور docker run -p 80:80 nginx است که پیشتر آنرا انجام دادیم. در این حالت این image تبدیل به container شده و همانند روشهای متداول نصب نرم افزار، اکنون به عنوان یک نرم افزار نصب شده در دسترس است. برای مشاهدهی لیست آنها، دستور docker ps -a را اجرا کنید. این لیست تا این لحظه باید شامل containerهای nginx و hello-world باشد. متوقف کردن یک container، سبب تخریب یا حذف آن نمیشود. در این حالت در لیستی که توسط دستور docker ps -a نمایش داده شدهاست، باز هم container idها قابل مشاهده هستند. فقط کافی است برای اجرای یکی از آنها، دستور docker start id را اجرا کرد. به این صورت دیگر نیازی به ذکر دستور کامل docker run با تمام پارامترهای آن نیست. این id نیز همانطور که ذکر شد، میتواند سه حرف ابتدایی این id باشد تا حدی که نسبت به سایر idهای موجود، منحصربفرد شناخته شود. یا بجای container id میتوان container name نمایش داده شدهی در این لیست را استفاده کرد.
پس از اجرای nginx توسط دستور docker start id، دو روش برای بررسی در حال اجرا بودن آن وجود دارد:
الف) مرورگر را باز کنیم و آدرس http://localhost را بررسی کنیم.
ب) دستور docker ps را در خط فرمان اجرا کنیم، تا مشخص شود که آیا پروسهی nginx در حال اجرا است یا خیر؟
بنابراین دستور docker ps -a لیست تمام containers در حال اجرا و همچنین متوقف شده را نمایش میدهد. اما دستور docker ps تنها لیست containers در حال اجرا را نمایش خواهد داد.
روش حذف Containers از Docker
همانطور که در قسمت قبل نیز بحث شد، معادل نصب نرم افزار در اینجا، ایجاد یک container از یک image دریافتی از docker hub است. روش عکس آن، یعنی تخریب یک container، دقیقا معادل عزل نرم افزار از سیستم، در حالتهای متداول است. برای اینکار مجددا دستور docker ps -a را اجرا میکنیم تا لیست تمام containerهای در حال اجرا و همچنین متوقف شده نمایش داده شوند. لیستی که در اینجا نمایش داده میشود، شبیه به لیستی است که در قسمت add/remove programs ویندوز مشاهده میکنید. این لیست معادل لیست نرم افزارهای نصب شدهی بر روی سیستم است و یا برای مشاهدهی لیست imageهای دریافتی از docker hub میتوان دستور docker images را صادر کرد.
قبل از حذف یک container نیاز است آنرا متوقف کنیم. برای این منظور از دستور docker stop id استفاده میشود. سپس اجرای دستور docker rm id، سبب حذف کامل این container خواهد شد. برای آزمایش آن، مجددا دستور docker ps -a را اجرا کنید.
دستور docker rm چندین id را نیز میپذیرد. میتوان این idها و یا حتی سه حرف ابتدایی آنها را با فاصله در اینجا ذکر کرد. علاوه بر id، ذکر نام containers نیز مجاز است.
روش حذف Imageهای دریافتی از Docker Hub
دستور docker rm، فقط containers را از سیستم حذف میکند (نرم افزارهای نصب شده). اما خود imageهای اصلی دریافت شدهی از docker hub را حذف نمیکند (معادل همان فایلهای zip دریافت نرم افزار یا برنامههای نصاب، در حالت متداول و سنتی نصب نرم افزار). برای آزمایش آن دستور docker images را اجرا کنید. هنوز هم در لیست آن، تمام موارد دریافتی موجود هستند.
برای حذف یک image میتوان از دستور docker rmi id استفاده کرد (rmi بجای rm). این id نیز در لیست docker images ظاهر میشود و ذکر قسمتی از آن، تا حدی که نسبت به سایر idهای لیست شده منحصربفرد باشد، کافی است. در اینجا بجای id، از نام image نیز میتوان استفاده کرد. همچنین ذکر چندین id و یا نام نیز پس از دستور docker rmi، میسر است.
روش جستجوی imageها در Docker Hub توسط Docker CLI
فرض کنید میخواهیم image مربوط به راهنمای Docker را از Docker Hub دریافت کنیم. یک روش آن مراجعهی مستقیم به سایت آن است و استفاده از امکانات جستجوی فراهم شدهی در آن سایت. روش دیگر، استفاده از Docker CLI است. اگر دستور docker search docs را در خط فرمان اجرا کنیم، لیست تمام مخازن کدی که در آنها واژهی docs قرار دارد، نمایش داده میشود. البته پیش از نصب image آن بهتر است به برگهی tags مخزن کد آن نیز مراجعه کنید تا بتوانید حجم آنرا نیز مشاهده نمائید که حدود یک گیگابایت است. مخازن docker hub، حاوی imageهای نصاب containerهای متناظر هستند. برای دریافت و اجرای آن میتوان دستور docker run -p 4000:4000 docs/docker.github.io را اجرا کرد.
پس از دریافت یک گیگابایت مستندات، container آن بر روی پورت 4000 در سیستم ما (http://localhost:4000)، به صورت یک وب سایت استاتیک، قابل دسترسی خواهد بود. به این صورت میتوان به مستندات کامل داکر به صورت آفلاین دسترسی داشت.
مفهوم Interactive Terminal در Docker
زمانیکه دستور اجرای مستندات آفلاین را صادر میکنید، در انتهای آن عنوان میکند که وب سایت محلی آن بر روی پورت 4000 قابل دسترسی است. سپس در ذیل آن ذکر شدهاست که اگر ctrl+c را فشار دهید، اجرای آن به پایان میرسد. اما عملا اینطور نیست و اگر دستور docker ps را صادر کنید، هنوز container در حال اجرای آن را میتوان مشاهده کرد.
اما اگر اینبار دستور اجرای docker run را به همراه یک interactive terminal با سوئیچ it و نام docs صادر کنیم:
docker run -p 4000:4000 -it --name docs docs/docker.github.io
سوئیچ it یا interactive terminal سبب میشود تا یک container در foreground، بجای background اجرا شود. به این ترتیب دستور ctrl+c، سبب خاتمهی واقعی پروسهی درحال اجرای در container میشود.
روش دیگر خاتمهی این container، استفاده از نام ذکر شدهاست؛ یعنی اجرای دستور docker stop docs.
یک نکته: اگر میخواهید از terminal باز شده قطع شوید (مجددا به command prompt باز گردید) اما سبب خاتمهی container آن نشوید، از ترکیب ctrl+p+q استفاده کنید.
اجرای containerهای ویندوزی
در مورد نحوهی سوئیچ بین نوعهای مختلف containerهای ویندوزی و لینوکسی پیشتر توضیح دادیم. برای این منظور میتوان بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینهی switch to Windows/Linux containers را انتخاب کرد. باید دقت داشت که پشتیبانی از containerهای ویندوزی، از ویندوز 10، نگارش 1607، یا همان Anniversary Update آن به بعد، به ویژگیهای ویندوز اضافه شدهاند که به صورت خودکار توسط docker فعالسازی میشوند:
اجرای IIS به عنوان یک Windows Container
تا اینجا imageهای دریافتی، لینوکسی بودند. اگر گزینهی Windows Containers را به روشی که گفته شد، فعال کنید، اینبار با اجرای دستورات docker ps و یا docker images، هیچ خروجی را دریافت نخواهید کرد. از این جهت که کانتینرهای ویندوزی و لینوکسی، به صورت کاملا ایزولهای از هم اجرا و مدیریت میشوند. علت آنرا هم در MobyLinuxVM که پیشتر با اجرای دستور Virtmgmt.msc بررسی کردیم، میتوان یافت. Containerهای لینوکسی، در داخل MobyLinuxVM اجرا میشوند.
در اینجا به عنوان مثال میتوان image رسمی مربوط به IIS را از docker hub دریافت و به صورت یک کانتینر ویندوزی اجرا کرد. البته پیش از اجرای دستورات آن بهتر است به برگهی tags آن مراجعه کرده و حجمهای نگارشهای مختلف آنرا بررسی کرد. اجرای دستور docker pull microsoft/iis به معنای دریافت tag ای به نام latest است (به حجم 6 گیگابایت!)؛ یعنی با دستور docker pull microsoft/iis:latest یکی است. بنابراین در اینجا بر اساس tagهای مختلف، میتوان دستور pull متفاوتی را صادر کرد. برای مثال اگر دستور docker pull microsoft/iis:nanoserver را صادر کردید، نگارش مخصوص nano server آنرا که فقط 449 مگابایت است، دریافت میکند. بنابراین از این پس به tagهای هر مخزن docker hub خوب دقت کنید و نگارش مختص به سیستم عامل خود را دریافت نمائید. عدم ذکر tag ای، همواره tag ویژهای را به نام latest، دریافت میکند.
با اجرای دستور زیر
docker run -p 81:80 -d --name iis microsoft/iis:nanoserver
یک نکته: مشکلی با اجرای IIS مخصوص نانوسرور بر روی ویندوز 10 به این صورت و توسط داکر نیست. بنابراین پس از اجرای دستور فوق، کار دریافت image و ساخت container و سپس اجرای آن به صورت خودکار انجام شده و بلافاصله به command prompt بازگشت داده میشویم (به علت استفادهی از پارامتر d). اکنون اگر دستور docker ps را صادر کنیم، مشاهده میکنیم که کانتینر IIS مخصوص نانوسرور، هم اکنون بر روی ویندوز 10 در حال اجرا است و در آدرس http://localhost:81 قابل دسترسی است.
جهت تکمیل این بحث، بهتر است image مخصوص nanoserver را نیز از docker hub دریافت و اجرا کنیم:
docker run microsoft/windowsservercore
تنظیمات کارت شبکهی Containers
هنگامیکه پروسهای درون یک container اجرا میشود، ایزوله سازیهای بسیاری نیز در مورد آن اعمال خواهد شد؛ به همین جهت گاهی از اوقات عدهای containerها را با ماشینهای مجازی نیز مقایسه میکنند. برای مثال کانتینرها به همراه network adapter خاص خود نیز هستند؛ درست مانند اینکه یک کامپیوتر مجزای از سیستم جاری میباشند و اگر این network adapter را ping کنیم، میتوان به این صورت نیز به آن کانتینر، دسترسی داشته باشیم.
برای یافتن آن، دستور docker inspect iis را صادر میکنیم. خروجی آن به همراه یک قسمت network نیز هست که داخل آن یک IP Address قابل مشاهده است. این IP است که مختص و منحصربفرد این container است. در ابتدا برای آزمایش آن، میتوان آنرا ping کرد؛ مانند ping 172.27.49.47. همچنین به تمام برنامههای داخل این container توسط این IP نیز میتوان دسترسی یافت. برای مثال فراخوانی http://172.27.49.47:81 در مرورگر، سبب نمایش صفحهی اول IIS میشود. البته اگر اینکار را انجام دهیم، کار نمیکند. علت اینجا است، نگاشت پورتی را که تعریف کردهایم (پورت 81)، به پورتی در کامپیوتر میزبان است و نه این IP ویژه. برنامهی اصلی IIS در داخل container، به پورت 80 بر روی این آدرس IP گوش فرا میدهد. اکنون اگر آدرس http://172.27.49.47:80 را در کامپیوتر میزبان فراخوانی کنیم، کار میکند.
بنابراین هرچند containerها به معنای نرم افزارهای از پیش نصب شدهی در حال اجرا هستند، اما ... به همراه ایزوله سازیهای قابل توجهی بر روی کامپیوتر میزبان اجرا میشوند؛ درست مانند یک کامپیوتر مجزای از آن.
همانطور که میدانید بسیاری از فونتهای فارسی برای وب اصلاح نشده اند و در سیستمهایی با تفکیک پذیری بالا دچاره شکستگی میشوند. برای همین برای فونتهای انگلیسی از کتابخانه جاوااسکریپتی کافون استفاده میشه که فونتها به صورت وکتور در یک فایل js معرفی میشوند و با استفاده از svg یا canvas نمایش میدهد، بیفون همانند کافون هست و برای فونتهای فارسی مناسب است.
در این گزارش میزان دریافتی ماهانه متخصصان و مدیران کشور در 23 گروه شغلی به تفکیک سابقه کار و رده سازمانی ارائه شده است. همچنین تأثیر نوع شرکت، تحصیلات و نوع دانشگاه بر دریافتی شاغلین ارزیابی شده است. طبق این آمار، بین حداقل و حداکثر دریافتی افراد در هر رده سازمانی تفاوت 200 درصدی قابل مشاهده است.
این گزارش نتیجه ارزیابی
دستمزد ماهانه 29 هزار نفر از جامعه متخصصان کشور در بهمن 92 بوده و هدف آن
آگاهی کارفرمایان، مدیران و کارشناسان از میزان حقوق و دستمزدهای رایج در
گروههای شغلی مختلف است.
درخواست مستندات
عدم سازگاری با EF
- همچنین اگر واقعا نیاز به یک خاصیت در گزارش نهایی دارید، در حین Projection نهایی کوئری LINQ تهیه شده (با نوشتن select ایی که فقط یک خاصیت را بر میگرداند)، تنها این یک خاصیت را بازگشت دهید. اگر گزارش شما یک شیء کامل را بر میگرداند، کار اضافی در سمت تهیه دیتاسورس شما انجام شده نه در سمت کتابخانهای که قرار است از این نتیجه استفاده کند.
- بحث lazy loading ، eager loading و تشکیل dynamicProxies را در اینجا مطالعه کنید.
- همچنین اهمیت استفاده از AsNoTracking را هم در اینجا مطالعه کنید.
اضافه کردن Indentation به ابتدای محتوای یک سلول
string.Concat(" ",item.Title);
در اینجا مشاهده میکنید که گروه دارایی جاری به همراه حسابهای کل آن در یک سطح نشان داده میشود که این امر خوانایی گزارش را کم میکند.
این گزارش قرار است در 3 سطح کل و معین و تفصیلی گرفته شود.
با تشکر
استفاده از pdfreport برای اولین بار
آقای نصیری از پروژه تون خیلی خیلی تشکر میکنم. واقعا عالیه. تو فکر بودم که چه جوری تحت وب گزارش گیری کنم بدون crystal report.
راستش خیلی از مقالهها رو خوندم اما واقعا گیج شدم.
توی مقاله معرفی کتابخانه PdfReport گفتید کلاسی به اسم مثلا User ایجاد کنیم توی یه ClassLibrary. سوالم اینه که من که از ef استفاده میکنم و کلاس هایی هم توی DomainClass دارم دوباره باید برای گزارش گیری این کلاسها رو ایجاد کنم؟
درکل چه جوری تحت وب (Web Form) از این کتابخانه استفاده کنم؟ اگه راهنمایی کلی میخوام که به چه نحوی باید گزارش ایجاد کنیم. انقد گشتم سرم گیج میره.
SQL Antipattern #2
بخش دوم : Naive Trees
فرض کنید یک وب سایت حرفهای خبری یا علمی-پژوهشی داریم که قابلیت دریافت نظرات کاربران را در مورد هر مطلب مندرج در سایت یا نظرات داده شده در مورد آن مطالب را دارا میباشد. یعنی هر کاربر علاوه بر توانایی اظهار نظر در مورد یک خبر یا مطلب باید بتواند پاسخ نظرات کاربران دیگر را نیز بدهد. اولین راه حلی که برای طراحی این مطلب در پایگاه داده به ذهن ما میرسد، ایجاد یک زنجیره با استفاده از کد sql زیر میباشد:
CREATE TABLE Comments ( comment_idSERIAL PRIMARY KEY, parent_idBIGINT UNSIGNED, comment TEXT NOT NULL, FOREIGN KEY (parent_id) REFERENCES Comments(comment_id) );
البته همان طور که پیداست بازیابی زنجیرهای از پاسخها در یک پرسوجوی sql کار سختی است. این نخها معمولا عمق نامحدودی دارند و برای به دست آوردن تمام نخهای یک زنجیره باید پرسوجوهای زیادی را اجرا نمود.
ایدهی دیگر میتواند بازیابی تمام نظرها و ذخیرهی آنها در حافظهی برنامه به صورت درخت باشد. ولی این روش برای ذخیره هزاران نظری که ممکن است در سایت ثبت شود و علاوه بر آن مقالات جدیدی که اضافه میشوند، تقریبا غیرعملی است.
1.2 هدف: ذخیره و ایجاد پرسوجو در سلسلهمراتب
وجود سلسله مراتب بین دادهها امری عادی محسوب میگردد. در ساختار دادهای درختی هر ورودی یک گره محسوب میگردد. یک گره ممکن است تعدادی فرزند و یک پدر داشته باشد. گره اول پدر ندارد، ریشه و گره فرزند که فرزند ندارد، برگ و گرهای دیگر، گرههای غیربرگ نامیده میشوند.
مثالهایی که از ساختار درختی دادهها وجود دارد شامل موارد زیر است:
Organizational chart: در این ساختار برای مثال در ارتباط بین کارمندان و مدیر، هر کارمند یک مدیر دارد که نشاندهندهی پدر یک کارمند در ساختار درختی است. هر مدیر هم یک کارمند محسوب میگردد.
Threaded discussion: در این ساختار برای مثال در سیستم نظردهی و پاسخها، ممکن است زنجیرهای از نظرات در پاسخ به نظرات دیگر استفاده گردد. در درخت، فرزندان یک گرهی نظر، پاسخهای آن گره هستند.
در این فصل ما از مثال ساختار دوم برای نشان دادن Antipattern و راه حل آن بهره میگیریم.
2.2 Antipattern : همیشه مبتنی بر یکی از والدین
راه حل ابتدایی و ناکارآمد
اضافه نمودن ستون parent_id . این ستون، به ستون نظر در همان جدول ارجاع داده میشود و شما میتوانید برای اجرای این رابطه از قید کلید خارجی استفاده نمایید. پرسوجویی که برای ساخت مثالی که ما در این بحث از آن استفاده میکنیم در ادامه آمده است:
CREATE TABLE Comments ( comment_idSERIAL PRIMARY KEY, parent_idBIGINT UNSIGNED, bug_idBIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_dateDATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (parent_id)REFERENCES Comments(comment_id), FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY(author) REFERENCES Accounts(account_id) );
لیست مجاورت :
لیست مجاورت خود میتواند به عنوان یک antipattern در نظر گرفته شود. البته این مطلب از آنجایی نشأت میگیرد که این روش توسط بسیاری از توسعهدهندگان مورد استفاده قرار میگیرد ولی نتوانسته است به عنوان راه حل برای معمولترین وظیفهی خود، یعنی ایجاد پرسوجو بر روی کلیه فرزندان، باشد.
• با استفاده از پرسوجوی زیر میتوان فرزند بلافاصلهی یک "نظر" را برگرداند:
SELECT c1.*, c2.* FROM Comments c1 LEFT OUTER JOIN Comments c2 ON c2.parent_id = c1.comment_id;
ضعف پرسوجوی فوق این است که فقط میتواند دو سطح از درخت را برای شما برگرداند. در حالیکه خاصیت درخت این است که شما را قادر میسازد بدون هیچ گونه محدودیتی فرزندان و نوههای متعدد (سطوح بیشمار) برای درخت خود تعریف کنید. بنابراین به ازای هر سطح اضافه باید یک join به پرسجوی خود اضافه نمایید. برای مثال اگر پرسوجوی زیر میتواند درختی با چهار سطح برای شما برگرداند ولی نه بیش از آن:
SELECT c1.*, c2.*, c3.*, c4.* FROM Comments c1 -- 1st level LEFT OUTER JOIN Comments c2 ON c2.parent_id = c1.comment_id -- 2nd level LEFT OUTER JOIN Comments c3 ON c3.parent_id = c2.comment_id -- 3rd level LEFT OUTER JOIN Comments c4 ON c4.parent_id = c3.comment_id; -- 4th level
این پرسوجو به این دلیل که با اضافه شدن ستونهای دیگر، نوهها را سطوح عمیقتری برمیگرداند، پرسوجوی مناسبی نیست. در واقع استفاده از توابع تجمیعی ، مانند COUNT() مشکل میشود.
راه دیگر برای به دست آوردن ساختار یک زیردرخت از لیست مجاورت برای یک برنامه، این است که سطرهای مورد نظر خود را از مجموعه بازیابی نموده و سلسهمراتب مورد نظر را در حافظه بازیابی نماییم و از آن به عنوان درخت استفاده نماییم:
SELECT * FROM Comments WHERE bug_id = 1234;
INSERT INTO Comments (bug_id, parent_id, author, comment) VALUES (1234, 7, 'Kukla' , 'Thanks!' );
UPDATE Comments SET parent_id = 3 WHERE comment_id = 6;
SELECT parent_id FROM Comments WHERE comment_id = 6; -- returns 4 UPDATE Comments SET parent_id = 4 WHERE parent_id = 6; DELETE FROM Comments WHERE comment_id = 6;
- چه تعداد سطح برای پشتیبانی در درخت نیاز خواهیم داشت؟
- من همیشه از کار با کدی که ساختار دادهی درختی را مدیریت میکند، میترسم
- من باید اسکریپتی را به طور دورهای اجرا نمایم تا سطرهای یتیم موجود در درخت را حذف کند.
WITH CommentTree (comment_id, bug_id, parent_id, author, comment, depth) AS ( SELECT *, 0 AS depth FROM Comments WHERE parent_id IS NULL UNION ALL SELECT c.*, ct.depth+1 AS depth FROM CommentTreect JOIN Comments c ON (ct.comment_id = c.parent_id) ) SELECT * FROM CommentTree WHERE bug_id = 1234;
SELECT * FROM Comments START WITH comment_id = 9876 CONNECT BY PRIOR parent_id = comment_id;
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, path VARCHAR(1000), bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id)
SELECT * FROM Comments AS c WHERE '1/4/6/7/' LIKE c.path || '%' ;
SELECT * FROM Comments AS c WHERE c.path LIKE '1/4/' || '%' ;
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, nsleft INTEGER NOT NULL, nsright INTEGER NOT NULL, bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs (bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id) );
شمارهی سمت چپ یک گره از تمام شمارههای سمت چپ فرزندان آن گره کوچکتر و شمارهی سمت راست آن گره از تمام شمارههای سمت راست آن گره بزرگتر است. این شمارهها هیچ ارتباطی به comment_id مربوط به آن گره ندارند.
یک راه حل ساده برای تخصیص این شمارهها به گرهها این است که از سمت چپ یک گره آغاز میکنیم و اولین شماره را اختصاص میدهیم و به همین به گرهای سمت چپ فرزندان میآییم و شمارهها را به صورت افزایشی به سمت چپ آنها نیز اختصاص میدهیم. سپس در ادامه به سمت راست آخرین نود رفته و از آن جا به سمت بالا میآییم و به همین ترتیب به صورت بازگشتی تخصیص شمارهها را ادامه میدهیم.
با اختصتص شمارهها به هر گره، میتوان از آنها برای یافتن نیاکان و فرزندان آن گره بهره جست. برای مثال برای بازیابی گرهی 4 و فرزندان (نوههای) آن باید دنبال گرههایی باشیم که شمارههای آن گرهها بین nsleft و nsright گرهی شماره4 باشد:
SELECT c2.* FROM Comments AS c1 JOIN Comments as c2 ON c2.nsleft BETWEEN c1.nsleft AND c1.nsright WHERE c1.comment_id = 4;
SELECT c2.* FROM Comments AS c1 JOIN Comment AS c2 ON c1.nsleft BETWEEN c2.nsleft AND c2.nsright WHERE c1.comment_id = 6;
SELECT parent.* FROM Comment AS c JOIN Comment AS parent ON c.nsleft BETWEEN parent.nsleft AND parent.nsright LEFT OUTER JOIN Comment AS in_between ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsright AND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright WHERE c.comment_id = 6 AND in_between.comment_id IS NULL;
-- make space for NS values 8 and 9 UPDATE Comment SET nsleft = CASE WHEN nsleft >= 8 THEN nsleft+2 ELSE nsleft END, nsright = nsright+2 WHERE nsright >= 7; -- create new child of comment #5, occupying NS values 8 and 9 INSERT INTO Comment (nsleft, nsright, author, comment) VALUES (8, 9, 'Fran' , 'Me too!' );
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id) ); CREATE TABLE TreePaths ( ancestor BIGINT UNSIGNED NOT NULL, descendant BIGINT UNSIGNED NOT NULL, PRIMARY KEY(ancestor, descendant), FOREIGN KEY (ancestor) REFERENCES Comments(comment_id), FOREIGN KEY (descendant) REFERENCES Comments(comment_id) );
به جای استفاده از جدول Comments برای ذخیرهی اطلاعات مربوط به یک درخت از جدول TreePath استفاده میکنیم. به ازای هر یک جفت گره در این درخت یک سطر در جدول ذخیره میشود که ارتباط پدر فرزندی را نمایش میدهد و الزاما نباید این دو پدر فرزند بلافصل باشد. همچنین یک سطر هم به ازای ارتباط هر گره با خودش به جدول اضافه میگردد.
پرسوجوهای بازیابی نیاکان و فرزندان (گرهها) از طریق جدول TreePaths سادهتر از روش مجموعههای تودرتو است. مثلا برای بازیابی فرزندان (نوههای) گرهی شمارهی 4، سطرهایی که نیاکان آنها 4 است را به دست میآوریم:
SELECT c.* FROM Comments AS c JOIN TreePaths AS t ON c.comment_id = t.descendant WHERE t.ancestor = 4;
SELECT c.* FROM Comments AS c JOIN TreePaths AS t ON c.comment_id = t.ancestor WHERE t.descendant = 6;
INSERT INTO TreePaths (ancestor, descendant) SELECT t.ancestor, 8 FROM TreePaths AS t WHERE t.descendant = 5 UNION ALL SELECT 8, 8;
DELETE FROM TreePaths WHERE descendant = 7;
DELETE FROM TreePaths WHERE descendant IN (SELECT descendant FROM TreePaths WHERE ancestor = 4);
DELETE FROM TreePaths WHERE descendant IN (SELECT descendant FROM TreePaths WHERE ancestor = 6) AND ancestor IN (SELECT ancestor FROM TreePaths WHERE descendant = 6 AND ancestor != descendant);
INSERT INTO TreePaths (ancestor, descendant) SELECT supertree.ancestor, subtree.descendant FROM TreePaths AS supertree CROSS JOIN TreePaths AS subtree WHERE supertree.descendant = 3 AND subtree.ancestor = 6;
میتوان عملکرد Closure Table را برای ایجاد پرسوجو روی فرزندان و پدر بلافصل را آسانتر نیز نمود. اگر فیلد path_length را به جدول TreePaths اضافه نماییم این کار انجام میشود. path_length گرهای که به خودش ارجاع میشود، صفر است. path_length فرزند بلافصل هر گره 1، path_length نوهی آن 2 میباشد و به همین ترتیب path_lengthها را در هر سطر مقداردهی میکنیم. اکنون یا فتن فرزند گرهی شمارهی 4 آسانتر است:
SELECT * FROM TreePaths WHERE ancestor = 4 AND path_length = 1;
<StackLayout BackgroundColor="LightYellow" HorizontalOptions="Center" Orientation="Vertical" VerticalOptions="Start" WidthRequest="320">
مشکل دیگری که وجود دارد این است که اگر بابت کمبود فضا، Stack Layout کوچکتر از 5 سانتی متر شود، از بغل، کامل، به کنارههای Parent خود میچسبد که اصلا جالب نیست. برای رفع این مشکل میتوانیم از Padding (حاشیه داخلی) و Margin (حاشیه خارجی) استفاده کنیم. در عکس زیر، Background مربوط به Content Page را قرمز و Stack Layout را آبی کردهایم. با اختصاص دادن یک سانتی متر (64) به Padding و نیم سانت به Margin، میتوانید در عکس زیر تاثیر آن را ببینید:
در سمت راست، بالا و چپ Stack Layout، به اندازه نیم سانت حاشیه قرمز میبینید که تاثیر Margin است. همچنین المانهای داخل Stack Layout نیز هر کدام یک سانت از اطراف فاصله دارند که تاثیر Padding است. در پایین Stack Layout، تا جایی که Content Page بزرگ شود، فضای قرمز میبینید، نیم سانت از این فضای قرمز به خاطر Margin بوده، ولی باقی مربوط به این است که VerticalOptions را برای Stack Layout، بر روی Start تنظیم کردهایم که باعث میشود Stack Layout بالای Content Page قرار بگیرد و زیر آن تماما قرمز شود. حتی اگر "عرض" صفحه نمایش بزرگتر از عکس قبلی میبود، فضای قرمز از راست و چپ بیشتر میشد که نیم سانت آن بابت Margin بوده و بیشتر از آن مربوط به HorizontalOptions است که برای Stack Layout روی Center تنظیم شده.
با توجه به اینکه Page - Layout - Controlها در Android-iOS-Windows به معادلهای Native خود تبدیل میشوند و برای مثال ظاهر Button در این سه پلتفرم متفاوت است، ممکن است Padding ای که به یک کنترل داده ایم و در اندروید خوب به نظر میرسد، در iOS مقدار دیگری را لازم داشته باشد. مثلا در Android مقدار 2 و در iOS مقدار 3 لازم داشته باشد. یا ممکن است این مقدار به ازای این که در موبایل، تبلت یا دسکتاپ باشیم، لازم باشد که متفاوت باشد. در Xaml ما دو امکان OnPlatform و OnIdiom را داریم که هر Property اعم از Font Size - Padding - Text و ... را میتوان به ازای هر شرایطی با مقداری متفاوت مقدار دهی نمود. برای مثال داریم:
<StackLayout Margin="{OnPlatform Android=3, iOS='0,0,20,0', UWP='2'}" Padding="{OnIdiom Default=2, Tablet=3}" ...
البته Propertyهای مهم دیگری نیز در UI تاثیر گذار هستند. برای مثال Opacity برای تنظیم شفافیت، FlowDirection برای تنظیم Right to left و Left to right بودن و خیلی Propertyهای دیگه که به مرور و با بیشتر کار کردن با Xamarin Forms با آنها آشنا میشوید.
در قسمت بعدی به نوشتن منطق میپردازیم؛ نحوه نمایش دادن خطا و رفتن از صفحهای به صفحهی دیگر.