var option = $('<option></option>').attr("value", data[i].Id).text(data[i].Title);
آشنایی با Bower
Bower چیست؟
مزایا:
- نصب ابزارها و کتابخانهها توسط یک خط فرمان!
- به جای اینکه در سایتهای مختلف ورژن کتابخانهها را پیگیری کنید و update شدن یا نشدن آنها را بررسی نمایید(مثلا آیا jQueryمورد استفاده درپروژه ، آخرین نسخه است؟) ، با استفاده از Bower در کمترین زمان ممکن این کار را انجام دهید.
- نصب آفلاین. وقتی کتابخانه ای برای اولین بار نصب شود کش شده و دفعات بعد برای نصب همان کتابخانه(و البته همان ورژن) از کش استفاده خواهد کرد.(مگر اینکه کاربر صراحتا کش را خالی کرده باشد).
- نصب کتابخانههای وابسته. اگر کتابخانه ای وابسته به کتابخانههای دیگر باشد (مثل وابستگی Twitter Bootstrap به jQuery)، بطور خودکار وابستگیها نیز نصب میگردند.
قبل از نصب باید دو ابزار زیر در سیستم نصب شده باشند:
نصب Bower :
در خط فرمان دستور زیر را اجرا نمایید:
npm install -g bower
نصب کتابخانه ها:
برای نصب کتابخانهها از دستور زیر استفاده میشود:
bower install <package>
bower install angular
bower install jquery
bower install <package>#<version>
bower install jquery#1.7.0
پس از اجرای دستور، در مسیر جاری فولدری به نام bower_components ایجاد شده و کتابخانهها در آن قرار میگیرند.
bower_components/ jquery/ README.md bower.json component.json composer.json jquery-migrate.js jquery-migrate.min.js jquery.js jquery.min.js jquery.min.map package.json
و در نهایت نحوه استفاده:
<script type="text/javascript" src="bower_components/jquery/jquery.js"></script>
جستجو در کتابخانه ها:
Bower امکان جستجو در کتابخانههای ثبت شده را میدهد. مثال:
bower search bootstrap
Search results:
bootstrap git://github.com/twbs/bootstrap.git
angular-bootstrap git://github.com/angular-ui/bootstrap-bower.git
sass-bootstrap git://github.com/jlong/sass-twitter-bootstrap.git
همانگونه که میدانید مقدار Identity پس از درج به آن تخصیص مییابد چنانچه بخواهید به این مقدار دسترسی پیدا کنید چندین روش به ازای اینکار وجود دارد که ما در این مقاله سه روش معمول را بررسی خواهیم نمود.
1- استفاده از متغییر سیستمی Identity@@
2- استفاده از تابع () Scope_Identity
3- استفاده از تابع Ident_Current
هر سه این توابع مقدار Identity ایجاد شده برای جداول را نمایش میدهند. اما تفاوت هایی باهم دارند که در ادامه مقاله این تفاوتها بررسی شده است.
1- متغییر سیستمی Identity@@ : این متغییر سیستمی حاوی آخرین Identity ایجاد شده به ازای Session جاری شما است. لازم به ذکر است اگر به واسته Insert شما، Identity دیگری در یک حوزه دیگر (مانند یک Trigger) ایجاد شود مقدار موجود در این متغییر حاوی آخرین Identity ایجاد شده است. (یعنی Identity ایجاد شده توسط آن تریگر و نه خود جدول). لازم به ذکر است این موضوع به طور کامل در ادامه مقاله شرح داده شده است.
2- استفاده از تابع()Scope_Identity : با استفاده از این تابع میتوانیم آخرین Identify ایجا دشده به ازای Session جاری را بدست آوریم. لازم به ذکر است مقادیر Identity ایجاد شده توسط سایر حوزهها تاثیر در مقدار بازگشتی توسط این تابع ندارد. در ادامه مقاله این موضوع به طور کامل بررسی شده است.
3- استفاده از تابع ident_Current : این تابع آخرین مقدار Identity موجود در یک جدول را نمایش میدهد. ذکر این نکته ضروری است که Identity ایجاد شده توسط سایر Sessionها هم روی خروجی این تابع تاثیرگذار است. چون این تابع آخرین Identity موجود در جدول را به شما نمایش میدهد و نه Identity ایجاد شده به ازای یکSession را.
برای بدست آوردن یک Identity کافی است که پس از درج رکورد در جدول مورد نظر متغییر سیستمی @@Identity و یا توابع Scope_Identity و یا Ident_Current را همانند مثال زیر Select کنید.
USE TEMPDB GO IF OBJECT_ID(N'Employees', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees ( ID int IDENTITY, FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO INSERT INTO Employees (FirstName,LastName) VALUES (N'مسعود',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
خروجی دستورات بالا پس از درج رکورد مورد نظر به صورت زیر است.
اما ممکن است از خودتان این سوال را بپرسید که آیا این توابع در سطح شبکه آخرین مقدار Identity درج شده توسط سایر Sessionها را نمایش میدهند و یا Session جاری را؟ (منظور Sessionی که درخواست مقدار موجود در identity را نموده است).
برای دریافت پاسخ این سوال مطابق مراحل اسکریپهای زیر را اجرا نمایید.
1-ایجاد جدول Employees1
USE TEMPDB GO IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees1 ( ID int IDENTITY(1,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
GO INSERT INTO Employees1(FirstName,LastName) VALUES (N'فرید',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
همانگونه که ملاحضه میکنید @@Identity، Scope_Identity() و Ident_Current هر سه مقدار Identity (عدد 1) ایجاد شده بوسیله دستور Insert را به شما نمایش میدهند.
1- و در انتها در یک Session دیگر دستورات زیر را اجرا نمایید.(واکشی مقدار Identity)
USE tempdb GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
همانطور که مشاهده میکنید در این Seesion ما از SQL خواستهایم آخرین مقدار Identity را به ما نشان داده شود. باید به این نکته توجه کنید با توجه به اینکه در این Session عملیات درجی هنوز انجام نگرفته است که ما Identity ایجاد شده را مشاهده نماییم. بنابراین صرفاً تابع Iden_Current مقدار Identity موجود در جدول را به ما نمایش میدهد.
پس میتوان به این نکته رسید که
@@Idnetity و Scope_Identity : Identity ایجاد به ازای Session جاری را نمایش داده و به مقادیر تولید شده توسط سایر Sessionهای دیگر دسترسی ندارد.
Ident_Current : آخرین Identity موجود در جدول را به شما نمایش میدهد. بنابراین باید این نکته را در نظر داشته باشید که Identityها ایجاد شده توسط سایر Sessionها روی مقدار بازگشتی این تابع تاثیرگدار است.
اما یکی دیگر از مباحث مهم درباره Identity تاثیر Scope بر مقدار Identity است (یعنی چه!) . برای اینکه با مفهوم این موضوع آشنا شوید اسکریپتهای مربوط به مثال زیر را بدقت اجرا کنید.
1- ایجاد جدول Employees1
USE TEMPDB GO IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees1 ( ID int IDENTITY(1,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
همانطور که مشاهده میکنید مقدار شروع برای Identity برابر 1 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(1,1)) .
2- ایجاد جدول Employees2
USE TEMPDB GO IF OBJECT_ID(N'Employees2', N'U') IS NOT NULL DROP TABLE Employees2; GO CREATE TABLE Employees2 ( ID int IDENTITY(100,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
همانطور که مشاهده میکنید مقدار شروع برای Identity برابر 100 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(100,1)).
3- ایجاد یک Trigger به ازای جدول Employees1
USE tempdb GO CREATE TRIGGER Employees1_Insert ON Employees1 FOR INSERT AS BEGIN INSERT Employees2(FirstName,LastName) SELECT FirstName,LastName FROM INSERTED END; GO
Trigger ایجاد شده به ازای جدول Employees1 به ازای عملیات Insert اجرا میشود. همچنین مقادیر درج شده در جدول Employees1 بوسیله جدول Inserted در دسترس است. لازم به ذکر است جدول Inserted یک جدول موقت بوده که توسط Trigger ایجاد شده و داخل خود آن معتبر است.
هدف ما از ایجاد این Trigger تهیه یک کپی از رکوردهایی که در جدول Employees1 درج میشوند است. این کپی قرار است با استفاده از دستور Insert…Select در جدول Employees2 ایجاد گردد.
4- درج یک رکورد در جدول Employees1 و واکشی مقدار Identity
USE tempdb GO INSERT INTO Employees1(FirstName,LastName) VALUES (N'مسعود',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] SELECT IDENT_CURRENT('Employees2') AS [IDENT_CURRENT('Employees2')] GO
مقادیر استخراج شده به ازای Identity به شرح زیر است
1- @@Identity : پس از درج رکورد در جدول Employees1 متغییر سیستمی @@Identity مقدار 100 را نمایش داده است دلیل این موضوع بر میگردد به Trigger موجود در جدول Employees1.
با توجه به اینکه جدول Employees1 دارای یک فیلد Identity بوده است هنگام درج رکورد در جدول مقدار @@Identity=1 است اما چون این جدول دارای Triggerی است که این Trigger خود با جدولی دیگری درگیر است که دارای Identity است مقدار متغییر @@identity=100 خواهد شد.
2- Scope_Identity() : مقدار نمایش داده شده توسط تابع Scope_Identity() برابر با مقدار Identity تخصیص (عدد 1) داده شده به ازای رکورد شما میباشد که این موضوع در اغلب موارد مد نظر برنامهنویسان میباشد.
3- Ident_Current(‘Employees1’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 1) موجود در جدول Employees1 است.
4- Ident_Current(‘Employees2’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 100) موجود در جدول Employees2 است.
چند نکته مهم
1- مقدار بازگردانده شده توسط تابع Ident_Current آخرین مقدار Identity موجود در جدول مورد نظر شما بوده است و عملیات درج سایر کاربران در این مقدار تاثیر گذار است.
2- برای بدست آوردن مقدار Identity درست بهتر است از تابع Scope_Identity() استفاده نماییم. معمولاً در بیشتر مواقع مقدار بازگردانده شده توسط این تابع مد نظر برنامه نویسان است.
3- EntityFramework و Nhibernate هم برای بدست آوردن Identity از تابع Scope_Identity استفاده میکند.
تفاوت دستور Delete و Truncate
دستور Delete جز دستورات DML هست یا بعبارتی Data Manipulation Language میباشد که برای اعمالی چون واکشی ، ذخیره سازی ، تغییرات و حذف مورد استفاده قرار میگیرند
دستور Truncate جز دستورات DDL هست یا بعبارتی Data Definition Language میباشد و برای ایجاد , تغییرات در ساختار آبجکتها ( مثل جدول ، ایندکس و ..) مورد استفاده قرار میگیرد
دستور Delete در زمان اجرا قفل گذاری را در سطح ردیف انجام میدهد (row lock)
دستور Truncate قفل گذاری را در سطح جدول در نظر میگیرد (table lock)
دستور Delete میتواند شامل فیلتر (where) برای حذف ردیفهای خاصی باشد
دستور Truncate نمیتواند شامل فیلتر (where) باشد و روی حذف تمام جدول عمل میکند
نکته :
با استفاده از دستور Delete میتوان بخشی از رکوردهای مشخص را حذف نمود ولی با دستور Truncate تمام رکوردها ی جدول حذف میشوند (دستور Truncate نتیجه اجرای دو دستور Drop و Create میباشد)
دستور Delete نسبت به دستور Truncate کندتر عمل میکند زیرا تمام تغییرات در Log ذخیره میکند در نتیجه قابلیت Rollback ممکن میباشد
دستور Truncate نسبت به دستور Delete سریعتر عمل میکند زیرا به ازای اجرای آن در فایل Log چیزی ثبت نمیشود در نتیجه قابلیت Rollback را ندارد
همراه دستور Delete قابلیت اجرای تریگر را داریم
در اجرای دستور Truncate قابلیت اجرای تریگر را نداریم
نکته:
اگر از Transaction استفاده کنیم هر دو دستور Delete و Truncate قابلیت rollback شدن را دارا هستند
static void Main(string[] args) { int Add(int a, int b) { return a + b; } Console.WriteLine(Add(3, 4)); }
بازنویسی کدهای C# 6 با توابع محلی C# 7
کلاس زیر را که بر اساس امکانات C# 6 تهیه شدهاست، در نظر بگیرید:
public class PersonWithPrivateMethod { public string Name { get; set; } public int Age { get; set; } public override string ToString() { string ageSuffix = GenerateAgeSuffix(Age); return $"{Name} is {Age} year{ageSuffix} old"; } private string GenerateAgeSuffix(int age) { return age > 1 ? "s" : ""; } }
public class PersonWithLocalFuncDelegate { public string Name { get; set; } public int Age { get; set; } public override string ToString() { Func<int, string> generateAgeSuffix = age => age > 1 ? "s" : ""; return $"{Name} is {Age} year{generateAgeSuffix(Age)} old"; } }
اکنون در C# 7 میتوان این Func delegate را به نحو ذیل تبدیل به یک local function کرد:
public class PersonWithLocalFunction { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old"; // Define a local function: string GenerateAgeSuffix(int age) { return age > 1 ? "s" : ""; } } }
مزیت کار با local functions نسبت به Func delegates محلی
در قطعه کد فوق، کار انجام شده صرفا استفادهی از یک Syntax جدید نیست؛ بلکه از لحاظ کارآیی نیز سربار کمتری را به همراه دارد. زمانیکه Func Delegates تعریف میشوند، کار ایجاد یک anonymous type، وهله سازی و فراخوانی آنها توسط کامپایلر صورت میگیرد. اما حین کار با توابع محلی، کامپایلر با یک متد استاندارد سروکار دارد و هیچکدام از مراحل یاد شده و سربارهای آنها رخ نمیدهند (هیچگونه GC allocation ایی نخواهیم داشت). به علاوه اینبار کامپایلر فرصت in-line تعریف کردن متد را به نحو بهتری یافته و به این ترتیب کار سوئیچ بین متدهای مختلف کاهش پیدا میکند که در نهایت سرعت برنامه را افزایش میدهند.
میدان دید توابع محلی
البته با توجه به اینکه متد مثال فوق محلی است، به تمام متغیرها و پارامترهای متد دربرگیرندهی آن نیز دسترسی دارد. بنابراین میتوان پارامتر int age آنرا نیز حذف کرد:
public class PersonWithLocalFunctionEnclosing { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix()} old"; // Define a local function: string GenerateAgeSuffix() { return Age > 1 ? "s" : ""; } } }
خلاصه نویسی توابع محلی به کمک expression bodies
میتوان این متد محلی را به صورت یک expression body ارائه شدهی در C# 6 نیز بیان کرد:
public class PersonWithLocalFunctionExpressionBodied { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old"; // Define a local function: string GenerateAgeSuffix(int age) => age > 1 ? "s" : ""; } }
روش ارسال یک local function به متدی دیگر
امکان ارسال یک تابع محلی به صورت یک Func delegate به متدی دیگر نیز وجود دارد:
public class LocalFunctionsTest { public void PassAnonFunctionToMethod() { var p = new SimplePerson { Name = "Name1", Age = 42 }; OutputSimplePerson(p, GenerateAgeSuffix); string GenerateAgeSuffix(int age) => age > 1 ? "s" : ""; } private void OutputSimplePerson(SimplePerson person, Func<int, string> suffixFunction) { Output.WriteLine( $"{person.Name} is {person.Age} year{suffixFunction(person.Age)} old"); } }
CoffeeScript #3
Syntax
Object & Array
برای تعریف Object در CoffeeScript میتوان دقیقا مانند جاوااسکریپت عمل کرد؛ با یک جفت براکت و ساختار کلید / مقدار. البته همانند تابع، نوشتن براکت اختیاری است. در واقع، شما میتوانید از تورفتگی و هر کلید/مقدار، در خط جدید به جای کاما استفاده کنید:
object1 = {one: 1, two: 2} # Without braces object2 = one: 1, two: 2 # Using new lines instead of commas object3 = one: 1 two: 2 User.create(name: "Vahid Mohammad Taheri")
array1 = [1, 2, 3] array2 = [ 1 2 3 ] array3 = [1,2,3,]
Flow control
طبق قاعدهای که برای نوشتن پرانتز در قبل گفته شد (پرانتز اختیاری است)، در دستورات if و else نیز چنین است:
if true == true "We're ok" if true != true then "Vahid" # برابر است با: # (1 > 0) ? "Yes" : "No!" if 1 > 0 then "Yes" else "No!"
CoffeeScript از اپراتورهای شرطی (:?) پشتیبانی نمی کند و به جای آن از if / else استفاده کنید.
CoffeeScript نیز همانند Ruby امکان نوشتن بدنه شرط را به صورت پسوندی ایجاد کرده است.
alert "It's cold!" if 1 < 5
if not true then "Vahid"
unless true "Vahid"
if true is 1 "OK!"
if true isnt true alert "OK!"
الحاق رشته ها
CoffeeScript امکان الحاق رشتهها را با استفاده از روش الحاق رشتهها در Ruby فراهم کرده است. برای انجام این عمل از {}# در داخل " " استفاده کنید که در داخل براکت میتوانید از دستورات مختلف استفاده کنید. برای مثال:favorite_color = "Blue. No, yel..." question = "Sam: What... is your favorite color? Ben: #{favorite_color} Sam: Wrong! "
var favorite_color, question; favorite_color = "Blue. No, yel..."; question = "Sam: What... is your favorite color? Ben: " + favorite_color + " Sam: Wrong!";
<meta name="fragment" content="!"> <base href="/">
http://www.example.com/?_escaped_fragment_=
http://www.example.com/user/123?_escaped_fragment_=
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
http://www.example.com/user/123?_escaped_fragment_=
<package id="PhantomJS" version="1.9.2" targetFramework="net452" /> <package id="phantomjs.exe" version="1.9.2.1" targetFramework="net452" />
public class AjaxCrawlableAttribute : System.Web.Mvc.ActionFilterAttribute { private const string Fragment = "_escaped_fragment_"; public override void OnActionExecuting(ActionExecutingContext filterContext) { var request = filterContext.RequestContext.HttpContext.Request; var url = request.Url.ToString(); if (request.QueryString[Fragment] != null && !url.Contains("HtmlSnapshot/returnHTML")) { url = url.Replace("?_escaped_fragment_=", string.Empty).Replace(request.Url.Scheme + "://", string.Empty); url = url.Split(':')[1]; filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } }); } return; } }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "HtmlSnapshot", url: "HtmlSnapshot/returnHTML/{*url}", defaults: new { controller = "HtmlSnapshot", action = "returnHTML", url = UrlParameter.Optional }); routes.MapRoute( name: "SPA", url: "{*catchall}", defaults: new { controller = "Home", action = "Index" }) }
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new AjaxCrawlableAttribute()); } }
public ActionResult returnHTML(string url) { var prefix = HttpContext.Request.Url.Scheme + "://" + HttpContext.Request.Url.Host+":"; url = prefix+url; string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); var startInfo = new ProcessStartInfo { Arguments = string.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo.js"), url), FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"), UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, StandardOutputEncoding = System.Text.Encoding.UTF8 }; var p = new Process(); p.StartInfo = startInfo; p.Start(); string output1 = p.StandardOutput.ReadToEnd(); p.WaitForExit(); ViewData["result"] = output1.Replace("<!-- ngView: -->", "").Replace("ng-view=\"\"", ""); return View(); }
@{ Layout = null; } @Html.Raw(ViewBag.result)
console.log(page.content)
var page = require('webpage').create(); var system = require('system'); var lastReceived = new Date().getTime(); var requestCount = 0; var responseCount = 0; var requestIds = []; var startTime = new Date().getTime();; page.onResourceReceived = function (response) { if (requestIds.indexOf(response.id) !== -1) { lastReceived = new Date().getTime(); responseCount++; requestIds[requestIds.indexOf(response.id)] = null; } }; page.onResourceRequested = function (request) { if (requestIds.indexOf(request.id) === -1) { requestIds.push(request.id); requestCount++; } }; function checkLoaded() { return page.evaluate(function () { return document.all["compositionComplete"]; }) != null; } // Open the page page.open(system.args[1], function () { }); var checkComplete = function () { // We don't allow it to take longer than 5 seconds but // don't return until all requests are finished if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) { clearInterval(checkCompleteInterval); console.log(page.content); phantom.exit(); } } // Let us check to see if the page is finished rendering var checkCompleteInterval = setInterval(checkComplete, 300);
<!DOCTYPE html> <html ng-app="appOne"> <head> <meta name="fragment" content="!"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta charset="utf-8" /> <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <meta name="viewport" content="width=device-width" /> <base href="/"> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") <script src="~/Scripts/angular/angular.js"></script> <script src="~/Scripts/angular/angular-route.js"></script> <script src="~/Scripts/angular/angular-animate.js"></script> <script> angular.module('appOne', ['ngRoute'], function ($routeProvider, $locationProvider) { $routeProvider.when('/one', { template: "<div>one</div>", controller: function ($scope) { } }) .when('/two', { template: "<div>two</div>", controller: function ($scope) { } }).when('/', { template: "<div>home</div>", controller: function ($scope) { } }); $locationProvider.html5Mode({ enabled: true }); }); </script> </head> <body> <div id="body"> <section ng-view></section> @RenderBody() </div> <div id="footer"> <ul class='xoxo blogroll'> <li><a href="one">one</a></li> <li><a href="two">two</a></li> </ul> </div> </body> </html>
در این قسمت در خصوص توابع مرتبط با ساختار سلسله مراتبی
صحبت خواهد شد.
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Year].[CY 2007] ) } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
تابع عمو زاده به این صورت کار می کند که دو پارامتر می گیرد . پارامتر اول سطح فعلی را مشخص می کند . پارامتر دوم سطح بالاتر از سطح اول را مشخص می کند در ساختار سلسله مراتبی و خروجی برابر است با سطحی برابر سطح پارامتر اول در زیر مجموعه ی پارامتر دوم و هم تراز پارامتر اول .
خروجی به صورت زیر میباشد:
خوب حالا به ساختار زیر دقت کنید (ساختار سلسله مراتبی Date )
همانطور که مشخص میباشد تاریخها از 2005 تا 2008 و سال 2010 میباشند و فصول عبارتند از دو فصل پایانی سال 2005 و تمامی فصول سال 2006 و 2007 و سه فصل اول سال 2008 و فصل چهارم سال 2010 . حال دوباره به کوئری نوشته شده دقت کنید. در کوئری بالا فصل همسطح فصل اول سال 2006 در سال 2007 مورد واکشی قرار گرفته است که همان فصل اول در سال 2007 میباشد.
حال به بررس کوئری زیر خواهیم پرداخت:
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Semester].[H2 CY 2006] ) } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
در این کوئری ما ابتدا ستون فصل اول سال 2006 را بر می گردانیم . سپس در تابع پسر عمو در نیم فصل دوم سال 2006 به دنبال هم سطح فصل اول 2006 می گردیم .
نمودار درختی زیر توضیح کاملی به ما خواهد داد:
حال برای ادامهی مطلب کار بر روی ساختارهای سلسله مراتبی، ابتدا باید در خصوص نحوهی ایجاد Range توضیحاتی ارایه گردد. دو کوئری زیر را در نظر گرفته و خروجی آنها را با هم مقایسه نمایید
Select { [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Quarter].[Q2 CY 2006], [Date].[Calendar].[Calendar Quarter].[Q3 CY 2006] } on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
و
Select [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006]: [Date].[Calendar].[Calendar Quarter].[Q3 CY 2006] on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
خروجیها به صورت زیر میباشد :
و
مشخص میباشد که از علامت <:> برای ایجاد یک محدوده و جلوگیری از تولید کدهای بلند و طولانی استفاده میشود.
حال کوئری زیر را اجرا کنید:
Select [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006] : cousin( [Date].[Calendar].[Calendar Quarter].[Q1 CY 2006], [Date].[Calendar].[Calendar Semester].[H2 CY 2006] ) on columns, [Measures].[Reseller Sales Amount] on rows From [Adventure Works]
در این کوئری در ابتدا تابع پسر عمو اجرا می گردد، سپس تابع رنج اجرا می گردد و در نتیجه، فاصله ی بین Q1 CY 2006 تا Q3 CY 2006 را بدست میآورد.
نمودار درختی زیر توضیح کاملی به ما خواهد داد :
خروجی به صورت زیر میباشد
در قسمتهای بعدی دیگر توابع MDX Queryها را بررسی میکنیم.
- کش کرنل به صورت پیش فرض بر روی صفحات ایستا فعال شده است؛ نه برای صفحاتی با محتوای پویا که البته این مورد قابل تغییر است که نحوه این تغییر را پایینتر توضیح خواهیم داد.
- اگر آدرس درخواستی شامل کوئری باشد صفحه کش نخواهد شد: http://www.site.info/postarchive.htm?id=25
- برای پاسخ ازمکانیزمهای فشرده سازی پویا استفاده شده باشد مثل gzip کش نخواهد شد
- صفحه درخواست شده صفحه اصلی سایت باشد کش نخواهد شد : http://www.dotnettip.info ولی اگر درخواست بدین صورت باشه http://www.domain.com/default.htm کش خواهد کرد.
- درخواست به صورت ناشناس anonymous نباشد و نیاز به authentication داشته باشد کش نخواهد شد (یعنی در هدر شامل گزینه authorization میباشد).
- درخواست باید از نوع نسخه http1 به بعد باشد.
- اگر درخواست شامل Entity-body باشد کش نخواهد کرد.
- درخواست شامل If-Range/Range header باشد کش نمیشود.
- کل حجم response بییشتر از اندازه تعیین شده باشد کش نخواهد گردید، این اندازه در کلید ریجستری UriMaxUriBytes قرار دارد. اطلاعات بیشتر
- اندازه هدر بیشتر از اندازه تعیین شده باشد که عموما اندازه تعیین شده یک کیلو بایت است.
- کش پر باشد، کش انجام نخواهد گرفت.
برای تعیین مقدار سایز کش response که در بالا اشاره کردیم میتوانید در همان پنجره، گزینه edit feature settings را انتخاب کنید.
این قسمت از مطلب که به نقل از مقاله آقای Karol Jarkovsky در این آدرس است یک سری تست هایی با نرم افزار(Web Capacity Analysis Tool (WCAT گرفته است که به نتایج زیر دست پیدا کرده است: