/* * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). * Copyright (c) 2013, Taylor Hornby * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Text; using System.Security.Cryptography; namespace PasswordHash { /// <summary> /// Salted password hashing with PBKDF2-SHA1. /// Author: havoc AT defuse.ca /// www: http://crackstation.net/hashing-security.htm /// Compatibility: .NET 3.0 and later. /// </summary> public class PasswordHash { // The following constants may be changed without breaking existing hashes. public const int SALT_BYTE_SIZE = 24; public const int HASH_BYTE_SIZE = 24; public const int PBKDF2_ITERATIONS = 1000; public const int ITERATION_INDEX = 0; public const int SALT_INDEX = 1; public const int PBKDF2_INDEX = 2; /// <summary> /// Creates a salted PBKDF2 hash of the password. /// </summary> /// <param name="password">The password to hash.</param> /// <returns>The hash of the password.</returns> public static string CreateHash(string password) { // Generate a random salt RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider(); byte[] salt = new byte[SALT_BYTE_SIZE]; csprng.GetBytes(salt); // Hash the password and encode the parameters byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); return PBKDF2_ITERATIONS + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash); } /// <summary> /// Validates a password given a hash of the correct one. /// </summary> /// <param name="password">The password to check.</param> /// <param name="correctHash">A hash of the correct password.</param> /// <returns>True if the password is correct. False otherwise.</returns> public static bool ValidatePassword(string password, string correctHash) { // Extract the parameters from the hash char[] delimiter = { ':' }; string[] split = correctHash.Split(delimiter); int iterations = Int32.Parse(split[ITERATION_INDEX]); byte[] salt = Convert.FromBase64String(split[SALT_INDEX]); byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]); byte[] testHash = PBKDF2(password, salt, iterations, hash.Length); return SlowEquals(hash, testHash); } /// <summary> /// Compares two byte arrays in length-constant time. This comparison /// method is used so that password hashes cannot be extracted from /// on-line systems using a timing attack and then attacked off-line. /// </summary> /// <param name="a">The first byte array.</param> /// <param name="b">The second byte array.</param> /// <returns>True if both byte arrays are equal. False otherwise.</returns> private static bool SlowEquals(byte[] a, byte[] b) { uint diff = (uint)a.Length ^ (uint)b.Length; for (int i = 0; i < a.Length && i < b.Length; i++) diff |= (uint)(a[i] ^ b[i]); return diff == 0; } /// <summary> /// Computes the PBKDF2-SHA1 hash of a password. /// </summary> /// <param name="password">The password to hash.</param> /// <param name="salt">The salt.</param> /// <param name="iterations">The PBKDF2 iteration count.</param> /// <param name="outputBytes">The length of the hash to generate, in bytes.</param> /// <returns>A hash of the password.</returns> private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) { Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt); pbkdf2.IterationCount = iterations; return pbkdf2.GetBytes(outputBytes); } } }
تا حدود 4 سال پیش بین کیفیت oracle db و sql server اختلاف فاحشی وجود داشت. چه از نظر سرعت و چه از نظر دیگر امکانات، اوراکل کاملا برتر از رقیب خود بود. در نسخهی sql server 2012، امکانات قابل توجهی به محصول شرکت مایکروسافت افزوده شد. از مهمترین این امکانات میتوان به ویژگی AlwaysOn و ColumnStore Indexها اشاره کرد. امکانات این نسخه باعث شد که اختلاف بین oracle db و sql server تا حدی کاهش یابد. مایکروسافت سرانجام در نسخهی sql server 2014 خود تغییرات اساسی بوجود آورد. مهمترین این تغییرات ایجاد موتور درونی In-Memory OLTP میباشد که برای تراکنشهای درون حافظه بهینه شده است. با استفاده از امکانات این نسخه میتوان بدون نیاز به دوباره نویسی محصولات، سرعت اجرای کوئریهای آنها را به طور متوسط ده برابر کرد. در شکل ذیل ساختار جدید sql server مشاهده میشود.
شرکت بوین که یک شرکت مشهور ارائه خدمات آنلاین و پیش بینی بازیهای ورزشی است و در هر لحظه، کاربران آنلاین بسیاری در وب سایت شرکت، کوئری اجرا میکنند، از قابلیتهای جدید اس کیو ال سرور 2014 استفاده کرده است و با استفاده از این قابلیتها توانسته سرعت اجرای پرس و جوهای مشتریانش را از 15 هزار پرس و جو در ثانیه به 250 هزار پرس و جو در ثانیه برساند. در نتیجه کارایی سرور این شرکت 16 برابر شده است.
در تحقیقی دیگر، یک محقق، با استفاده از قابلیتهای جدید اس کیو ال سرور 2014 توانسته است دو رکورد جدید را از اجرای کوئریهای انبار داده ای برای حجمهای 3 ترابایت و 10 ترابایت و نوع پارتیشن بندی نشده به ثبت برساند و رکوردهای قبلی را که متعلق به اوراکل بوده، بشکند. این محقق توانسته 404005 کوئری نسبتا سنگین انبار دادهای را در پایگاه دادهای با 10 ترابایت اطلاعات، در یک ساعت اجرا کند و رکورد قبلی را که متعلق به اوراکل و برابر 377594 کوئری با همین شرایط بوده، بشکند. همچنین هزینهی اجرای کوئریهای سرور اس کیو ال مذکور برابر 2.04 دلار در هر ساعت اجرای کوئری بوده است. به این معنی است که کمتر از نصف هزینهی مشابه در رکورد ثبت شدهی اوراکل که برابر 4.65 دلار در ساعت اجرای کوئری بوده است، هزینه داشته است.
در واقع اگر بخواهیم سیستمهای مدیریت پایگاه داده عملیاتی را رتبه بندی کنیم، به جز سرعت، باید عوامل مختلفی را در نظر بگیریم که چنین کاری نیاز به همکاری گروهی بزرگ دارد. خوشبختانه چنین گروههایی وجود دارند و آن قدر معتبر هستند که اکثر شرکتهای بزرگ به آمارهای آنها استناد میکنند. در فناوریهای مربوط به آی تی، برای رسیدن به معتبرترین نتایج باید به گزارشهای ارائه شدهی شرکت گارتنر رجوع کنیم. گارتنر، شرکت پژوهشی و مشاورهی آمریکایی است، که در زمینهی ارائه خدمات برونسپاری، تحقیق و پژوهش و مشاوره فناوری اطلاعات فعالیت مینماید. این شرکت در سال 1979 راهاندازی شد و در سال 2014 بیش از 6500 نفر کارمند داشته که در 85 کشور بودهاند. در این بین حدود 1500 نفر از آنها در بخش تحقیق و توسعه فعالیت داشتهاند. همچنین در این سال، درآمد شرکت گارتنر که عمدتا از طریق مشاوره دادن به شرکتهای مختلف بوده، بیش از 2 میلیارد دلار در سال 2014 بوده است.
شرکت گارتنر معمولا خلاصهی نتیجهی بررسیهای خود را در نمودارهایی خاص به نام مربع جادویی گارتنر ارائه میکند. در این نمودار، قابلیتهای اجرایی که بیانگر کیفیت فعلی محصول هستند، در محور عمودی نمایش داده میشوند و از پایین به بالا زیاد میشوند. یعنی هر چه محصولی بالاتر باشد، در حال حاضر کیفیت بهتری دارد. محور افقی نمودار بیانگر بصیرت و آینده نگری محصول میباشد و از چپ به راست زیاد میشود. به این ترتیب رهبران یک حوزهی خاص، در ربع بالا و سمت راست مربع جای میگیرند.
حال که با نحوهی تفسیر مربع جادویی گارتنر آشنا شدیم، به بررسی نمودارهای مربوط به سیستمهای مدیریت پایگاه داده عملیاتی در سه سال اخیر میپردازیم.
در شکل ذیل میبینیم که در سال 2013 و پس از ارائهی نسخهی sql server 2012 توسط مایکروسافت، اوراکل همچنان پیشتاز است و شرکتهای مایکروسافت، آی بی ام و SAP پس از آن قرار گرفتهاند. البته در این سال شرکت مایکروسافت فاصلهی زیاد قبلی خود را با اوراکل، کم کرده است.
در سال 2014، شرکت مایکروسافت از نظر آینده نگری و بصیرت، از اوراکل پیشی گرفته ولی هنوز در قابلیتهای اجرایی عقبتر از اوراکل قرار دارد.
اما چند روز پیش در تاریخ 12 اکتبر 2015، شرکت گارتنر گزارشی ارائه کرد که خیلی از فعالان آی تی را شگفت زده کرد. این گزارش در حال حاضر در وب سایت شرکت گارتنر قابل دسترسی است؛ ولی معمولا گارتنر پس از مدتی آن را از حالت رایگان به پولی تغییر میدهد.
لینک موقت گزارش
در گزارش سال 2015 و پس از ارائهی نسخهی sql server 2014 و کاربردی شدن و تست قابلیتهای آن در عمل توسط شرکتهای مختلف، بالاخره طلسم چند ده سالهی اوراکل شکسته شده و اگرچه اوراکل نسبت به سال قبل رشد داشته است، ولی sql server مایکروسافت توانسته، هم در قابلیت اجرای فعلی و هم در بصیرت و آینده نگری بالاتر از محصول شرکت اوراکل بایستد. بنابراین عملا دوران پادشاهی مطلق اوراکل در حوزهی پایگاههای دادهی عملیاتی به سر رسیده است.
در انتها لازم میبینم به نکاتی مهم اشاره کنم:
- شرکت اوراکل بر خلاف تصور خیلی از افراد، همانند شرکتهای مایکروسافت، آی بی ام و ... محصولات گسترده و مختلفی دارد و این بررسی و نتایج تنها در حوزهی سیستمهای مدیریت پایگاه داده عملیاتی بود.
- بالاتر بودن sql server مایکروسافت از اوراکل در سال 2015 به این معنا نیست که اوراکل نمیتواند به جایگاه قبلی خود برگردد؛ بلکه شاید در سالهای آینده این رتبه بندی باز هم تغییر کند. در واقع این گزارش به این معنا است که فاصلهی زیاد قدیم بین sql server و oracle db از بین رفته و در حال حاضر این دو به رقیب سر سختی برای یکدیگر تبدیل شدهاند.
- وجود رقابت نزدیک بین شرکتهای بزرگ باعث میشود که این شرکتها حداکثر تلاش خود را برای بهتر کردن محصولات خود انجام بدهند و برندگان اصلی این وضعیت، استفاده کنندگان از این محصولات هستند.
- بنده به عنوان نگارندهی این پست شخصا با هر دو محصول oracle db و sql server کار میکنم و تلاش کردم که این پست بی طرفانه باشد؛ پس لطفا متعصبانه قضاوت نکنید.
برای اضافه کردن یک فایل به عنوان منبع، از سوئیچ [embed[resource استفاده میشود. این سوئیچ محتوای هر نوع فایلی را که به آن پاس شود، به فایل PE اجرایی انتقال داده و جدول ManifestResourceDef را به روز میکند تا سیستم از وجود آن آگاه شود.
سوئیچ [link[Resource هم برای الحاق کردن یک فایل به اسمبلی به کار میرود و دو جدول ManifestResourceDef و FileDef را جهت معرفی منبع جدید و شناسایی فایل اسمبلی که حاوی این منبع است، به روز میکند. در این حالت فایل منبع embed نشده و باید در کنار پروژه منتشر شود.
csc هم قابلیتهای مشابهی را با استفاده از سوئیچهای resource/ و link/ دارد و به روز رسانی و دیگر اطلاعات تکمیلی آن مشابه موارد بالاست.
شما حتی میتوانید منابع یک فایل win32 را خیلی راحت و آسان به اسمبلی معرفی کنید. شما به آسانی میتوانید مسیر یک فایل res. را با استفاده از سوئیچ win32res/ در al یا csc مشخص کنید. یا برای embed کردن آیکن یک برنامه win32 از سوئیچ win32icon/ مسیر یک فایل ICO را مشخص کنید. در ویژوال استودیو اینکار به صورت ویژوالی در پنجره تنظمیات پروژه و برگهی Application امکان پذیر است. دلیل اصلی که آیکن برنامهها به صورت embed ذخیره میشوند این است که این آیکن برای فایل اجرایی یک برنامهی مدیریت شده هم به کار میرود.
فایلهای اسمبلی Win32 شامل یک فایل مانیفست اطلاعاتی هستند که به طور خودکار توسط کمپایلر سی شارپ تولید میگردند. با استفاده از سوئیچ nowin32manifest/ میتوان از ایجاد این نوع فایل جلوگیری کرد. این اطلاعات به طور پیش فرض شبیه زیر است:
<?xml version="1.0" encoding="UTF8" standalone="yes"?> <assembly xmlns="urn:schemasmicrosoftcom:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" name="MyApplication.app" /> <trustInfo xmlns="urn:schemasmicrosoftcom:asm.v2"> <security> <requestedPrivileges xmlns="urn:schemasmicrosoftcom:asm.v3"> <requestedExecutionLevel level="asInvoker" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> </assembly>
موقعیکه شما یک اسمبلی میسازید باید فیلدهای منبع نسخه بندی را هم ذکر کنید. اینکار توسط خصوصیتها (Attributes) در سطح کد انجام میگیرد. این خصوصیات شامل موارد زیر هستند که در فضای نام Reflection قرار گرفتهاند.
using System.Reflection; // FileDescription version information: [assembly: AssemblyTitle("MultiFileLibrary.dll")] // Comments version information: [assembly: AssemblyDescription("This assembly contains MultiFileLibrary's types")] // CompanyName version information: [assembly: AssemblyCompany("Wintellect")] // ProductName version information: [assembly: AssemblyProduct("Wintellect (R) MultiFileLibrary's Type Library")] // LegalCopyright version information: [assembly: AssemblyCopyright("Copyright (c) Wintellect 2013")] // LegalTrademarks version information: [assembly:AssemblyTrademark("MultiFileLibrary is a registered trademark of Wintellect")] // AssemblyVersion version information: [assembly: AssemblyVersion("3.0.0.0")] // FILEVERSION/FileVersion version information: [assembly: AssemblyFileVersion("1.0.0.0")] // PRODUCTVERSION/ProductVersion version information: [assembly: AssemblyInformationalVersion("2.0.0.0")] // Set the Language field (discussed later in the "Culture" section) [assembly:AssemblyCulture("")]
جدول زیر اطلاعاتی در مورد سوئیچهای AL جهت مقداردهی این فیلدهای نسخه بندی دارد (کامپایلر سی شارپ این سوئیچها را ندارد و بهتر است از طریق همان خصوصیات در کدها اقدام کنید). بعضی از اطلاعات زیر با استفاده از سوئیچها قابل تغییر نیستند؛ چرا که این مقادیر یا ثابت هستند یا اینکه طبق شرایطی از بین چند مقدار ثابت، یکی از آنها انتخاب میشود.
نسخه منبع | سوئیچ AL.exe | توصیف خصوصیت یا سوئیچ مربوطه |
FILEVERSION | fileversion/ | System.Reflection.AssemblyFileVersionAttribute. |
PRODUCTVERSION | productversion/ | System.Reflection. AssemblyInformationalVersionAttribute |
FILEFLAGSMASK | - | Always set to VS_FFI_FILEFLAGSMASK (defined in WinVer.h as 0x0000003F). |
FILEFLAGS | - | همیشه صفر است |
FILEOS | - | در حال حاضر همیشه VOS__WINDOWS32 است |
FILETYPE | target/ | Set to VFT_APP if /target:exe or /target:winexe is specified; set to VFT_DLL if /target:library is specified. |
FILESUBTYPE | - | Always set to VFT2_UNKNOWN. (This field has no meaning for VFT_APP and VFT_DLL.) |
AssemblyVersion | version/ | System.Reflection.AssemblyVersionAttribute |
Comments | description/ | System.Reflection.AssemblyDescriptionAttribute |
CompanyName | company/ | System.Reflection.AssemblyCompanyAttribute |
FileDescription | title/ | System.Reflection.AssemblyTitleAttribute |
FileVersion | version/ | System.Reflection.AssemblyFileVersionAttribute |
InternalName | out/ | ذکر نام فایل خروجی بدون پسوند. |
LegalCopyright | copyright/ | System.Reflection.AssemblyCopyrightAttribute |
LegalTrademarks | trademark/ | System.Reflection.AssemblyTrademarkAttribute |
OriginalFilename | out | ذکر نام فایل خروجی بدون پسوند. |
PrivateBuild | - | همیشه خالی است. |
ProductName | product | System.Reflection.AssemblyProductAttribute |
ProductVersion | productversion | System.Reflection. AssemblyInformationalVersionAttribute |
SpecialBuild | - | همیشه خالی است. |
شما برای ویرایش این فایل میتوانید به راحتی آن را باز کرده و اطلاعات داخل آن را تغییر دهید. ویژوال استودیو نیز برای ویرایش این فایل، امکانات GUI را نیز فراهم کرده است. برای استفاده از این امکان، پنجرهی properties را در سطح Solution باز کرده و در تب Application روی Assembly Information کلیک کنید.
Live Connect
XP Connect
NPRuntime
در پایان سال 2004 بسیاری از شرکتهای اصلی ارائه کننده مرورگرهای وب که از NPAPI استفاده میکردند، به این توافق رسیدند که از فناوری NPRuntime به عنوان یک اکستنشن (افزونه) برای تامین اسکریپت نویسی NPAPI استفاده کنند. اسکریپت نویسی این فناوری مستقل از دیگر فناوریهایی چون جاوا و XPCOM است و توسط گوگل (در آن زمان)، فایرفاکس، سافاری و اپرا (در آن زمان) پشتیبانی میشود.
PPAPI
این فناوری در 12 آگوست 2009 معرفی شد. Pepper Plugin API مجموعه ای از تغییرات و اصلاحات روی NPAPI است که باعث انعطاف پذیری و امنیت بیشتر میشود. این افزونه جهت پیاده سازی آسان اجرای پلاگین در یک پروسهی جداگانه است. هدف این پروژه تهیه یک فریمورک برای ایجاد پلاگینهای مستقل یا Cross-Platform میباشد.
- اجرای جداگانه از پروسهی مرورگر
- تعریف رویدادهای استاندارد و رسم تصاویر دو بعدی
- تهیه گرافیک سه بعدی
- ریجستری پلاگین
- استاندارد سازی برای استفاده از سیستم رندر مرورگر Compositing Process
- تعریف مفهوم یا معنای واحد از NPAPI بین تمامی مروگرها
همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و pluginهای مربوط به Cordova آشنا خواهیم شد.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1">
روال کار jQuery Mobile
از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.
لیست کامل اتریبیوتهای -data به همراه مقادیری که میپذیرند
شما میتوانید از امکانات Theme Roller برای شخصی سازی تمهای مورد نیاز استفاده کنید.
Cordova Plugins
از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت http://plugreg.com/plugins میتوانید سراغ پلاگینهای مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins و تب Core یکسری از پلاگینهایی را که در Cordova گنجانده شده است، مشاهده میکنید. با کلیک بر روی دکمهی Add میتوانید آن را دانلود کرده و از APIهای آن استفاده کنید.
برای مثال پلاگین Notification را به پروژه اضافه میکنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و محتوای فایلهای index.html , custome.js را به شکل زیر در نظر میگیرم:
$(function() { $("#alert").on('tap', function(event) { navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید"); }); $("#prompt").on('tap', function(event) { navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]); }); function onPrompt(results) { navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null); } $("#confirm").on('tap', function(event) { navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]); }); function onConfirm(buttonIndex) { navigator.notification.alert(buttonIndex , null); } $("#beep").on('tap', function(event) { navigator.notification.beep(1); }); });
رخداد tap زمانی صادر میشود که کاربر، دکمهی مورد نظر را لمس کند و یکی از رخدادهای jQuery Mobile میباشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification میتوانید به متدهای مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.
برای آشنایی با این پلاگین میتوانید داکیومنت آن را مطالعه کنید.
در کد بالا با استفاده از متدهای callback توانستهایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>CordovaApp01</title> <meta name="viewport" content="width=device-width, initial-scale=1"/> <!-- CordovaApp01 references --> <link href="css/index.css" rel="stylesheet" /> <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" /> </head> <body> <div data-role="page" id="page1"> <div data-role="header"> <h2> تست پلاگین Notification </h2> </div> <div data-role="content"> <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a> <button data-role="button" id="alert" data-inline="true" >alert</button> <button data-role="button" id="confirm" data-inline="true">confirm</button> <button data-role="button" id="beep" data-inline="true" >beep</button> <button data-role="button" id="prompt" data-inline="true" >prompt</button> </div> <div data-role="footer"> <h2>من فوتر هستم</h2> </div> </div> <div data-role="page" id="page2"> <div data-role="header"> <h1>Header</h1> </div> <div data-role="content"> Content </div> <div data-role="footer"> <h1>Footer</h1> </div> </div> <!-- Cordova reference, this is added to your app when it's built. --> <script src="scripts/jquery-2.1.3.min.js"></script> <script src="cordova.js"></script> <script src="scripts/platformOverrides.js"></script> <script src="scripts/index.js"></script> <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script> <script src="scripts/custom.js"></script> </body> </html>
در کد بالا 4 تا button دیده میشود که ویژگی data-role آنها مقدار button در نظر گرفته شدهاست تا توسط jQuery Mobile به عنوان button شناخته شوند و استایلهای لازم بر روی آنها اعمال گردد. قرار است طبق کد js ایی که نوشتهایم، با لمس کردن هر کدام از دکمهها، notification هایی نمایش داده شوند.
برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینهی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:
Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01 Error448throw e;CordovaApp-01 Error449^CordovaApp-01 Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
adb connect localhost:5558
<a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
در مقالهی بعد، به مباحث Database در Cordova خواهیم پرداخت.
ادامه دارد...
- محیط برنامه نویسی قدرتمند
- هسته اصلی کدهای همه اپلیکیشنها تولید شده شبیه به هم است
- نیازی به یادگیری زبانهای مربوط به هر پلتفرم را ندارید
- کم هزینه و زمان کمتر
- طراحی رابط گرافیکی سریع و منعطف به کمک HTML5 , CSS3
- برنامه نویسی آسان و سریع با javascript , Typescript
- قابلیت اجرا بر روی چندین پلتفرم مختلف(Android,iOS,Widnows Phone )
- قابلیت استفاده از فریمورکهای تحت وب مانند Bootstrap , Angular JS, ...
- قابلیت طراحی پلاگین برای ارتباط با سیستم عامل
- مناسب برای برای برنامههای چت و استفاد از وب سرویسها
- مناسب برای ساخت بازیهای آنلاین و آفلاین با تکنولوژیهای تحت وب
- راحتی کار با آن برای برنامه نویسان تحت وب
- نداشتن ابزار گزارش خطاهای مناسب؛ درنتیجه برطرف کردن خطاها خسته کننده خواهد بود .
- UI, UX اپلیکیشنها باید به نحوی باشد که کاربر حس کند با نرمافزارهای بومی گوشی کار میکند.
- کاهش سرعت اجرایی جزئی نسبت به سایر برنامهها (به دلیل استفاده از WebView)
- عدم دسترسی مستقیم به سیستم عامل و امکانات آن
- Node.js
- Git CLI
- Google Chrome
- Apache Ant
- Oracle Java JDK 7 (حتما نسخه x86 نصب شود)
- Android SDK
- SQLLite For Windows Runtime
- Apple iTunes
- node.js را از لینک مقابل دانلود کنید: اینجا (پیشنهاد میکنیم نسخهی x86 آن را نصب کنید)
- Google Chrome را نصب کنید
- Git Command Line Tools را نصب کنید و توجه کنید که در هنگام نصب، گزینه مربوط به افزودن Git را به مسیر Command Prompt شما، انتخاب کرده باشید.
- Apchage Ant را دانلود و در مسیری از سیستم خودتان قرار دهید.
- Java JDK 7 x86 را از لینک مشخص شده دانلود کنید و سپس عملیات نصب را انجام دهید.
- Android SDK را از آدرس مشحص شده دانلود کنید. پکیچهای مورد نیاز، به این SDK افزوده شده است. بعد از دانلود آن را در مسیری از سیستم خود قرار دهید.
- Apple iTunes و SQLite را دانلود و نصب کنید.
- اگر از ویندوز 7 استفاده میکنید ، WebSocket4Net را از لینک مقابل دانلود کنید ( اینجا ) و سپس فایل net45\Release\WebSocket4Net.dll در مسیر زیر کپی کنید:
%GIT_HOME%\cmd;C:\Program Files (x86)\nodejs\;%JAVA_HOME%\bin;%ANT_HOME%\bin; %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools; C:\ProgramData\Oracle\Java\javapath;
نکته تکمیلی
روش معمول انجام اینکار، به روز رسانی دستی تمام فایلهای AssemblyInfo.cs یک Solution است و همچنین اطمینان حاصل کردن از همگام بودن آنها. در ادامه قصد داریم با استفاده از فایلهای T4، یک فایل SharedAssemblyInfo.tt را جهت تولید اطلاعات مشترک Build بین اسمبلیهای مختلف یک پروژه، تولید کنیم.
ایجاد پروژهی SharedMetaData
برای نگهداری فایل مشترک SharedAssemblyInfo.cs نهایی و همچنین اطمینان از تولید مجدد آن به ازای هر Build، یک پروژهی class library جدید را به نام SharedMetaData به Solution جاری اضافه کنید.
سپس نیاز است یک فایل text template جدید را به نام SharedAssemblyInfo.tt، به این پروژه اضافه کنید.
به خواص فایل SharedAssemblyInfo.tt مراجعه کرده و Transform on build آنرا true کنید. به این ترتیب مطمئن خواهیم شد این فایل به ازای هر build جدید، مجددا تولید میگردد.
اکنون محتوای این فایل را به نحو ذیل تغییر دهید:
<#@ template debug="false" hostspecific="false" language="C#" #> // // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. // using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyCompany("some name")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguageAttribute("en")] [assembly: AssemblyProduct("product name")] [assembly: AssemblyCopyright("Copyright VahidN 2014")] [assembly: AssemblyTrademark("some name")] #if DEBUG [assembly: AssemblyConfiguration("Debug")] #else [assembly: AssemblyConfiguration("Release")] #endif // Assembly Versions are incremented manually when branching the code for a release. [assembly: AssemblyVersion("<#= this.MajorVersion #>.<#= this.MinorVersion #>.<#= this.BuildNumber #>.<#= this.RevisionNumber #>")] // Assembly File Version should be incremented automatically as part of the build process. [assembly: AssemblyFileVersion("<#= this.MajorVersion #>.<#= this.MinorVersion #>.<#= this.BuildNumber #>.<#= this.RevisionNumber #>")] <#+ // Manually incremented for major releases, such as adding many new features to the solution or introducing breaking changes. int MajorVersion = 1; // Manually incremented for minor releases, such as introducing small changes to existing features or adding new features. int MinorVersion = 0; // Typically incremented automatically as part of every build performed on the Build Server. int BuildNumber = (int)(DateTime.UtcNow - new DateTime(2013,1,1)).TotalDays; // Incremented for QFEs (a.k.a. “hotfixes” or patches) to builds released into the Production environment. // This is set to zero for the initial release of any major/minor version of the solution. int RevisionNumber = 0; #>
MajorVersion با افزودن تعداد زیادی قابلیت به برنامه، به صورت دستی تغییر میکند. همچنین اگر یک breaking change در برنامه یا کتابخانه وجود داشته باشد نیز این شماره باید تغییر نماید.
MinorVersion با افزودن ویژگیهای کوچکی به نگارش فعلی برنامه تغییر میکند.
BuildNumber به صورت خودکار بر اساس هر Build انجام شده باید تغییر یابد. در اینجا این عدد به صورت خودکار به ازای هر روز، یک واحد افزایش پیدا میکند. ابتدای مبداء آن در این مثال، 2013 قرار گرفتهاست.
RevisionNumber با ارائه یک وصله جدید برای نگارش فعلی برنامه، به صورت دستی باید تغییر کند. اگر اعداد شماره نگارش major یا minor تغییر کنند، این عدد باید به صفر تنظیم شود.
اکنون اگر این محتوای جدید را ذخیره کنید، فایل SharedAssemblyInfo.cs به صورت خودکار تولید خواهد شد.
افزودن فایل SharedAssemblyInfo.cs به صورت لینک به تمام پروژهها
نحوهی افزودن فایل جدید SharedAssemblyInfo.cs به پروژههای موجود، اندکی متفاوت است با روش معمول افزودن فایلهای cs هر پروژه. ابتدا از منوی پروژه گزینهی add existing item را انتخاب کنید. سپس فایل SharedAssemblyInfo.cs را یافته و به صورت add as link، به تمام پروژههای موجود اضافه کنید.
اینکار باید در مورد تمام پروژهها صورت گیرد. به این ترتیب چون فایل SharedAssemblyInfo.cs به این پروژهها صرفا لینک شدهاست، اگر محتوای آن در پروژهی metadata تغییر کند، به صورت خودکار و یک دست، در تمام پروژههای دیگر نیز منعکس خواهد شد.
در ادامه اگر بخواهید Solution را Build کنید، پیام تکراری بودن یک سری از ویژگیها را یافت خواهید کرد. این مورد از این جهت رخ میدهد که هنوز فایلهای AssemblyInfo.cs اصلی، در پروژههای برنامه موجود هستند.
این فایلها را یافته و صرفا چند سطر همیشه ثابت ذیل را در آنها باقی بگذارید:
using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyTitle("title")] [assembly: AssemblyDescription("")] [assembly: ComVisible(false)] [assembly: Guid("9cde6054-dd73-42d5-a859-7d4b6dc9b596")]
اضافه کردن build dependency به پروژه MetaData
در پایان کار نیاز است اطمینان حاصل کنیم، فایل SharedAssemblyInfo.cs به صورت خودکار پیش از Build هر پروژه، تولید میشود. برای این منظور، از منوی Project، گزینهی Project dependencies را انتخاب کنید. سپس در برگهی dependencies آن، به ازای تمام پروژههای موجود، گزینهی SharedMetadata را انتخاب نمائید.
این مساله سبب اجرای خودکار فایل SharedAssemblyInfo.tt پیش از هر Build میشود و به این ترتیب میتوان از تازه بودن اطلاعات SharedAssemblyInfo.cs اطمینان حاصل کرد.
اکنون اگر پروژه را Build کنید، تمام اجزای آن شماره نگارش یکسانی را خواهند داشت:
و در دفعات آتی، تنها نیاز است تک فایل SharedAssemblyInfo.tt را برای تغییر شماره نگارشهای اصلی، ویرایش کرد.
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace FormValidationWithBootstrap.Models { [Table("Product")] public class ProductModel { [Key] public int Id { get; set; } [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")] [StringLength(50, ErrorMessage = "طول {0} باید کمتر از {1} کاراکتر باشد.")] [Display(Name = "نام کالا")] public string Name { get; set; } [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")] [Display(Name = "قیمت")] [DataType(DataType.Currency)] public double Price { get; set; } [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")] [Display(Name = "موجودی")] public int Qty { get; set; } } }
using System.Web.Mvc; using FormValidationWithBootstrap.Models; namespace FormValidationWithBootstrap.Controllers { public class ProductController : Controller { // GET: Product public ActionResult Index() { return View(); } public ActionResult New() { return View(); } [HttpPost] public ActionResult New(ProductModel product) { if (!ModelState.IsValid) return View(product); if (product.Name != "پفک") { ModelState.AddModelError("", "لطفا مشکلات را برطرف کنید!"); ModelState.AddModelError("Name", "فقط محصولی با نام پفک قابل ثبت است :)"); return View(product); } // todo:save... return RedirectToAction("Index"); } } }
@model FormValidationWithBootstrap.Models.ProductModel @{ ViewBag.Title = "New"; } <h2>کالای جدید</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div> <hr /> @Html.ValidationSummary(true, "", new { @class = "alert alert-danger" }) <div> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" }) </div> </div> <div> @Html.LabelFor(model => model.Qty, htmlAttributes: new { @class = "control-label col-md-2" }) <div> @Html.EditorFor(model => model.Qty, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Qty, "", new { @class = "text-danger" }) </div> </div> <div> <div> <input type="submit" value="ثبت" /> <input type="reset" value="ریست" /> @Html.ActionLink("بازگشت به لیست", "Index", "Product", null, new {@class="btn btn-default"}) </div> </div> </div> }
@section Scripts { @Scripts.Render("~/bundles/jqueryval") <script> // override jquery validate plugin defaults $.validator.setDefaults({ highlight: function (element) { $(element).closest('.form-group').addClass('has-error'); }, unhighlight: function (element) { $(element).closest('.form-group').removeClass('has-error').addClass('has-success'); }, errorElement: 'span', errorClass: 'help-block', errorPlacement: function (error, element) { if (element.parent('.input-group').length) { error.insertAfter(element.parent()); } else { error.insertAfter(element); } } }); $(function () { $('form').each(function () { $(this).find('div.form-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('has-error'); } }); }); }); </script> }
در قسمت مدل ابتدا یک کلاس پایه برای مدل ایجاد خواهیم کرد:
public abstract class Entity { public Guid Id { get; set; } }
public class Book : EntityBase { public string Name { get; set; } public decimal Author { get; set; } }
public class BookRepository { private readonly ConcurrentDictionary<Guid, Book> result = new ConcurrentDictionary<Guid, Book>(); public IQueryable<Book> GetAll() { return result.Values.AsQueryable(); } public Book Add(Book entity) { if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid(); if (result.ContainsKey(entity.Id)) return null; if (!result.TryAdd(entity.Id, entity)) return null; return entity; } }
نوبت به کلاس کنترلر میرسد. یک کنترلر Api به نام BooksController ایجاد کنید و سپس کدهای زیر را در آن کپی نمایید:
public class BooksController : ApiController { public static BookRepository repository = new BookRepository(); public BooksController() { repository.Add(new Book { Id=Guid.NewGuid(), Name="C#", Author="Masoud Pakdel" }); repository.Add(new Book { Id = Guid.NewGuid(), Name = "F#", Author = "Masoud Pakdel" }); repository.Add(new Book { Id = Guid.NewGuid(), Name = "TypeScript", Author = "Masoud Pakdel" }); } public IEnumerable<Book> Get() { return repository.GetAll().ToArray(); } }
در این کنترلر، اکشنی به نام Get داریم که در آن اطلاعات کتابها از Repository مربوطه برگشت داده خواهد شد. در سازنده این کنترلر ابتدا سه کتاب به صورت پیش فرض اضافه میشود و انتظار داریم که بعد از اجرای برنامه، لیست مورد نظر را مشاهده نماییم.
module Model { export class Book{ Id: string; Name: string; Author: string; } }
<div ng-controller="Books.Controller"> <table class="table table-striped table-hover" style="width: 500px;"> <thead> <tr> <th>Name</th> <th>Author</th> </tr> </thead> <tbody> <tr ng-repeat="book in books"> <td>{{book.Name}}</td> <td>{{book.Author}}</td> </tr> </tbody> </table> </div>
ابتدا یک کنترلری که به نام Controller که در ماژولی به نام Book تعریف شده است باید ایجاد شود. اطلاعات تمام کتب ثبت شده باید از سرویس مورد نظر دریافت و با یک ng-repeat در جدول نمایش داده خواهند شود.
در پوشه app یک فایل TypeScript دیگر برای تعریف برخی نیازمندیها به نام AngularModule ایجاد میکنیم که کد آن به صورت زیر خواهد بود:
declare module AngularModule { export interface HttpPromise { success(callback: Function) : HttpPromise; } export interface Http { get(url: string): HttpPromise; } }
در اینترفیس Http نیز تابعی به نام get تعریف شده است که برای دریافت اطلاعات از سرویس api، مورد استفاده قرار خواهد گرفت. از آن جا که تعریف توابع در اینترفیس فاقد بدنه است در نتیجه این جا فقط امضای توابع مشخص خواهد شد. پیاده سازی توابع به عهده کنترلرها خواهد بود:
مرحله بعد مربوط است به تعریف کنترلری به نام BookController تا اینترفیس بالا را پیاده سازی نماید. کدهای آن به صورت زیر خواهد بود:
/// <reference path='AngularModule.ts' /> /// <reference path='BookModel.ts' /> module Books { export interface Scope { books: Model.Book[]; } export class Controller { private httpService: any; constructor($scope: Scope, $http: any) { this.httpService = $http; this.getAllBooks(function (data) { $scope.books = data; }); var controller = this; } getAllBooks(successCallback: Function): void { this.httpService.get('/api/books').success(function (data, status) { successCallback(data); }); } } }
توضیح کدهای بالا:
برای دسترسی به تعاریف انجام شده در سایر ماژولها باید ارجاعی به فایل تعاریف ماژولهای مورد نظر داشته باشیم. در غیر این صورت هنگام استفاده از این ماژولها با خطای کامپایلری روبرو خواهیم شد. عملیات ارجاع به صورت زیر است:
/// <reference path='AngularModule.ts' /> /// <reference path='BookModel.ts' />
export interface Scope { books: Model.Book[]; }
در نهایت خروجی به صورت زیر خواهد بود:
سورس پیاده سازی مثال بالا در Visual Studio 2013
1- مقدمه
پارتیشن بندی در بانک اطلاعاتی SQL Server، از ویژگیهایی است که از نسخه 2005، به این محصول اضافه شده است. بکارگیری این قابلیت که با Split کردن، محتوای یک جدول و قرار دادن آنها در چندین فایل، برای جداول حجیم، به ویژه جداولی که دادههای آن حاوی مقادیر تاریخچهای است، بسیار سودمند است.سادگی در مدیریت دادهها و شاخصهای موجود یک جدول (از قبیل اندازه فضای ذخیره سازی و استراتژی جدید Back up گیری)، اجرای سریعتر کوئری هایی که روی یک محدوده از دادهها کار میکنند و سهولت در آرشیو دادههای قدیمی یک جدول، از قابلیتهایی است که استفاده از این ویژگی بوجود میآورد.
محدوده استفاده از این ویژگی روی یک بانک اطلاعاتی و در یک Instance است. بنابراین مباحث مرتبط با معماری Scalability را پوشش نمیدهد و صرفاً Solution ایی است که در یک Instance بانک اطلاعاتی استفاده میشود.
2- Data File و Filegroup
هر بانک اطلاعاتی در حالت پیش فرض، شامل یک فایل دادهای (MDF.) و یک فایل ثبت تراکنشی (LDF.) میباشد. میتوان جهت ذخیره سطرهای دادهای از فایلهای بیشتری تحت نام فایلهای ثانویه (NDF.) استفاده نمود. به همان طریق که در فایل سیستم، فایلها به پوشهها تخصیص داده میشوند، میتوان Data File را به Filegroup تخصیص داد. چنانچه چندین Data File به یک Filegroup تخصیص داده شوند، دادهها در تمامی Data Fileها به طریق Round-Robin توزیع میشوند.3- Partition Function
مطابق با مقادیر تعریف شده در بدنه دستور، محدوده دادهای (پارتیشنها) با استفاده از Partition Function ایجاد میشود. با در نظر گرفتن ستونی که به عنوان Partition Key انتخاب شده، این تابع یک Data Type را به عنوان ورودی دریافت میکند. در هنگام تعریف محدوده برای پارتیشنها، به منظور مشخص کردن محدوده هر پارتیشن از Right و Left استفاده میشود.Left نمایش دهندهی حد بالای هر محدوده است و به طور مشابه، Right برای مشخص کردن حد پائین آن محدوده استفاده میشود. به منظور درک بهتر، به شکل زیر توجه نمائید:
همانطور که مشاهده میشود، همواره نیاز به یک Filegroup اضافهتری از آنچه مورد نظرتان در تعریف تابع است، میباشد. بنابراین اگر Function دارای n مقدار باشد، به n+1 مقدار برای Filegroup نیاز است.
همچنین هیچ محدودیتی برای اولین و آخرین بازه در نظر گرفته نمیشود. بنابراین جهت محدود کردن مقادیری که در این بازهها قرار میگیرند، میتوان از Check Constraint استفاده نمود.
3-1- Right or Left
یک سوال متداول اینکه از کدام مورد استفاده شود؟ در پاسخ باید گفت، به چگونگی تعریف پارتیشن هایتان وابسته است. مطابق شکل، تنها تفاوت این دو، در نقاط مرزی هر یک از پارتیشنها میباشد. در بیشتر اوقات هنگام کار با دادههای عددی میتوان از Left استفاده نمود و بطور مشابه هنگامیکه نوع دادهها از جنس زمان است، میتوان از Right استفاده کرد.
4- Partition Schema
گام بعدی پس از ایجاد Partition Function، تعریف Partition Schema است، که به منظور قرار گرفتن هر یک از پارتیشنهای تعریف شده توسط Function در Filegroupهای مناسب آن استفاده میشود.
5- Partition Table
گام پایانی ایجاد یک جدول، استفاده از Partition Schema است، که دادهها را با توجه به رویه درون Partition Function مورد استفاده، ذخیره میکند. همانطور که میدانید هنگام ایجاد یک جدول، میتوان مکان ذخیره شدن آنرا مشخص نمود.
Create Table <name> (…) ON …
در هنگام ایجاد یک جدول، معمولاً جدول در Filegroup پیش فرض که PRIMARY است، قرار میگیرد. میتوان با نوشتن نام Partition Schema و همچنین Partition Key که پیشتر ذکر آن رفت، بعد از بخش ON، برای جدول مشخص نمائیم که دادههای آن به چه ترتیبی ذخیره شوند. ارتباط این سه به شرح زیر است:
توجه شود زمانیکه یک Primary Key Constraint به یک جدول اضافه میشود، یک Unique Clustered Index نیز همراه با آن ساخته میشود. چنانچه Primary Key شامل یک Clustered Index باشد، جدول با استفاده از این ستون (ستونهای) شاخص ذخیره خواهد شد، در حالیکه اگر Primary Key شامل یک Non Clustered Index باشد، یک ساختار ذخیره-سازی اضافی ایجاد خواهد شد که دادههای جدول در آن قرار خواهند گرفت.
6- Index & Data Alignment
به عنوان یک Best Practice هنگام ایجاد یک Partition Table به منظور پارتیشن بندی، از ساختار Aligned Index استفاده شود. بدین ترتیب که تعریف Index، شامل Partition Key (ستونی که معیاری برای پارتیشن بندی است) باشد. چنانچه این عمل انجام شود، دادههای ذخیره شده مرتبط با هر پارتیشن متناظر با همان شاخص، در فایل دادهای (NDF.) ذخیره خواهند شد. از این رو چنانچه کوئری درخواست شده از جدول روی یک محدوده باشد
Where [OrderDate] Between …
بدین ترتیب برای بهرمندی از این مزایا، استفاده از Aligned Index توصیه شده است.
7- Operations
از نیازمندیهای متداول در پارتیشنینگ میتوان به افزودن، حذف پارتیشنها و جابجایی محتوای یک پارتیشن که برای عملیات آرشیو استفاده میشود، اشاره کرد.
7-1- Split Partition
به منظور ایجاد یک محدوده جدید به پارتیشنها استفاده میشود. یک نکته مهم مادامی که عملیات انتقال دادهها به پارتیشن جدید انجام میگیرد، روی جدول یک قفل انحصاری قرار میگیرد و بدین ترتیب عملیات ممکن است زمانبر باشد.
به عنوان یک Best Practice همواره یک Partition خالی را Split نمائید و پس از آن اقدام به بارگذاری داده در آن نمائید.
به یاد داشته باشید پیش از انجام عملیات splitting روی Partition Function با تغییر در Partition Schema (و بکارگیری Next Used) مشخص نمائید چه محدودهای در این Filegroup جدید قرار خواهد گرفت.
7-2- Merge Partition
به منظور ادغام پارتیشنها استفاده میشود، چنانچه پارتیشن خالی نیست، برای عملیات ادغام مسائل Performance به علت اینکه در طول عملیات از Lock (قفل انحصاری) استفاده میشود، در نظر گرفته شود.
7-3- Switch Partition
چنانچه جدول و شاخصهای آن به صورت Aligned هستند، میتوانید از Switch in و Switch out استفاده نمائید. عملیات بدین ترتیب انجام میشود که بلافاصله محتوای یک پارتیشن یا جدول (Source) در یک پارتیشن خالی جدولی دیگر و یا یک جدول خالی (Target) قرار میگیرد. عملیات تنها روی Meta Data انجام میگیرد و هیچ داده ای منتقل نمیشود.
محدودیتهای بکارگیری به شرح زیر است:
- جدول یا پارتیشن Target باید حتماً خالی باشد.
- جداول Source و Target حتماً باید در یک Filegroup یکسان قرار داشته باشند.
- جدول Source باید حاوی Aligned Indexهای مورد نیاز Target و همچنین مطابقت در Filegroup را دارا باشد.
- چنانچه Target به عنوان یک پارتیشن است، اگر Source جدول است بایست دارای یک Check Constraint باشد در غیر این صورت چنانچه یک پارتیشن است باید محدوده آن در محدوده Target قرار گیرد.
8- بررسی یک سناریوی نمونه
در ابتدا یک بانک اطلاعاتی را به طریق زیر ایجاد میکنیم:
این بانک مطابق تصویر، شامل 3 عدد فایل گروپ (FG1، FG2 و FG3) و 3 عدد دیتا فایل (P1، P2 و P3) میباشد. Filegroup پیش فرض Primary است، که چنانچه در تعریف جداول به نام Partition Schema و Partition Key مرتبط اشاره نشود، به طور پیش فرض در Filegroup موسوم به Primary قرار میگیرد. چنانچه چک باکس Default انتخاب شود، همانطور که قابل حدس زدن است، آن Filegroup در صورت مشخص نکردن نام Filegroup در تعریف جدول، به عنوان مکان ذخیره سازی انتخاب میشود. چک باکس Read Only نیز همانطور که از نامش پیداست، چنانچه روی یک Filegroup تنظیم گردد، عملیات مربوط به Write روی دادههای آن قابل انجام نیست و برای Filegroup هایی که جنبه نگهداری آرشیو را دارند، قابل استفاده است.
چنانچه Filegroup ای را از حالت Read Only دوباره خارج کنیم، میتوان عملیات Write را دوباره برای آن انجام داد.
پس از ایجاد بانک اطلاعاتی، گام بعدی ایجاد یک Partition Function و پس از آن یک Partition Schema است. همانطور که مشاهده میکنید در Partition Function از سه مقدار استفاده شده، بنابراین در Partition Schema باید از چهار Filegroup استفاده شود، که در مثال ما از Filegroup پیش فرض که Primary است، استفاده شده است.
USE [PartitionDB] GO CREATE PARTITION FUNCTION pfOrderDateRange(DATETIME) AS RANGE LEFT FOR VALUES ('2010/12/31','2011/12/31','2012/12/31') GO CREATE PARTITION SCHEME psOrderDateRange AS PARTITION pfOrderDateRange TO (FG1,FG2,FG3,[PRIMARY]) GO
پس از طی گامهای قبل، به ایجاد یک جدول به صورت Aligned Index مبادرت ورزیده میشود.
CREATE TABLE Orders ( OrderID INT IDENTITY(1,1) NOT NULL, OrderDate DATETIME NOT NULL, OrderFreight MONEY NULL, ProductID INT NULL, CONSTRAINT PK_Orders PRIMARY KEY CLUSTERED (OrderID ASC, OrderDate ASC) ON psOrderDateRange (OrderDate) ) ON psOrderDateRange (OrderDate) GO
در ادامه برای بررسی درج اطلاعات در پارتیشن با توجه به محدوده آنها اقدام به افزودن رکوردهایی در جدول ساخته شده مینمائیم.
SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2010/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2011/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2012/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO
از طریق دستور Select زیر میتوان نحوه توزیع دادهها را در جدول مشاهده کرد.
USE [PartitionDB] GO SELECT OBJECT_NAME(i.object_id) AS OBJECT_NAME, p.partition_number, fg.NAME AS FILEGROUP_NAME, ROWS, au.total_pages, CASE boundary_value_on_right WHEN 1 THEN 'Less than' ELSE 'Less or equal than' END AS 'Comparition',VALUE FROM sys.partitions p JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id JOIN sys.partition_schemes ps ON ps.data_space_id = i.data_space_id JOIN sys.partition_functions f ON f.function_id = ps.function_id LEFT JOIN sys.partition_range_values rv ON f.function_id = rv.function_id AND p.partition_number = rv.boundary_id JOIN sys.destination_data_spaces dds ON dds.partition_scheme_id = ps.data_space_id AND dds.destination_id = p.partition_number JOIN sys.filegroups fg ON dds.data_space_id = fg.data_space_id JOIN (SELECT container_id, SUM(total_pages) AS total_pages FROM sys.allocation_units GROUP BY container_id) AS au ON au.container_id = p.partition_id WHERE i.index_id < 2
خروجی دستور فوق به شرح زیر است:
در ادامه به ایجاد یک Filegroup جدید میپردازیم.
/* Query 2-3- Split a partition*/ -- Add FG4: ALTER DATABASE PartitionDB ADD FILEGROUP FG4 Go ALTER PARTITION SCHEME [psOrderDateRange] NEXT USED FG4 GO ALTER PARTITION FUNCTION [pfOrderDateRange]() SPLIT RANGE('2013/12/31') GO -- Add Partition 4 (P4) to FG4: GO ALTER DATABASE PartitionDB ADD FILE ( NAME = P4, FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\P4.NDF' , SIZE = 1024KB , MAXSIZE = UNLIMITED, FILEGROWTH = 10%) TO FILEGROUP [FG4] -- GO
و در ادامه به درج اطلاعاتی برای بررسی نحوه توزیع دادهها در Filegroup هایمان میپردازیم.
SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2013/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO SET NOCOUNT ON DECLARE @OrderDate DATETIME DECLARE @X INT SET @OrderDate = '2012/01/01' SET @X = 0 WHILE @X < 300 BEGIN INSERT dbo.Orders ( OrderDate, OrderFreight, ProductID) VALUES( @OrderDate + @X, @X + 10, @X) SET @X = @X + 1 END GO
جهت ادغام پارتیشنها به طریق زیر عمل میشود:
/* Query 2-4- Merge Partitions */ ALTER PARTITION FUNCTION [pfOrderDateRange]() MERGE RANGE('2010/12/31') Go
به منظور آرشیو نمودن اطلاعات به طریق زیر از Switch استفاده میکنیم. ابتدا یک جدول موقتی برای ذخیره رکوردهایی که قصد آرشیو آنها را داریم، ایجاد میکنیم. همانگونه که در تعریف جدول مشاهده میکنید، نام Filegroup ای که برای ساخت این جدول استفاده میشود، با Filegroup ای که قصد آرشیو اطلاعات آنرا داریم، یکسان است.
در ادامه میتوان مثلاً با ایجاد یک Temporary Table به انتقال این اطلاعات بدون توجه به Filegroup آنها پرداخت.
/* Query 2-5- Switch Partitions */ USE [PartitionDB] GO CREATE TABLE [dbo].[Orders_Temp]( [OrderID] [int] IDENTITY(1,1) NOT NULL, [OrderDate] [datetime] NOT NULL, [OrderFreight] [money] NULL, [ProductID] [int] NULL, CONSTRAINT [PK_OrdersTemp] PRIMARY KEY CLUSTERED ([OrderID] ASC,[OrderDate] ASC)ON FG2 ) ON FG2 GO USE [tempdb] GO CREATE TABLE [dbo].[Orders_Hist]( [OrderID] [int] NOT NULL, [OrderDate] [datetime] NOT NULL, [OrderFreight] [money] NULL, [ProductID] [int] NULL, CONSTRAINT [PK_OrdersTemp] PRIMARY KEY CLUSTERED ([OrderID] ASC,[OrderDate] ASC) ) GO USE [PartitionDB] GO ALTER TABLE [dbo].[Orders] SWITCH PARTITION 1 TO [dbo].[Orders_Temp] GO INSERT INTO [tempdb].[dbo].[Orders_Hist] SELECT * FROM [dbo].[Orders_Temp] GO DROP TABLE [dbo].[Orders_Temp] GO SELECT * FROM [tempdb].[dbo].[Orders_Hist]