با افزایش حجم بانکهای اطلاعاتی دسترسی سریع به دادههای مطلوب به یک معضل تبدیل میشود. بهمین دلیل نیاز به مکانیزم هایی برای بازیابی سریع دادهها احساس میشود. یکی از این مکانیزمها اندیس گذاری (indexing) است. اندیس گذاری مکانیزمی است که به ما امکان دسترسی مستقیم (direct access) را به دادههای بانک اطلاعاتی میدهد.
عمل اندیس گذاری وظیفه طراح بانک اطلاعاتی است که با توجه به دسترسی هایی که در آینده به بانک اطلاعاتی وجود دارد مشخص میکند که بر روی چه ستون هایی میخواهد اندیس داشته باشد. بعنوان مثال با تعیین کلید اصلی اعلام میکند که بیشتر دسترسیهای آینده من بر اساس این کلید اصلی است و بنابراین بانک اطلاعاتی بر روی کلید اصلی اندیس گذاری را انجام میدهد. علاوه بر کلید اصلی میتوان بر روی هر ستون دیگری از جدول نیز اندیس گذاشت که همانطور که گفته شد این مسئله بستگی به تعداد دسترسی آینده ما از طریق آن ستونها دارد.
پس از اندیس گذاری بر روی یک ستون بسته به نوع اندیس فایلی در پایگاه اطلاعاتی ما ایجاد میشود که به آن فایل اندیس (index file) گفته میشود. این فایل یک فایل مبتنی بر رکورد (record-based) است که هر رکورد آن محتوی زوج کلید جستجو – اشاره گر می باشد. کلید جستجو را مقدار ستون مورد نظر و اشاره گر را اشاره گری به رکورد مربوط به ان میتواند در نظر گرفت.
توجه داشته باشید که اندیس گذاری و مدیریت اندیس ها، همانطور که در این مقاله آموزشی گفته خواهد شد سر بار هایی ( از نظر حافظه و پردازش) را بر سیستم تحمیل مینمایند. بعنوان مثال با اندیس گذاری بر روی هر ستونی یک فایل اندیس نیز ایجاد میشود بنابراین اگر اندیسهای ما بسیار زیاد باشد حجم زیادی از بانک اطلاعاتی ما را خواهند گرفت. مدیریت و بروز نگهداری فایلهای اندیس نیز خود مسئله ایست که سربار پردازشی را بدنبال دارد. بنابراین توصیه میشود در هنگام اندیس گذاری حتما بررسیها و تحلیلهای لازم را انجام دهید و تنها بر روی ستون هایی اندیس بگذرید که در آینده بیشتر دسترسیهای شما از طریق ان ستونها خواهد بود.
عموما در بانکهای اطلاعاتی دو نوع اندیس میتواند بکار گیری شود که عبارتند از :
- اندیسهای مرتب (ordered indices) : در این نوع کلیدهای جستجو (search-key) بصورت مرتب نگداری میشوند.
- اندیسهای هش (Hash indices) : در این نوع از اندیسها کلیدهای جستجو در فایل اندیس مرتب نیستند. بلکه توسط یک تابع هش (hash function) توزیع میشوند.
در این مقاله قصد داریم به اندیسهای مرتب بپردازیم و بخشی از مفاهیم مطرح در این باره را پوشش دهیم.
اندیسهای متراکم ( dense index ):
اولین و سادهترین نوع از اندیسهای مرتب اندیسهای متراکم ( dense ) هستند. در این نوع از اندیسها وقتی بر روی ستونی میخواهیم عمل اندیس گذاری را انجام دهیم میبایست به ازای هر کلید – جست و جو (search-key) غیر تکراری در ستون مورد نظر، یک رکورد در فایل اندیس مربوط به ان ستون اضافه کنیم. برای روشن شدن بیشتر موضوع به شکل زیر توجه کنید.
شکل 1 – اندیس متراکم (sparse index)
همانطور که در تصوری مشاهده میکنید بر روی ستون دوم از این جدول (جدول سمت راست)، اندیس متراکم (dense) گذاشته شده است. بر همین اساس به ازای هر کدام از اسامی خیابانها یک رکورد در فایل اندیس (جدول سمت چپ) آورده شده است. در فایل اندیس میبینید که در کنار کلید جستجو یک اشاره گر نیز به جدول اصلی وجود دارد که در هنگام دسترسی مستقیم (direct access) از این اشاره گر استفاده خواهد شد. دقت کنید که کلیدهای جستجو در فایل اندیس بصورت مرتب نگهداری شده اند که نکته ای کلیدی در اندیسهای مرتب میباشد.
مرتب بودن فایل اندیس موجب میشود که ما در هنگام جستجوی کلید مورد نظرمان در جدول اندیس بتوانیم از روشهای جستجویی نظری جست و جوی دو دویی استفاده کنیم و در نتیجه سریعتر کلید مورد نظر را پیدا کنیم. این مسئله باعث ببهبود کارایی میشود. بعنوان مثال فرض کنید در فایل اندیس یک ملیون رکورد داریم. در این صورت برای یافتن کلید مورد نظرمان در جدول اندیس بروش جست و جوی دو دویی تنها کافی است 20 عمل مقایسه انجام دهیم. بنابراین میبینید که مرتب نگهداشتن جدول اندیس چقدر در سرعت بازیابی، تاثیر دارد.
نکته مهمی که در اندیسهای متراکم باید به آن دقت شود اینست که ما به ازای کلیدهای جستجوی غیر تکراری یک رکورد در جدول اندیس نگهداری میکنیم. برای مثال در شکل بالا در ستون مورد نظر ما دو رکورد برای Downtown و سه رکورد برای Perryridge وجود دارد. این در حالی است که در فایل اندیس فقط یک Downtown و Perryridge داریم.
در اندیسهای متراکم ما امکان دو نوع دسترسی را داریم :
- دسترسی مستقیم (direct access)
- دسترسی ترتیبی (sequential access)
دسترسی مستقیم :
توجه داشته باشید که در هنگام کار با یک جدول، فایلهای اندیس آن به حافظه اصلی آورده میشوند (البته ممکن است که بخشی از فایلهای اندیس به حافظه اصلی نیایند). این در حالی است که فایل اصلی جدول در حافظه جانبی قرار دارد. بنابراین در هنگام بازیابی یک رکورد از برای یافتن محل ان رکورد نیازی به مراجعه زیاد به حافظه جانبی نیست. بلکه در حافظه اصلی بسرعت با یک عمل جستجو اشاره گر مربوط به رکورد مورد نظر در حافظه جانبی پیدا شده و مستقیما به آدرس همان رکورد میرویم و آن را میخوانیم. به این دسترسی، دسترسی مستقیم (direct access) می گوییم.
دسترسی ترتیبی :
در برخی از روشهای اندیس گذاری علاوه بر دسترسی مستقیم امکان دسترسی بصورت ترتیبی نیز وجود دارد. در دسترسی ترتیبی این امکان وجود دارد که از یک رکورد خاص در جدول اصلی بتوانیم رکوردهای بعد از آن را به ترتیبی منطقی پیمایش کنیم. برای روشنتر شدن موضوع به شکل شماره 1 توجه کنید. در انتهای هر رکورد اشاره گری به رکورد منطقی بعدی مشاهده میکنید. این اشاره گرها امکان پیمایش و دسترسی ترتیبی را به ما میدهند. بعنوان مثال فرض کنید قصد داریم تمامی رکوردهای حاوی کلید Perryridge را بازیابی نماییم. از آنجایی که در جدول اندیس تنها برای یکی از رکوردهای حاوی این کلید اندیس داریم، برای بازیابی باقی رکوردها چه باید کرد؟ در چنین شرایطی ابتدا با دسترسی مستقیم اولین رکورد حاوی Perryridge را پیدا کرده و آن را بازیابی میکنیم. سپس از طریق اشاره گر انتهای آن رکورد، میتوان به رکورد بعدی آن دست یافت و به همین ترتیب میتوان یک به یک به رکوردهای دیگر دسترسی ترتیبی پیدا نمود.
دقت کنید که رکوردهای جدول ما بصورت فیزیکی مرتب نیستند. اما اشاره گرهای انتهای رکوردها طوری مقدار دهی شده اند که بتوان آنها را بصورت مرتب شده پیمایش نمود.
اندیس اولیه (primary index) و اندیس ثانویه (secondary index) :
بر روی ستونهای یک جدول میتوان چندین اندیس را تعریف نمود. اولین اندیسی که بر روی یک ستون از یک جدول گذاشته میشود اندیس اولیه (primary index) نامیده میشود. عموما این اندیس به کلید اصلی نسبت داده میشود، چراکه اولین اندیسی است که بر روی جدول زده میشود. توجه داشته باشید که رکوردهای جدول اصلی بر اساس کلیدهای جستجوی اندیس اولیه بصورت منطقی (با استفاده اشاره گرهای انتهای رکورد که توضیح داده شد) مرتب هستند. بنابراین امکان دسترسی بصورت ترتیبی وجود دارد. وقتی پس از اندیس اولیه اقدام به اندیس گذاریهای دیگری میکنیم، اندیسهای ثانویه را ایجاد میکنیم که اندکی با اندیسهای اولیه متفاوت میباشند. در اندیسهای ثانویه دیگر امکان پیمایش و دسترسی ترتیبی وجود ندارد چراکه اشاره گرهای انتهای رکوردها بر اساس اندیس اصلی (اولیه) مرتب شده اند. بنابراین ما در اندیسهای ثانویه تنها دسترسی مستقیم خواهیم داشت. شکر زیر نمونه ای از یک اندیس ثانویه را نشان میدهد.
شکل 2 – اندیس ثانویه
همانطور که مشاهده میکنید علاوه بر اندیس اصلی (بر روی ستون 2) بر روی سومین ستون این جدول اندیس ثانویه متراکم زده شده است. دقت کنید که هر اشاره گر از جدول اندیس به یک باکت (bucket) اشاره دارد. در هر باکت اشاره گر هایی وجود دارد که به رکورد هایی از جدول اصلی اشاره میکنند. فلسفه وجود باکتها اینست که در اندیسهای ثانویه امکان دسترسی ترتیبی وجود ندارد. بنابراین برای مقادیری تکراری در جدول (مثلا عدد 700) نمیتوان از اشاره گرهای انتهای رکوردها استفاده نمود. در چنین شرایطی در باکتها اشاره گر مربوط به تمامی رکوردهای حاوی مقادیر تکراری یک کلید را نگهداری میکنیم تا بتوان به انها دسترسی مستقیم داشت. همانطور که مشاهده میکنید برای بازیابی رکوردهای حاوی مقدار 700 ابتدا از جدول اندیس (که مرتب است) باکت مربوطه را پیدا کرده و سپس از طریق اشاره گرهای موجود در این باکت به رکوردهای حاوی مقدار 700 دستیابی پیدا میکنیم.
اندیسهای تنک (sparse index) :
در این نوع از اندیسها بر خلاف اندیسهای متراکم، تنها به ازای برخی از کلیدهای جستجو در جدول اندیس اشاره گر نگهداری میکنیم. بهمین دلیل فایل اندیس ما کوچکتر خواهد بود (نسبت به اندیس متراکم). در مورد اندیسهای تنک نیز امکان دسترسی ترتیبی وجود دارد. در شکل زیر نمونه از اندیس تنک (sparse) را مشاهده میکنید.
شکل 3 – اندیس تنک (sparse index)
همانند شکل 1، در این شکل نیز اندیس اولیه بر روی ستون دوم زده شده است. اما این بار از اندیس تنک استفاده گردیده است. مشاهده میکنید که از میان مقادیر مختلف این ستون تنها برای سه کلید Brighton، Perryridge و Redwood در جدول اندیس رکورد درج شده است. بنابراین برای دست یابی به کلیدهای دیگر باید ابتدا محل تقریبی آن را با جستجو بر روی جدول اندیس پیدا نمود و سپس از طریق پیمایش ترتیبی به رکورد مورد نظر دست یافت. بعنوان مثال برای بازیابی رکورد حاوی مقدار Mianus ابتدا در جدول اندیس کلیدی که از Mianus کوچکتر باشد (یعنی Brighton ) را پیدا میکنیم. سپس به رکورد حاولی Brighton می رویم و از آنجا با استفاده از اشاره گرهای انتهایی رکوردها به سمت رکورد حاوی Mianus حرکت میکنیم تا به آن برسیم.
نکته بسیار مهمی که در مورد اندیسهای تنک مطرح میشود اینست که سیستم چگونه باید تشخیص دهد که کدام کلیدها را در جدول اندیس نگهداری کند. این تصمیم به مفهوم بلاکهای حافظه و اندازه انها باز میگردد. میدانیم که واحد خواندن اطلاعات از حافظه بر اساس بلاکها میباشد. این بدان معنی است که در هنگام خواندن رکوردهای جداول بانک اطلاعاتی، عمل خواندن بصورت بلاکی انجام میشود. هنگامی که بر روی یک جدول میخواهیم اندیس تنک بزنیم ابتدا باید ببینیم این جدول چند بلاک از حافظه را اشغال کرده است. سپس رکوردهای اول هر بلاک را پیدا کرده و به ازای هر بلاک آدرس و کلید جستجوی رکورد اول آن را در جدول اندیس نگهداری کنیم. بدین ترتیب ما به ازای هر بلاک از جدول یک رکورد در فایل اندیس خواهیم داشت و با تخصیص بلاکهای جدید به ان، طبیعی است که اندیسهای جدید نیز در فایل اندیس ذخیره خواهند شد.
اندیسهای چند سطحی (multi-level index)
در دنیایی واقعی معمولا تعداد رکوردهای جداول مورد استفاده بسیار بزرگ است و این اندازه دائما در حال زیاد شدن میباشد. افزایش اندازه جداول باعث میشود که اندازه فایلهای اندیس نیز رفته رفته زیاد شود. گفتیم برای کارایی هرچه بیشتر باید جدول اندیس مورد استفاده به حافظه اصلی آورده شود تا تعداد دسترسیهای ما به حافظه جانبی تا حد امکان کاهش یابد. اما اگر اندازه فایل اندیس ما بسیار بزرگ باشد ممکن است حجم زیادی از حافظه اصلی را بگیرد یا اینکه در حافظه اصلی فضای کافی برای ان وجود نداشته باشد. در چنین شرایطی از اندیسهای چند سطحی استفاده میشود. به بیان دیگر بر روی جدول اندیس نیز اندیس زده میشود. تعداد سطوح اندیس ما بستگی به اندازه جدول اصلی دارد و هر چه این اندازه بزرگتر شود، ممکن است باعث افزایش تعداد سطوح اندیس شود. در شکل زیر ساختار یک اندیس دو سطحی را مشاهده میکنید.
نکته مهم در مورد اندیسهای چند سطحی اینست که اندیسهای سطوح خارجی (outer index) از نوع تنک هستند. این مسئله به این دلیل است که اندازه اندیسها کوچکتر شود. چراکه اگر اندیس خارجی از نوع متراکم باشد به این معناست که به ازای هر رکورد غیر تکراری باید یک رکورد در فایل اندیس نیز آورده شود و این مسئله باعث بزرگ شدن اندیس میشود. بهمین دلیل سطوح خارجی را در اندیسهای چند سطحی از نوع تنک میگیرند. تنها آخرین سطحی که مستقیما به جدول اصلی اشاره میکند از نوع متراکم است. به این سطح از اندیس، اندیس داخلی (inner index) گفته میشود.
بروز نگهداشتن اندیسها :
با انجام عملیات درج و حذف بروی جداول، جداول اندیس مربوطه نیز باید بروز رسانی شوند. در این بخش قصد داریم به نحوه بروز رسانی جداول اندیس در زمان حذف و درج رکورد بپردازیم.
بروز رسانی در زمان حذف :
اندیس متراکم :
هنگامی که رکوردی از جدول اصلی حذف میشود، در صورتی که بر روی ستونهای آن اندیسهای متراکم داشته باشیم، پس از حذف رکورد اصلی باید ابتدا کلید جستجوی ستون مربوط را در جدول اندیس پیدا کنیم. در صورتی که از این کلید تنها یک مقدار در جدول اصلی وجود داشته باشد، اندیس آن را از فایل اندیس حذف کرده و اشاره گرهای انتهای رکوردها را بروز رسانی میکنیم. اما اگر از کلید مورد نظر چندین مورد وجود داشته باشد نباید رکورد مورد نظر در جدول اندیس پاک شود. بلکه تنها ممکن است نیاز به ویرایش اشاره گر اندیس باشد. ویرایش در زمانی رخ میدهد که اشاره گر جدول اندیس مستقیما به رکوردی اشاره کند که حذف شده باشد، در این صورت باید اشاره گر اندیس را ویراش نمود تا به رکورد بعدی اشاره نماید.
اندیس تنک :
همانند روش قبل ابتدا رکورد اصلی را از جدول حذف میکنیم. سپس در فایل اندیس بدنبال کلید جستجوی مربوط به رکورد حذف شده میگردیم. در صورتی که کلید مورد نظر در جدول اندیس پیدا شد کلید جستجوی رکورد بعدی در جدول اصلی را جایگزین آن میکنیم. چنانچه کلید مربوط به رکورد بعدی در جدول اندیس وجود داشته باشد نیازی به جایگزینی نیست و باید فقط عمل حذف اندیس را انجام داد.
اگر کلید مورد جستجو در جدول اندیس وجود نداشته باشد نیاز به انجام هیچ عملی نیست. در پایان باید اشاره گرهای انتهای رکوردها را ویرایش نمود تا ترتیب منطقی برای پیمایش ترتیبی حفظ شود.
بروز رسانی در زمان درج:
اندیس متراکم:
در هنگام درج یک رکورد جدید، ابتدا باید کلید موجود در رکورد جدید را در جدول اندیس جستجو نمود. در صورتی که کلید مورد نظر در جدول اندیس یافت نشد، باید رکوردی جدیدی در فایل اندیس درج کرد و اشاره گر آن طوری مقدار دهی نمود تا به رکورد جدید اشاره نماید. اگر کلید مورد نظر در جدول اندیس وجود داشته باشد دیگر نیازی بروز رسانی اندیسها نیست و تنها کافی است اشاره گرهای انتهای رکوردها بروز رسانی شوند.
اندیس تنک :
در مورد اندیسهای تنک کمی پیچیدگی وجود دارد. در صورتی که رکورد جدید باعث تخصیص بلاک (block) جدیدی از حافظه به جدول شود، باید به ازای آن بلاک یک اندیس در جدول اندیسها ایجاد شود و آدر آن بلاک را (که در واقع آدرس رکورد جدید نیز میشود) در اشاره گرد اندیس قرار داد. اما درغیز این صورت ( در صورتی که رکورد در بلاکهای موجود ذخیره شود) نیازی به بروز رسانی جدول اندیسها وجود ندارد.
نوع دیگری از اندیسهای مرتب نیز وجود دارد که اندیس های B-Tree هستند که در سیستمهای اطلاعاتی دنیای واقعی بیشتر از آنها استفاده میشود. به امید خدا در مطالب بعدی این اندیسها را نیز مورد بررسی قرار خواهیم داد.
موفق و پیروز باشید.
برای اینکه مطمئن شوید که آیا دات نت فریم ورک نصب شده است، میتوانید در شاخهی system32 سیستم، وجود فایل MSCorEE.dll را بررسی نمایید. البته بر روی یک سیستم میتواند نسخههای مختلفی از یک دات نت فریم ورک نصب باشد. برای آگاهی از اینکه چه نسخههایی بر روی سیستم نصب است باید مسیرهای زیر را مورد بررسی قرار دهید:
%SystemRoot%\Microsoft.NET\Framework %SystemRoot%\Microsoft.NET\Framework64
بستهی دات نت فریمورک شامل ابزار خط فرمانی به نام CLRVer.exe میشود که همهی نسخههای نصب شده را نشان میدهد. این ابزار با سوییچ all میتواند نشان دهد که چه پروسههایی در حال حاضر دارند از یک نسخهی خاص استفاده میکنند. یا اینکه ID یک پروسه را به آن داده و نسخهی در حال استفاده را بیابیم.
قبل از اینکه پروسهی بارگیری یک اسمبلی را بررسی کنیم، بهتر است به نسخههای 32 و 64 بیتی ویندوز، نگاهی بیندازیم:
یک برنامه در حالت عمومی بر روی تمامی نسخهها قابل اجراست و نیازی نیست که توسعه دهنده کار خاصی انجام دهد. ولی اگر توسعه دهنده نیاز داشته باشد که برنامه را محدود به پلتفرم خاصی کند، باید از طریق برگه build در projectProperties در قسمت PlatformTarget معماری پردازنده را انتخاب کند:
بسته به پلتفرمی که برای توزیع انتخاب میکنید، کامپایلر به ساخت اسمبلیهای با هدرهای (+)P32 میپردازد. مایکروسافت دو ابزار خط فرمان را به نامهای DumpBin .exe و CoreFlags .exe در راستای آزمایش و بررسی هدرهای تولید شده توسط کامپایلر ارائه کرده است.
موقعی که شما یک فایل اجرایی را اجرا میکنید، ابتدا هدرها را خوانده و طبق اطلاعات موجود تصمیم میگیرد برنامه به چه شکلی اجرا شود. اگر دارای هدر p32 باشد قابل اجرا بر روی سیستمهای 32 و 64 بیتی است و اگر +PE32 باشد روی سیستمهای 64 بیتی قابل اجرا خواهد بود. همچنین به بررسی معماری پردازنده که در قسمت هدر embed شده، پرداخته تا اطمینان کسب کند که با خصوصیات پردازنده مقصد مطابقت میکند.
نسخههای 64 بیتی ارائه شده توسط مایکروسافت دارای فناوری به نام WOW64 یا Windows On Windows64 هستند که اجازهی اجرای برنامههای 32 بیت را روی نسخههای 64 بیتی، میدهند.
جدول زیر اطلاعاتی را ارائه میکند که در حالت عادی برنامه روی چه سیستمهایی ارائه شده است و اگر آنرا محدود به نسخههای 32 یا 64 بیتی کنیم، نحوهی اجرا آن بر روی سایر پلتفرمها چگونه خواهد بود.
%SystemRoot%\SysWow64
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
T attachedEntity = set.Find(entity.Id); var attachedEntry = dbContext.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity);
public DbContext() { this.Configuration.ProxyCreationEnabled = false; this.Configuration.LazyLoadingEnabled = false; this.Configuration.AutoDetectChangesEnabled = false; }
dbContext.Entry(entity).State = EntityState.Unchanged ; dbContext.Entry(entity).State = EntityState.Added ; //or Dbset.Add(entity) dbContext.Entry(entity).State = EntityState.Modified ; dbContext.Entry(entity).State = EntityState.Deleted ; // or Dbset.Remove(entity)
dbContext.Entry(entity).State = EntityState.Detached;
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);// groupstate=EntityState.Added
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(group).State = EntityState.Unchanged; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);// groupstate=EntityState.Unchanged
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(customer).State = EntityState.Unchanged; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);//// groupstate=EntityState.Unchanged
var customer = dbContext.Customers.FirstOrDefault(); var customerAsNoTracking = dbContext.Customers.AsNoTracking().FirstOrDefault(); var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Unchanged var customerstateAsNoTracking = dbContext.Entry(customerAsNoTracking).State;// customerstate=EntityState.Detached
var Entries = dbContext.ChangeTracker.Entries(); var AddedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Added); var ModifiedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Modified); var UnchangedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Unchanged); var DeletedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Deleted); var DetachedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Detached);//* not working !
var CustomersWithGroup = dbContext.Customers.AsNoTracking().Include("Group").ToList(); var CustomerFull = dbContext.Customers.AsNoTracking().Include("Group").Include("Bills").Include("Bills.BillDetails").ToList();
public virtual void AddOrUpdate(T entity) { if (entity.Id == 0) Add(entity); else Update(entity); }
5) اگر بخواهیم موجودیتهای رابطه ای در دیتا گرید ویو (ویندوز فرم) نشون بدیم باید چه کار کنیم؟
گرید ویو در ویندوز فرم قادر به نشون دادن فیلدهای رابطه ای نیست برای حل این مشکل میتونیم یک نوع ستون جدید برای گرید ویو تعریف کنیم و برای نشون دادن فیلدهای رابطه ای از این نوع ستون استفاده کنیم:
public class DataGridViewChildRelationTextBoxCell : DataGridViewTextBoxCell { protected override object GetValue(int rowIndex) { try { var bs = (BindingSource)DataGridView.DataSource; var cl = (DataGridViewChildRelationTextBoxColumn)DataGridView.Columns[ColumnIndex]; return getChildValue(bs.List[rowIndex], cl.DataPropertyName).ToString(); } catch (Exception) { return ""; } } private object getChildValue(object dataSource, string childMember) { int nextPoint = childMember.IndexOf('.'); if (nextPoint == -1) return dataSource.GetType().GetProperty(childMember).GetValue(dataSource, null); string proName = childMember.Substring(0, nextPoint); object newDs = dataSource.GetType().GetProperty(proName).GetValue(dataSource, null); return getChildValue(newDs, childMember.Substring(nextPoint + 1)); } } public class DataGridViewChildRelationTextBoxColumn : DataGridViewTextBoxColumn { public string DataMember { get; set; } public DataGridViewChildRelationTextBoxColumn() { CellTemplate = new DataGridViewChildRelationTextBoxCell(); } }
نحوه استفاده را در ادامه میبینید. این روش توسط ویزارد گریدویو هم قابل استفاده است. موقع Add کردن Column نوع اون رو روی DataGridViewChildRelationTextBoxColumn تنظیم کنید.
GroupNameColumn= new DataGridViewChildRelationTextBoxColumn(); //from your class GroupNameColumn.HeaderText = "گروه مشتری"; GroupNameColumn.DataPropertyName = "Group.Name"; //EF Property: Customer.Group.Name GroupNameColumn.Visible = true; GroupNameColumn.Width = 300; DataGridView.Columns.Add(GroupNameColumn);
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;
Mvc File Manager
Admin (Full access) FileManager_Read(readonly access) FileManager_Write(Creat Folder & upload file) FileManager_Change(Move & Rename) FileManager_Delete(Delete file and Folder
اگر به تصویر دقت کنید، در ستون Model آن، اطلاعات باینری ذخیره شدهاند. شاید در وهلهی اول اینطور به نظر برسد که این ستون حاوی هش نقل و انتقالات صورت گرفتهاست؛ اما ... خیر. اطلاعات این ستون، GZip شدهی یک رشتهی XML ایی یا همان EDMX معادل مدلها و نگاشتهای برنامه است.
در کدهای ذیل، نمونه مثالی را از نحوهی خواندن این اطلاعات، مشاهده میکنید:
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.IO.Compression; using System.Xml.Linq; namespace EF_General { public static class InsideMigrations { public static void PrintFirstMigrationModel() { const string connectionString = "Data Source=(local);Initial Catalog=TestDbIdentity;Integrated Security = true"; const string sqlToExecute = "select top 1 model from __MigrationHistory"; using (var connection = new SqlConnection(connectionString)) { connection.Open(); using (var command = new SqlCommand(sqlToExecute, connection)) { using (var reader = command.ExecuteReader()) { if (!reader.HasRows) { throw new KeyNotFoundException("Nothing to display."); } while (reader.Read()) { var model = (byte[]) reader["model"]; var decompressed = decompressMigrationModel(model); Console.WriteLine(decompressed); } } } } } private static XDocument decompressMigrationModel(byte[] bytes) { using (var memoryStream = new MemoryStream(bytes)) { using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { return XDocument.Load(gzipStream); } } } } }
بر اساس این اطلاعات، EF کاری به ساختار فعلی بانک اطلاعاتی شما ندارد. زمانیکه Add-Migration را اجرا میکنید، به جدول migrations مراجعه کرده، آخرین رکورد آنرا یافته و سپس اطلاعات آنرا از حالت فشرده خارج و XML نهایی آنرا استخراج میکند. در ادامه اطلاعات این فایل XML را با معادل مدلهای فعلی برنامه مقایسه میکند. اگر این دو یکی نبودند، اسکریپت اعمال تغییرات را تولید خواهد کرد.
مورد دیگری که در این جدول حائز اهمیت است، ستون ContextKey آن است: «رفع مشکل Migration با تغییر NameSpace در EF»
دریافت Twitter Bootstrap
محل اصلی دریافت Twitter Bootstrap، آدرس ذیل است:
البته ما از آن در اینجا به شکل خام فوق استفاده نخواهیم کرد؛ زیرا نیاز است قابلیتهای استفاده در محیطهای راست به چپ فارسی نیز به آن اضافه شود. برای این منظور میتوانید از یکی از دو بسته نیوگت ذیل استفاده نمائید:
RTL Twitter Bootstrap, https://nuget.org/packages/Twitter.Bootstrap.RTL
و یا حتی از منابع سایت http://rbootstrap.ir نیز میتوان استفاده کرد.
برای نمونه دستور زیر را در کنسول پاورشل ویژوال استودیو وارد نمائید تا اسکریپتها و فایلهای CSS مورد نیاز به پروژه جاری اضافه شوند:
PM> Install-Package Twitter.BootstrapRTL
در اینجا فایلهای min، نگارشهای فشرده شده فایلهای js یا css هستند که با توجه به امکانات اضافه شده به ASP.NET MVC4، از آنها استفاده نخواهیم کرد و برای افزودن و تعریف آنها از امکانات Bundling and minification توکار فریم ورک ASP.NET MVC به نحوی که در ادامه توضیح داده خواهد شد، استفاده میکنیم.
فایلهای png اضافه شده، آیکونهای مخصوص Twitter Bootstrap هستند که اصطلاحا به آنها sprite images نیز گفته میشود. در این نوع تصاویر، تعداد زیادی آیکون در کنار هم، برای بهینه سازی تعداد بار رفت و برگشت به سرور جهت دریافت تصاویر، طراحی شده و قرار گرفتهاند.
فایلهای js این مجموعه اختیاری بوده و برای استفاده از ویجتهای Twitter Bootstrap مانند آکاردئون کاربرد دارند. این فایلها برای اجرا، نیاز به jQuery خواهند داشت.
افزودن تعاریف اولیه Twitter Bootstrap به یک پروژه ASP.NET MVC
امکانات Bundling and minification در نوع پروژههای نسبتا جامعتر ASP.NET MVC به صورت پیش فرض لحاظ شده است. اما اگر یک پروژه خالی را شروع کردهاید، نیاز است بسته نیوگت آنرا نیز نصب کنید:
PM> Install-Package Microsoft.AspNet.Web.Optimization
using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Optimization; namespace Mvc4TwitterBootStrapTest.Helper { /// <summary> /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are /// included in the order you register them. /// </summary> public class AsIsBundleOrderer : IBundleOrderer { public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files) { return files; } } public static class BundleConfig { private static void addBundle(string virtualPath, bool isCss, params string[] files) { BundleTable.EnableOptimizations = true; var existing = BundleTable.Bundles.GetBundleFor(virtualPath); if (existing != null) return; Bundle newBundle; if (HttpContext.Current.IsDebuggingEnabled) { newBundle = new Bundle(virtualPath); } else { newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify()); } newBundle.Orderer = new AsIsBundleOrderer(); foreach (var file in files) newBundle.Include(file); BundleTable.Bundles.Add(newBundle); } public static IHtmlString AddScripts(string virtualPath, params string[] files) { addBundle(virtualPath, false, files); return Scripts.Render(virtualPath); } public static IHtmlString AddStyles(string virtualPath, params string[] files) { addBundle(virtualPath, true, files); return Styles.Render(virtualPath); } public static IHtmlString AddScriptUrl(string virtualPath, params string[] files) { addBundle(virtualPath, false, files); return Scripts.Url(virtualPath); } public static IHtmlString AddStyleUrl(string virtualPath, params string[] files) { addBundle(virtualPath, true, files); return Styles.Url(virtualPath); } } }
پس از افزودن کلاسهای کمکی فوق، به فایل layout پروژه مراجعه کرده و تعاریف ذیل را به ابتدای فایل اضافه نمائید:
@using Mvc4TwitterBootStrapTest.Helper <!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> @BundleConfig.AddStyles("~/Content/css", "~/Content/bootstrap.css", "~/Content/bootstrap-responsive.css", "~/Content/Site.css" ) @BundleConfig.AddScripts("~/Scripts/js", "~/Scripts/jquery-1.9.1.min.js", "~/Scripts/jquery.validate.min.js", "~/Scripts/jquery.unobtrusive-ajax.min.js", "~/Scripts/jquery.validate.unobtrusive.min.js", "~/Scripts/bootstrap.min.js" ) @RenderSection("JavaScript", required: false) </head>
<!DOCTYPE html> <html> <head> <title>Index</title> <link href="/Content/css?v=vsUQD0OJg4AJ-RZH8jSRRCu_rjl2U1nZrmSsaUyxoAc1" rel="stylesheet"/> <script src="/Scripts/js?v=GezdoTDiWY3acc3mI2Ujm_7nKKzh6Lu1Wr8TGyyLpW41"></script> </head>
مفاهیم پایهای Twitter Bootstrap
الف) Semantic class names
به عبارتی کلاسهای Twitter Bootstrap دارای نامهایی معنا دار و مفهومی میباشند؛ مانند کلاسهای CSSایی، به نامهای Succes، Error، Info و امثال آن. این نامها مفهومی را میرسانند؛ اما در مورد نحوه پیاده سازی آنها جزئیاتی را بیان نمیکنند.
برای نمونه میتوان کلاسی را به نام redText ایجاد کرد. هر چند این نام، توضیحاتی را در مورد علت وجودیاش بیان میکند، اما بسیار ویژه بوده و در مورد جزئیات پیاده سازی آن نیز اطلاعاتی را ارائه میدهد. در این حالت redText معنایی ندارد. چرا یک Text باید قرمز باشد؟ برای مثال این متن قرمز است چون مثلا شخصی، به آن رنگ ویژه علاقه دارد، یا اینکه قرمز است بخاطر نمایش خطایی در صفحه؟ به همین جهت در Twitter Bootstrap از نامهای مفهومی یاده شده، مانند Error استفاده میشود. نامهایی معنا دار اما بدون دقیق شدن در مورد ریز جزئیات پیاده سازی آنها. در این حالت میتوان قالب جدیدی را تدارک دید و با ارائه تعاریف جدیدی برای کلاس Error و نحوه نمایش دلخواهی را به آن اعمال نمود.
یا برای نمونه نام rightside را برای نمایش ستونی در صفحه، درنظر بگیرید. این نام بسیار ویژه است؛ اما Sematic name آن میتواند sidebar باشد تا بدون دقیق شدن در جزئیات پیاده سازی آن، در چپ یا راست صفحه قابل اعمال باشد.
Semantic class names کلیدهایی هستند جهت استفاده مجدد از قابلیتهای یک فریم ورک CSS.
ب) Compositional classes
اکثر کلاسهای Twitter Bootstrap دارای محدوده کاری کوچکی هستند و به سادگی قابل ترکیب با یکدیگر جهت رسیدن به نمایی خاص میباشند. برای مثال به سادگی میتوان به یک table سه ویژگی color، hover و width برگرفته شده از Twitter Bootstrap را انتساب داد و نهایتا به نتیجه دلخواه رسید؛ بدون اینکه نگران باشیم افزودن کلاس جدیدی در اینجا بر روی سایر کلاسهای انتساب داده شده، تاثیر منفی دارد.
ج) Conventions
برای استفاده از اکثر قابلیتهای این فریم ورک CSS یک سری قراردادهای پیش فرضی وجود دارند. برای مثال اگر از کلاس توکار pagination به همراه یک سری ul و li استفاده کنید، به صورت خودکار یک pager شکیل ظاهر خواهد شد. یا برای مثال اگر به یک html table کلاسهای table table-striped table-hover را انتساب دهیم، به صورت خودکار قراردادهای پیش فرض table مجموعه Twitter Bootstrap به آن اعمال شده، به همراه رنگی ساختن یک درمیان زمینه ردیفها و همچنین فعال سازی تغییر رنگ ردیفها با حرکت ماوس از روی آنها.
طرحبندی صفحات یک سایت به کمک Twitter Bootstrap
بررسی Grid layouts
Layout به معنای طرحبندی و چیدمان محتوا در یک صفحه است. یکی از متداولترین روشهای طرحبندی صفحات چه در حالت چاپی و چه در صفحات وب، چیدمان مبتنی بر جداول و گریدها است. از این جهت که نحوه سیلان و نمایش محتوا از چپ به راست و یا راست به چپ را به سادگی میسر میسازند؛ به همراه اعمال حاشیههای مناسب جهت قسمتهای متفاوت محتوای ارائه شده. Grid در طرحبندی، نمایش بصری نخواهد داشت اما در ساختار صفحه وجود داشته و مباحثی مانند جهت، موقعیت و یکپارچگی و یکدستی طراحی را سبب میشود.
به علاوه مرورگرها و مفهوم Grid نیز به خوبی با یکدیگر سازگار هستند. در دنیای HTML و ،CSS طراحیها بر اساس مفهوم ساختار مستطیلی اشیاء صورت میگیرد:
برای نمونه در اینجا تصویر CSS Box Model را مشاهده میکنید. به این ترتیب، هر المان دارای محدودهای مستطیلی با طول و عرض مشخص، به همراه ویژگیهایی مانند Margin، Border و Padding است.
در سالهای اولیه طراحی وب، عموما کارهای طراحی صفحات به کمک HTML Tables انجام میشد. اما با پختهتر شدن CSS، استفاده از Tables برای طراحی صفحات کمتر و کمتر گشت تا اینکه نهایتا فریم ورکهای CSS ایی پدید آمدند تا طراحیهای مبتنی بر CSS را با ارائه گریدها، سادهتر کنند. مانند Blue print، 960 GS و ... Twitter Bootstrap که طراحی مبتنی بر گریدهای CSS ایی را به مجموعه قابلیتهای دیگر خود افزوده است.
بررسی Fixed Grids
در اینجا در صفحه layout برنامه، یک Div دربرگیرنده دو Div دیگر را مشاهده میکنید:
<body> <div> <div> <h1> Title Title </h1> Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text </div> <div> @RenderBody() </div> </div> </body>
برای اینکار در Twitter Bootstrap از کلاسی به نام row استفاده میشود که بیانگر یک ردیف است. این کلاس را به خارجیترین Div موجود اعمال خواهیم کرد. در یک صفحه، هر تعداد row ایی را که نیاز باشد، میتوان تعریف کرد. داخل این ردیفها، امکان تعریف ستونهای مختلف و حتی تعریف ردیفهای تو در تو نیز وجود دارد. هر ردیف Twitter Bootstrap از 12 ستون تشکیل میشود و برای تعریف آنها از کلاس span استفاده میگردد. در این حالت جمع اعداد ذکر شده پس از span باید 12 را تشکیل دهند.
<body> <div class="row"> <div class="span7"> <h1> Title Title </h1> Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text </div> <div class="span5"> @RenderBody() </div> </div> </body>
در این تصویر، قسمت RenderBody کار رندر اکشن متد Index کنترلر Home برنامه را با Viewایی معادل کدهای ذیل، انجام داده است:
@{ ViewBag.Title = "Index"; } <h2> Index</h2> <div class="hero-unit"> <h2>@ViewBag.Message</h2> <p> This is a template to demonstrate the way to beautify the default MVC template using Twitter Bootstrap website. It is pretty simple and easy.</p> <p> <a href="http://asp.net/mvc" class="btn btn-primary btn-large" style="color: White;"> To learn more about ASP.NET MVC visit »</a></p> </div>
درک نحوه عملکرد Grid در Twitter Bootstrap
در مثال ذیل 5 ردیف را مشاهده میکنید:
<div class="row"> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> <div class="span1">1</div> </div> <div class="row"> <div class="span3">3</div> <div class="span4">4</div> <div class="span5">5</div> </div> <div class="row"> <div class="span5">5</div> <div class="span7">7</div> </div> <div class="row"> <div class="span3">3</div> <div class="span7 offset2">7 offset 2</div> </div> <div class="row"> <div class="span12">12</div> </div>
در ستون چهارم، از کلاس offset نیز استفاده شده است. این مورد سبب میشود ستون جاری به تعدادی که مشخص شده است به سمت چپ (با توجه به استفاده از حالت RTL در اینجا) رانده شود و سپس ترسیم گردد.
یا اینکه میتوان مانند ردیف آخر، یک ستون را به عرض 12 که در حقیقت 940 پیکسل است، ترسیم نمود.
برای اینکه بتوانیم این گرید تشکیل شده و همچنین ستونها را بهتر مشاهده کنیم، به فایل style.css سایت، تنظیم زیر را اضافه کنید:
[class*="span"] { background-color: lightblue; text-align: center; margin-top: 15px; }
نکته جالب این گرید، Responsive یا واکنشگرا بودن آن است. در این حالت، عرض مرورگر را کم و زیاد کنید. خواهید دید که ستونها در صورتیکه در عرض نمایشی جاری، قابل ارائه نباشند، به ردیفهای بعدی منتقل خواهند شد.
البته باید دقت داشت که این گرید هیچگاه یک ستون را نخواهد شکست. برای نمونه ردیف آخر، همواره با همان عرض ثابتش نمایش داده میشود و با کوچکتر کردن اندازه مرورگر، یک اسکرول افقی برای نمایش محتوای آن ظاهر خواهد شد.
یک نکته
اگر نمیخواهید که چنین رفتار واکنشگرایی بروز کند، نیاز است کلیه ردیفها را در div ایی با کلاسی به نام container محصور کنید.
به این ترتیب ابتدا گرید نمایش داده شده، در میانه صفحه ظاهر خواهد شد (پیشتر از سمت راست شروع شده بود). همچنین دیگر با کوچک و بزرگ شدن اندازه مرورگر، ستونها به شکل یک پشته بر روی هم قرار نخواهند گرفت. (اگر پس از این تنظیم، چنین قابلیتی را مشاهده نکردید و هنوز هم طراحی، واکنشگرا بود، تعریف bootstrap-responsive.css را نیاز است برای آزمایش، از هدر صفحه حذف کنید)
بررسی Fluid Grids
به گرید قسمت قبل از این جهت Fixed Grid گفته میشود که عرض هر span آن با یک عدد مشخص تعیین گشته است. اما در حالت Fluid Grid، عرض هر span برحسب درصد تعیین میشود. بکارگیری درصد در اینجا به معنای امکان تغییر عرض یک ستون بر اساس عرض جاری Container آن است. در اینجا span12 دارای عرض 100 درصد خواهد بود.
در مثال قبل، برای استفاده از Fluid grids، تنها کافی است هرجایی کلاسی مساوی row وجود دارد، به row-fluid تغییر کند. همچنین کلاس container را به container-fluid تغییر دهید.
برای آزمایش آن، اندازه و عرض نمایشی مرورگر خود را تغییر دهید. اینبار مشاهده خواهید کرد که برخلاف حالت Fixed Grid، عرض ستونها به صورت خودکار کم و زیاد میشوند. این مورد بر روی محتوای قرار گرفته در این ستونها نیز تاثیر گذار است. برای مثال اگر یک تصویر را در حالت Fluid grid در ستونی قرار دهید، با تغییر عرض مرورگر، اندازه این تصویر نیز تغییر خواهند کرد؛ اما در حالت Fixed Grid خیر.
حالت Fluid، شیوه متداول استفاده از bootstrap در اکثر سایتهای مهمی است که تابحال از این فریم ورک CSS استفاده کردهاند.
مروری بر طراحی واکنشگرا یا Responsive
این روزها تعدادی از کاربران، با استفاده از ابزارهای موبایل و تبلتها از وب سایتها بازدید میکنند. هر کدام از اینها نیز دارای اندازه نمایشی متفاوتی میباشند. بنابراین نیاز خواهد بود تا حالت بهینهای را جهت اینگونه وسایل نیز طراحی نمود. حالت بهینه در اینجا به معنای قابل خواندن بودن متون، امکان استفاده از لینکهای ورود به صفحات مختلف و همچنین حذف اسکرول و مباحث زوم جهت مشاهده صفحات است.
یکی از ویژگیهای CSS به نام media چنین قابلیتی را فراهم میسازد. برای نمونه قسمتی از فایل bootstrap-responsive.css دارای چنین تعاریفی است:
@media (min-width: 768px) and (max-width: 979px) { .hidden-desktop { display: inherit !important; }
Bootstrap برای مدیریت اندازههای مختلف وسایل موبایل، شیوهنامههای خاصی را تدارک دیده است که از اندازه px480 و یا کمتر، تا px1200 و یا بیشتر را پوشش میدهد.
به این ترتیب با اندازه px940 که پیشتر در مورد آن بحث شد، اندازه span12 به صورت خودکار به اندازههای متناسب با صفحات نمایشی کوچکتر تنظیم میگردد. همچنین برای اندازههای صفحات کوچکتر از 768px به صورت خودکار از Fluid columns استفاده میگردد.
تنها کاری که برای اعمال این قابلیت باید صورت گیرد، افزودن تعاریف فایل bootstrap-responsive.css به هدر صفحه است که در قسمت قبل انجام گردید. این فایل باید پس از فایل اصلی bootstrap.css اضافه شود.
کلاسهای کمکی طراحی واکنشگرا
Bootstrap شامل تعدادی کلاس کمکی در فایل bootstrap-responsive.css خود میباشد شامل visible-phone، visible-tablet و visible-desktop به علاوه hidden-phone، hidden-tablet و hidden-desktop. به این ترتیب میتوان محتوای خاصی را جهت وسایل ویژهای نمایان یا مخفی ساخت.
برای مثال محتوای مشخص شده با کلاس hidden-desktop، در اندازه وسایل دسکتاپ نمایش داده نخواهد شد.
برای آزمایش آن، شش div را با کلاسهای یاد شده و محتوایی دلخواه ایجاد کرده و سپس اندازه عرض مرورگر را تغییر دهید تا بهتر بتوان مخفی یا نمایان ساختن محتوا را بر اساس اندازه صفحه جاری درک کرد.
یکی از کاربردهای این قابلیت، قرار دادن تبلیغاتی با اندازههای تصاویری مشخص برای وسایل مختلف است؛ بجای اینکه منتظر شویم تا Fluid layout اندازه تصاویر را به صورت خودکار کوچک یا بزرگ کند، که الزاما بهترین کیفیت را حاصل نخواهد ساخت.
<div class="container-fluid"> <div class="row-fluid"> <div class="span4"> <div class="visible-phone"> visible-phone</div> </div> <div class="span4"> <div class="visible-tablet"> visible-tablet</div> </div> <div class="span4"> <div class="visible-desktop"> visible-desktop</div> </div> </div> </div> <div class="container-fluid"> <div class="row-fluid"> <div class="span4"> <div class="hidden-phone"> hidden-phone</div> </div> <div class="span4"> <div class="hidden-tablet"> hidden-tablet</div> </div> <div class="span4"> <div class="hidden-desktop"> hidden-desktop</div> </div> </div> </div>
با بالا رفتن تعداد اشیاء تعریف شده در SQL server ، نگهداری آنها نیز مشکلتر میشود. در این حالت تغییر کوچکی در یکی از اشیاء ممکن است باعث از کار افتادن قسمتی از سیستم شود. بنابراین قبل از هر گونه تغییری در یک شیء، ابتدا باید سایر اشیاء وابسته به آن را یافت و در نظر داشت ( dependencies ). برای این منظور ( impact analysis ) راهکارهای مختلفی در SQL server وجود دارد که در ادامه به آنها خواهیم پرداخت:
الف) استفاده از امکانات management studio (اس کیوال سرور 2005 به بعد)
سادهترین راه ممکن که گزارش مفصلی را نیز ارائه میدهد، کلیک بر روی یک شیء در management studio و انتخاب گزینه view dependencies است (شکل زیر).
در صفحه ظاهر شده میتوان اشیایی را که شیء مورد نظر به آنها وابسته است، مشاهده نمود یا برعکس (اشیایی که عملکرد آنها وابسته به شیء انتخابی است را نیز میتوان ملاحظه کرد).
ب) کوئری گرفتن از جداول سیستمی
امکانات قسمت قبل را با استفاده از اطلاعات جدول syscomments نیز میتوان شبیه سازی کرد. در این جدول اطلاعات تعاریف کلیه view ، trigger ، رویههای ذخیره شده و غیره نگهداری میشود. برای مثال فرض کنید قصد داریم در جدول Orders دیتابیس Northwind ، نام فیلد OrderDate را تغییر دهیم. قبل از اینکار بهتر است کوئری زیر را اجرا کنیم تا نام اشیاء وابسته را بدست آوریم:
SELECT NAME
FROM syscomments c
JOIN sysobjects o
ON c.id = o.id
WHERE TEXT LIKE '%OrderDate%'
AND TEXT LIKE '%Orders%'
این روش انعطاف پذیری بیشتری را نسبت به امکانات قسمت الف ، ارائه میدهد. برای نمونه فرض کنید میخواهید در یک دیتابیس کلیه اشیایی که عملیات delete را انجام میدهند پیدا کنید (جستجوی اشیاء حاوی یک عبارت خاص). در این مورد خواهیم داشت:
SELECT NAME
FROM syscomments c
JOIN Northwind.dbo.sysobjects o
ON c.id = o.id
WHERE TEXT LIKE '%delete%'
جدول سیستمی دیگری در اس کیوال سرور به نام sysdepends وجود دارد که اطلاعات وابستگیهای اشیاء در آنها نگهداری میشود. برای دسترسی به اطلاعات این جدول ، اس کیوال سرور رویه ذخیره شده سیستمی sp_depends را ارائه داده است. برای مثال فرض کنید میخواهیم لیست اشیایی را که به جدول Oredres دیتابیس Northwind وابسته هستند، پیدا کنیم. در این حالت داریم:
USE Northwind
EXEC sp_depends 'Orders'
د) استفاده از schema view
با استفاده از view سیستمی INFORMATION_SCHEMA.ROUTINES ، که از ترکیب جداول syscolumns و sysobjects ایجاد شده است نیز میتوان عملکرد sp_depends را شبیه سازی کرد اما جداول و view ها از گزارش آن حذف شدهاند.
SELECT routine_name,
routine_type
FROM INFORMATION_SCHEMA.ROUTINES
WHERE routine_definition LIKE '%Orders%'
ه) استفاده از برنامه SQL Dependency Tracker
نسخه آزمایشی برنامه ذکر شده را از این آدرس میتوان دریافت کرد.
همانطور که ملاحظه میکنید این جستجوها بر روی اطلاعات ذخیره شده در اس کیوال سرور صورت میگیرند و اگر در کدهای خود در خارج از اس کیوال سرور مخلوطی از عبارات اس کیوال را داشته باشید، نگهداری آنها بسیار مشکل خواهد بود. بنابراین تا حد ممکن باید عملیات مرتبط را در دیتابیس و توسط اشیاء اس کیوال سرور مانند رویههای ذخیره شده، view ها و امثال آنها انجام داد تا این جدا سازی بهخوبی صورت گرفته و در زمان نیاز به انجام تغییرات، ردگیری اشیاء وابسته بهسادگی صورت گیرد.