static void Main() { int factor = 2; Func<int, int> multiplier = n => n * factor; Console.WriteLine (multiplier (3)); // 6 }
عبارت لامبدا زمانی ارزیابی میشود که delegate متناظر فراخوانی (Invoke) گردد؛ نه زمانیکه متغیر اصطلاحا capture میشود:
int factor = 2; Func<int, int> multiplier = n => n * factor; factor = 10; Console.WriteLine (multiplier (3)); // 30
عبارات لامبدا خود میتوانند captured variableها را تغییر دهند:
int seed = 0; Func<int> natural = () => seed++; Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 1 Console.WriteLine (seed); // 2
static Func<int> Natural() { int seed = 0; return () => seed++; // Returns a closure } static void Main() { Func<int> theNatural = Natural(); Console.WriteLine (theNatural ()); // 0 Console.WriteLine (theNatural ()); // 1 }
static Func<int> Natural() { return() => { int seed = 0; return seed++; }; } static void Main() { Func<int> natural = Natural(); Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 0 }
Action[] actions = new Action[3]; for (int i = 0; i < 3; i++) actions [i] = () => Console.Write (i); foreach (Action a in actions) a(); // 333
Action[] actions = new Action[3]; int i = 0; actions[0] = () => Console.Write (i); i = 1; actions[1] = () => Console.Write (i); i = 2; actions[2] = () => Console.Write (i); i = 3; foreach (Action a in actions) a(); // 333
Action[] actions = new Action[3]; for (int i = 0; i < 3; i++) { int loopScopedi = i; actions [i] = () => Console.Write (loopScopedi); } foreach (Action a in actions) a(); // 012
(function () { var itemCtx = {}; itemCtx.Templates = {}; itemCtx.Templates.Header = "<div><b title=\"اطلاعات فیلم ها\">Movie Data</b></div><ul>"; itemCtx.Templates.Item = MyOverrideTemplate; itemCtx.Templates.Footer = "</ul>"; itemCtx.BaseViewID = 1; itemCtx.ListTemplateType = 100; //For Generic List (More : http://msdn.microsoft.com/en-us/library/ms462947(v=office.12).aspx) SPClientTemplates.TemplateManager.RegisterTemplateOverrides(itemCtx); })(); function GT(val , index) { // example of val : 60 % var temp = val.split(' ')[0]; var v = Number(temp); return v > index; } function LT(val, index) { var temp = val.split(' ')[0]; var v = Number(temp); return v < index; } function EQ(val, index) { var temp = val.split(' ')[0]; var v = Number(temp); return v == index; } function MyOverrideTemplate(ctx) { if (LT(ctx.CurrentItem.PopularityPercent ,25)) { return "<li title='خیلی کم بازدید' style='color:white;background-color: red;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,50)) { return "<li title='کم بازدید' style='color:maroon;background-color: #ffcc00;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,75)) { return "<li title='بازدید معمولی' style='color:#ffcc00;background-color: maroon;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (LT(ctx.CurrentItem.PopularityPercent ,95)) { return "<li title='پر بازدید' style='color:yellow;background-color: blue;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else if (EQ(ctx.CurrentItem.PopularityPercent, 100)) { return "<li title='بالاترین بازدید' style='color:black;background-color: green;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } else { return "<li title='نامعلوم' style='color:navy;background-color: yellow;width: 300px;height: 24px;'>" + ctx.CurrentItem.Title + " – " + ctx.CurrentItem.PopularityPercent + "</li>"; } }
~site/_catalogs/masterpage/MyJsLinkSample.js
کارهای سورس باز قابل توجهی از برنامه نویسهای ایرانی یافت نمیشوند؛ عموما کارهای ارائه شده در حد یک سری مثال یا کتابخانههای کوچک است و در همین حد. یا گاهی هم انگشت شمار پروژههایی کامل. مثل یک وب سایت یا یک برنامه نصفه نیمه دبیرخانه و امثال آن. اینها هم خوب است از دیدگاه به اشتراک گذاری اطلاعات، ایدهها و هم ... یک مزیت دیگر را هم دارد و آن این است که بتوان کیفیت عمومی کد نویسی را حدس زد.
اگر کیفیت کدها رو بررسی کنید به یک نتیجهی کلی خواهید رسید: "عموم برنامه نویسهای ایرانی (حداقل اینهایی که چند عدد کار سورس باز به اشتراک گذاشتهاند) با مفهومی به نام Refactoring هیچگونه آشنایی ندارند". مثلا یک برنامهی WinForm تهیه کردهاند و کل سورس برنامه همان چند عدد فرم برنامه است و هر فرم بالای 3000 سطر کد دارد. دوستان عزیز! به این میگویند «فاجعهای به نام کدنویسی!» صاحب اول و آخر این نوع کدها خودتان هستید! شاید به همین جهت باشد که عمدهی پروژههای سورس باز پس از اینکه برنامه نویس اصلی از توسعهی آن دست میکشد، «میمیرند». چون کسی جرات نمیکند به این کدها دست بزند. مشخص نیست الان این قسمت را که تغییر دادم، کجای برنامه به هم ریخت. تستی ندارند. ساختاری را نمیتوان از آنها دریافت. منطق قسمتهای مختلف برنامه از هم جدا نشده است. برنامه یک فرم است با چند هزار سطر کد در یک فایل! کار شما شبیه به کد اسمبلی چند هزار سطری حاصل از decompile یک برنامه که نباید باشد!
به همین جهت قصد دارم یک سری «ساده» Refactoring را در این سایت ارائه دهم. روی سادگی هم تاکید کردم، چون اگر عموم برنامه نویسها با همین موارد به ظاهر ساده آشنایی داشتند، کیفیت کد نویسی بهتری را میشد در نتایج عمومی شده، شاهد بود.
این مورد در راستای نظر سنجی انجام شده هم هست؛ درخواست مقالات خالص سی شارپ در صدر آمار فعلی قرار دارد.
Refactoring چیست؟
Refactoring به معنای بهبود پیوسته کیفیت کدهای نوشته شده در طی زمان است؛ بدون ایجاد تغییری در عملکرد اصلی برنامه. به این ترتیب به کدهایی دست خواهیم یافت که قابلیت آزمون پذیری بهتری داشته، در مقابل تغییرات مقاوم و شکننده نیستند و همچنین امکان به اشتراک گذاری قسمتهایی از آنها در پروژههای دیگر نیز میسر میشود.
قسمت اول - مجموعهها را کپسوله کنید
برای مثال کلاسهای ساده زیر را در نظر بگیرید:
namespace Refactoring.Day1.EncapsulateCollection
{
public class OrderItem
{
public int Id { set; get; }
public string Name { set; get; }
public int Amount { set; get; }
}
}
using System.Collections.Generic;
namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
public List<OrderItem> OrderItems { set; get; }
}
}
نکته اول: هر کلاس باید در داخل یک فایل جدا قرار گیرد. «لطفا» یک فایل درست نکنید با 50 کلاس داخل آن. البته اگر باز هم یک فایل باشد که بتوان 50 کلاس را داخل آن مشاهده کرد که چقدر هم عالی! نه اینکه یک فایل باشد تا بعدا 50 کلاس را با Refactoring از داخل آن بیرون کشید!
قطعه کد فوق، یکی از روشهای مرسوم کد نویسی است. مجموعهای به صورت یک List عمومی در اختیار مصرف کننده قرار گرفته است. حال اجازه دهید تا با استفاده از برنامه FxCop برنامه فوق را آنالیز کنیم. یکی از خطاهایی را که نمایش خواهد داد عبارت زیر است:
Error, Certainty 95, for Do Not Expose Generic Lists
بله. لیستهای جنریک را نباید به همین شکل در اختیار مصرف کننده قرار داد؛ چون به این صورت هر کاری را میتوانند با آن انجام دهند، مثلا کل آن را تعویض کنند، بدون اینکه کلاس تعریف کننده آن از این تغییرات مطلع شود.
پیشنهاد FxCop این است که بجای List از Collection یا IList و موارد مشابه استفاده شود. اگر اینکار را انجام دهیم اینبار به خطای زیر خواهیم رسید:
Warning, Certainty 75, for Collection Properties Should Be ReadOnly
FxCop پیشنهاد میدهد که مجموعه تعریف شده باید فقط خواندنی باشد.
چکار باید کرد؟
بجای استفاده از List جهت ارائه مجموعهها، از IEnumerable استفاده کنید و اینبار متدهای Add و Remove اشیاء به آنرا به صورت دستی تعریف نمائید تا بتوان از تغییرات انجام شده بر روی مجموعه ارائه شده، در کلاس اصلی آن مطلع شد و امکان تعویض کلی آنرا از مصرف کننده گرفت. برای مثال:
using System.Linq;
using System.Collections.Generic;
namespace Refactoring.Day1.EncapsulateCollection
{
public class Orders
{
private int _orderTotal;
private List<OrderItem> _orderItems;
public IEnumerable<OrderItem> OrderItems
{
get { return _orderItems; }
}
public void AddOrderItem(OrderItem orderItem)
{
_orderTotal += orderItem.Amount;
_orderItems.Add(orderItem);
}
public void RemoveOrderItem(OrderItem orderItem)
{
var order = _orderItems.Find(o => o == orderItem);
if (order == null) return;
_orderTotal -= orderItem.Amount;
_orderItems.Remove(orderItem);
}
}
}
اکنون اگر برنامه را مجددا با fxCop آنالیز کنیم، دو خطای ذکر شده دیگر وجود نخواهند داشت. اگر این تغییرات صورت نمیگرفت، امکان داشتن فیلد _orderTotal غیر معتبری در کلاس Orders به شدت بالا میرفت. زیرا مصرف کننده مجموعه OrderItems میتوانست به سادگی آیتمی را به آن اضافه یا از آن حذف کند، بدون اینکه کلاس Orders از آن مطلع شود یا اینکه بتواند عکس العمل خاصی را بروز دهد.
اکثر خدمات گوگل دارای API هم هستند و به این ترتیب با استفاده از برنامه نویسی نیز میتوان به آنها دسترسی پیدا کرد. برای نمونه API دسترسی به Blogger در اینجا توضیح داده شده است. برای کار با این امکانات یا میتوان چرخ را از نو اختراع کرد یا از کتابخانههای مرتبطی همانند Gdata API for .NET استفاده نمود. برای دات نت فریم ورک، از آدرس http://code.google.com/p/google-gdata/ میتوان آخرین کتابخانههای کار با GData یا Google Data API را دریافت کرد. برای نمونه فایل Google_Data_API_Setup_1.9.0.0.msi فعلی آن حدود 28 مگ حجم دارد و به درد کسانی میخورد که علاقمند هستند تا تمام امکانات موجود آنرا بررسی کنند. راه سادهتری هم برای دسترسی به این کتابخانهها وجود دارد؛ میتوان از NuGet استفاده کرد.
به این ترتیب به سادگی و سرعت هرچه تمامتر فایل 200 کیلوبایتی Google.GData.Client.dll دریافت شده و ارجاعی نیز به آن اضافه خواهد شد. همین حد جهت کار با بلاگر کافی است.
برای نمونه قطعه کد زیر کار ارسال یک مطلب جدید به وبلاگ بلاگری شما را انجام خواهد داد:
using System;
using System.Collections.Generic;
using Google.GData.Client;
namespace BloggerAutoPoster
{
public class BloggerAutoPoster
{
public string UserName { set; get; }
public string Password { set; get; }
public string PostTitle { set; get; }
public IList<string> PostTags { set; get; }
public string PostBody { set; get; }
public string BlogUrl { set; get; }
public bool PostAsDraft { set; get; }
public bool PostNewEntry()
{
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(UserName, Password)
};
var newPost = constructNewEntry();
var result = service.Insert(new Uri(BlogUrl), newPost);
return result != null;
}
private AtomEntry constructNewEntry()
{
var newPost = new AtomEntry
{
Title = { Text = PostTitle },
Content = new AtomContent
{
Content = string.Format(@"<div xmlns=""http://www.w3.org/1999/xhtml"">{0}</div>", PostBody),
Type = "xhtml"
},
IsDraft = PostAsDraft
};
foreach (var tag in PostTags)
{
newPost.Categories.Add(
new AtomCategory
{
Term = tag,
Scheme = "http://www.blogger.com/atom/ns#"
});
}
return newPost;
}
}
}
مثالی از استفاده آن هم به صورت زیر میباشد:
new BloggerAutoPoster
{
BlogUrl = "https://www.blogger.com/feeds/number/posts/default",
UserName = "name@gmail.com",
Password = "pass",
PostTitle = "بررسی ارسل خودکار-3",
PostTags = new List<string> { "بررسی ارسال خودکار" },
PostBody = "تست میشود123",
PostAsDraft = false
}.PostNewEntry();
نام کاربری و کلمه عبور آن، همان مشخصات وارد شدن به اکانت جیمیل شما است. اگر میخواهید مطلب ارسالی بلافاصله در سایت ظاهر نشود PostAsDraft را true کنید. همچنین BlogUrl آن، همانطور که ملاحظه میکنید فرمت خاصی دارد. جهت یافتن آن میتوان از قطعه کد زیر کمک گرفت:
using System;
using System.Collections.Generic;
using System.Linq;
using Google.GData.Client;
namespace BloggerAutoPoster
{
public class BlogInfo
{
public string Title { set; get; }
public string Url { set; get; }
}
public class BloggerInfo
{
public static IList<BlogInfo> FindMyBlogsUrls(string username, string password)
{
var result = new List<BlogInfo>();
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(username, password)
};
var query = new FeedQuery { Uri = new Uri("https://www.blogger.com/feeds/default/blogs") };
var feed = service.Query(query);
if (feed == null)
throw new NotSupportedException("You don't have any blogs!");
foreach (var entry in feed.Entries)
{
result.AddRange(entry.Links.Where(t => t.Rel.Equals("http://schemas.google.com/g/2005#post"))
.Select(t => new BlogInfo
{
Url = new Uri(t.HRef.ToString()).AbsoluteUri,
Title = entry.Title.Text
}));
}
return result;
}
}
}
توسط کد فوق، آدرس ویژه و عنوان تمام بلاگهای ثبت شدهی بلاگری شما بازگشت داده میشود.
فرض کنید میخواهیم مطمئن شویم که موجودیتی که توسط یک کلاینت WCF تغییر کرده است، تنها در صورتی بروز رسانی شود که شناسه (token) همزمانی آن تغییر نکرده باشد. به بیان دیگر شناسه ای که هنگام دریافت موجودیت بدست میآید، هنگام بروز رسانی باید مقداری یکسان داشته باشد.
مدل زیر را در نظر بگیرید.
میخواهیم یک سفارش (order) را توسط یک سرویس WCF بروز رسانی کنیم در حالی که اطمینان حاصل میکنیم موجودیت سفارش از زمانی که دریافت شده تغییری نکرده است. برای مدیریت این وضعیت دو رویکرد تقریبا متفاوت را بررسی میکنیم. در هر دو رویکرد از یک ستون همزمانی استفاده میکنیم، در این مثال فیلد TimeStamp.
- در ویژوال استودیو پروژه جدیدی از نوع WCF Service Library بسازید و نام آن را به Recipe6 تغییر دهید.
- روی نام پروژه کلیک راست کنید و گزینه Add New Item را انتخاب کنید. سپس گزینههای Data -> Entity Data Model را برگزینید. از ویزارد ویژوال استودیو برای اضافه کردن مدل جاری و جدول Orders استفاده کنید. در EF Designer روی فیلد TimeStamp کلیک راست کنید و گزینه Properties را انتخاب کنید. سپس مقدار CuncurrencyMode آنرا به Fixed تغییر دهید.
- فایل IService1.cs را باز کنید و تعریف سرویس را مطابق لیست زیر بروز رسانی کنید.
[ServiceContract] public interface IService1 { [OperationContract] Order InsertOrder(); [OperationContract] void UpdateOrderWithoutRetrieving(Order order); [OperationContract] void UpdateOrderByRetrieving(Order order); }
- فایل Service1.cs را باز کنید و پیاده سازی سرویس را مطابق لیست زیر تکمیل کنید.
public class Service1 : IService1 { public Order InsertOrder() { using (var context = new EFRecipesEntities()) { // remove previous test data context.Database.ExecuteSqlCommand("delete from [orders]"); var order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; context.Orders.Add(order); context.SaveChanges(); return order; } } public void UpdateOrderWithoutRetrieving(Order order) { using (var context = new EFRecipesEntities()) { try { context.Orders.Attach(order); if (order.Status == "Received") { context.Entry(order).Property(x => x.Quantity).IsModified = true; context.SaveChanges(); } } catch (OptimisticConcurrencyException ex) { // Handle OptimisticConcurrencyException } } } public void UpdateOrderByRetrieving(Order order) { using (var context = new EFRecipesEntities()) { // fetch current entity from database var dbOrder = context.Orders .Single(o => o.OrderId == order.OrderId); if (dbOrder != null && // execute concurrency check StructuralComparisons.StructuralEqualityComparer.Equals(order.TimeStamp, dbOrder.TimeStamp)) { dbOrder.Quantity = order.Quantity; context.SaveChanges(); } else { // Add code to handle concurrency issue } } } }
- برای تست این سرویس به یک کلاینت نیاز داریم. پروژه جدیدی از نوع Console Application به راه حل جاری اضافه کنید و کد آن را مطابق لیست زیر تکمیل کنید. با کلیک راست روی نام پروژه و انتخاب گزینه Add Service Reference سرویس پروژه را هم ارجاع کنید. دقت کنید که ممکن است پیش از آنکه بتوانید سرویس را ارجاع کنید نیاز باشد روی آن کلیک راست کرده و از منوی Debug گزینه Start Instance را انتخاب کنید تا وهله از سرویس به اجرا در بیاید.
class Program { static void Main(string[] args) { var service = new Service1Client(); var order = service.InsertOrder(); order.Quantity = 5; service.UpdateOrderWithoutRetrieving(order); order = service.InsertOrder(); order.Quantity = 3; service.UpdateOrderByRetrieving(order); } }
شرح مثال جاری
متد ()InsertOrder دادههای پیشین را حذف میکند، سفارش جدیدی میسازد و آن را در دیتابیس ثبت میکند. در آخر موجودیت جدید به کلاینت باز میگردد. موجودیت بازگشتی هر دو مقدار OrderId و TimeStamp را دارا است که توسط دیتابیس تولید شده اند. سپس در کلاینت از دو رویکرد نسبتا متفاوت برای بروز رسانی موجودیت استفاده میکنیم.
در رویکرد نخست، متد ()UpdateOrderWithoutRetrieving موجودیت دریافت شده از کلاینت را Attach میکند و چک میکند که مقدار فیلد Status چیست. اگر مقدار این فیلد "Received" باشد، فیلد Quantity را با EntityState.Modified علامت گذاری میکنیم و متد ()SaveChanges را فراخوانی میکنیم. EF دستورات لازم برای بروز رسانی را تولید میکند، که فیلد quantity را مقدار دهی کرده و یک عبارت where هم دارد که فیلدهای OrderId و TimeStamp را چک میکند. اگر مقدار TimeStamp توسط یک دستور بروز رسانی تغییر کرده باشد، بروز رسانی جاری با خطا مواجه خواهد شد. برای مدیریت این خطا ما بدنه کد را در یک بلاک try/catch قرار میدهیم، و استثنای OptimisticConcurrencyException را مهار میکنیم. این کار باعث میشود اطمینان داشته باشیم که موجودیت Order دریافت شده از متد ()InsertOrder تاکنون تغییری نکرده است. دقت کنید که در مثال جاری تمام خواص موجودیت بروز رسانی میشوند، صرفنظر از اینکه تغییر کرده باشند یا خیر.
رویکرد دوم نشان میدهد که چگونه میتوان وضعیت همزمانی موجودیت را پیش از بروز رسانی مشخصا دریافت و بررسی کرد. در اینجا میتوانید مقدار TimeStamp موجودیت را از دیتابیس بگیرید و آن را با مقدار موجودیت کلاینت مقایسه کنید تا وجود تغییرات مشخص شود. این رویکرد در متد ()UpdateOrderByRetrieving نمایش داده شده است. گرچه این رویکرد برای تشخیص تغییرات خواص موجودیتها و یا روابط شان مفید و کارآمد است، اما بهترین روش هم نیست. مثلا ممکن است از زمانی که موجودیت را از دیتابیس دریافت میکنید، تا زمانی که مقدار TimeStamp آن را مقایسه میکنید و نهایتا متد ()SaveChanges را صدا میزنید، موجودیت شما توسط کلاینت دیگری بروز رسانی شده باشد.
مسلما رویکرد دوم هزینه برتر از رویکرد اولی است، چرا که برای مقایسه مقادیر همزمانی موجودیت ها، یکبار موجودیت را از دیتابیس دریافت میکنید. اما این رویکرد در مواقعی که Object graphهای بزرگ یا پیچیده (complex) دارید بهتر است، چون پیش از ارسال موجودیتها به context در صورت برابر نبودن مقادیر همزمانی پروسس را لغو میکنید.
درس خوندن، ارزشش رو داره؟
در این قسمت میخواهیم بیشتر در خصوص توابع مرتبط با ساختار سلسله مراتبی صحبت کنیم. برای آشنایی با این توابع و امکانات MDX Query، مقاله را با بررسی چندین Query دنبال خواهیم کرد.
بدست آوردن تمامی برادران یک سطح خاص :
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.children on rows From [Adventure Works]
در کوئری بالا تمامی مشتریانی را که دارای کد پستی مشابه با کد پستی [Crystal Zheng]. میباشند، واکشی کرده ایم.
به عبارت دیگر با اعمال [Crystal Zheng].parent، به کد پستی مشتری دسترسی پیدا کرده ایم (برای درک بیشتر در زیر ساختار سلسله مراتبی موقعیت جغرافیایی مشتریان را ببینید) و سپس با اعمال .children به تمامی مشتریان موجود در آن کد پستی رسیده ایم؛ که عملا همان برادران [Crystal Zheng] می باشند.
نتیجه کوئری بالا در زیر نمایش داده شده است
راه بهتر برای بدست آوردن تمامی برادران یک سطح، استفاده از تابع siblings میباشد.
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].siblings on rows From [Adventure Works]
کوئریهای بالا جواب یکسانی را بر میگردانند. به عبارت دیگر تابع siblings عملا کار دو تابع parent.children را انجام میدهد
برای بدست آوردن برادر ارشد به صورت زیر عمل میکنیم (اولین بچه در ساختار سلسله مراتبی)
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.firstchild on rows From [Adventure Works]
و یا از تابع زیر استفاده میکنیم
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].firstsibling on rows From [Adventure Works]
هر دو کوئری به جواب یکسان خواهند رسید.
و برای بدست آوردن آخرین برادر در ساختار سلسله مراتبی (برادر ته تغاری) از دو روش زیر میتوان استفاده کرد.
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.lastchild on rows From [Adventure Works]
یا
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].lastsibling on rows From [Adventure Works]
برای توضیح بیشتر میتوان اضافه کرد که در کوئری بالا میزان فروش اینترنتی را برای آخرین مشتری در موقعیت جغرافیایی مشتری با نام [Crystal Zheng] واکشی شده است.
حال تصور کنید بخواهیم میزان فروش اینترنتی را برای تمامی مشتریان ایالت [Yveline] بدست بیاوریم. در این صورت MDX Query به شکل زیر خواهد بود
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[State-Province].[Yveline] ,[Customer].[Customer Geography].[Customer] )on rows From [Adventure Works]
تابع descendants دارای دو پارامتر میباشد. اولی برای مشخص نمودن شروع و مبدا در ساختار سلسله مراتبی و دومین برای مشخص کردن سطح واکشی در ساختار سلسله مراتبی میباشد. به عبارت دیگر در کوئری بالا تمامی زاد و رود ایالت [Yveline] در سطح شهر واکشی شده است و میزان فروش اینترنتی آن نمایش داده شده است.
در زیر یک کوئری ترکیبی با استفاده از دو تابع ancestor و descendants نوشته شده است.
Select [Measures].[Internet Sales Amount] on columns, descendants( ancestor( [Customer].[Customer Geography].[Customer].[Crystal Zheng], [Customer].[Customer Geography].[State-Province] ) ,[Customer].[Customer Geography].[Customer] )on rows From [Adventure Works]
در اینجا ابتدا جد یک مشتری در سطح ایالت بدست آمده سپس زاد و رود آن در سطح مشتری بدست می آید .
برای بدست آوردن فروش اینترنتی تمامی شهرهای کشور فرانسه میتوانیم به صورت زیر عمل کنیم.
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City] ) on rows From [Adventure Works]
تابع descendants دارای یک پارامتر سوم هم میباشد که مشخص کنندهی میزان واکشی سطوح میباشد و به صورت پیش فرض Self میباشد. بنابر این کوئری بالا و پایین ، نتیجه یکسان خواهند داشت
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], self )on rows From [Adventure Works]
حال اگر بخواهیم فروش اینترنتی را برای تمامی زاد و رود کشور فرانسه از سطح شهر به پایین واکشی کنیم داریم :
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], self_and_after ) on rows From [Adventure Works]
در این حالات تمامی زاد و رود کشور فرانسه از سطح شهر به پایین در خروجی قرار می گیرد .به این صورت که ابتدا اولین شهر می آید؛ سپس اولین کد پستی در آن شهر و بعد تمامی مشتری های آن کد پستی و بعد کد پستی بعدی و ...
به دست آوردن تمامی زاد و رود فرانسه از سطح بعد از شهر .
به عبارت دیگر ، خروجی باتوجه به ساختار سلسله مراتبی تعریف شده عبارت است از کد پستی و تمام مشتریان آن کد پستی و سپس کد پستی بعدی .
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], after )on rows From [Adventure Works]
در کوئری فوق، خود شهر در خروجی نمایش داده نمیشود.
به دست آوردن زاد و رود فرانسه تا یک سطح قبل از شهر .
در این حالت فرانسه و تمامی ایالت های آن در خروجی آورده می شود .
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], before )on rows From [Adventure Works]
همچنین میتوان دومین پارامتر تابع را به صورت عدد وارد کرد و این عدد بیانگر تعداد سطح پایینتر از پارامتر اول در ساختار سلسله مراتبی میباشد.
به عنوان مثال :
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], 2, before ) on rows From [Adventure Works]
در این حالت فرانسه و تمامی ایالت های آن در خروجی قرار می گیرد .
در ابتدا دو سطح ار کشور پایین می رویم و به شهر می رسیم و بعد زاد و رود فرانسه تا یکی قبل از شهر را بر می گرداند .
در قسمتهای بعدی در خصوص دیگر توابع مرتبط با ساختارهای سلسله مراتبی، توضیحاتی را ارایه خواهم کرد.
Hello, you should definitely check this thing out http://www.newsl5.net/biz/?page=xyz
این ایمیلها هم جعلی نبودند. یعنی واقعا از اکانت یاهوی من ارسال شده بودند و در قسمت sent وجود خارجی داشتند! فقط IP ارسال کننده آن (115.78.224.246) متعلق به کشور ویتنام بود (IP ارسال کننده را در هدر ایمیل ارسالی میتوان مشاهده کرد).
این مساله باعث شد که من سیستم را چندین بار چک کنم؛ از لحاظ بحث ویروس تا اسپایور و غیره. «هیچ» مشکلی مشاهده نشد.
مرحله بعد کمی در مورد یاهو جستجو کردم و مشخص شد که یاهو با session hijacking به شدت مشکل دارد. همچنین ابزار دیگری که میتواند به این session hijacking کمک کند خود «فایرفاکس» است. فایرفاکس حاوی گزینهای است که سشنهای قبلی شما را ذخیره میکند. زمانیکه مرورگر را بسته و پس از مدتی آنرا باز میکنیم، یک راست و قشنگ همان سشن قبلی مثلا یاهو را بازیابی کرده و کار ادامه پیدا میکند.
کمی گشتم و این قابلیت رو به کل غیرفعال کردم. برای غیرفعال کردن آن «Disable Session Restore in Firefox» را در گوگل جستجو کنید.
و خلاصه آن به صورت زیر است:
در نوار آدرس فایرفاکس تایپ کنید about:config
در ادامه موارد زیر را یافته و غیرفعال کنید:
browser.sessionstore.resume_from_crash;false browser.sessionstore.resume_session_once;false browser.sessionstore.restore_pinned_tabs_on_demand;false browser.sessionstore.restore_hidden_tabs;false services.sync.prefs.sync.browser.sessionstore.restore_on_demand;false
راه سادهتر:
افزونه session manager را نصب کنید
در قسمت session manager options در برگه startup & shutdown آن کلا بحث ذخیره سازی سشن در حین بسته شدن مرورگر را غیرفعال کنید.
و به صورت خلاصه: تنظیمات پیش فرض فایرفاکس از لحاظ امنیتی مناسب نیستند.
ضمن اینکه ایمیل فوق رو من هفتهای یکی دو بار از تمام افرادی که میشناسم دریافت میکنم! به عبارتی خیلیها گرفتار این مساله شدهاند.
ذخیره سازی سشنها به نظر کارها رو ساده میکنه. مرورگر رو باز میکنی همه چیز مثل قبل از بسته شدن آن است و ... همین یعنی مشکل امنیتی. خصوصا مراجعه به سایتها و لینکهایی که از باگهای XSS سوء استفاده میکنند.
یک کاراکتر کنترلی، یک نقطه کدی است که به وسیله علائم نوشتاری قابل نمایش نباشد. مانند Backspace
تعریف بالا به ما میگوید که در یک متن نوشتاری، به غیر از کد حروفی که مشاهده میکنیم، کدهای دیگری هم هستن که قابل نمایش نیستند ولی بین متون وجود دارند. شاید شما تعدادی از آنها را بشناسید مثل کدهای 10 و 13 برای خط بعد و اول سطر که به line feed و carriage return معروف هستند. در این نوشتار قصد داریم با تعدادی از آنها آشنا شویم.
قبل از آغاز این نوشتار به شما توصیه میکنم یک نگاه اجمالی هم که شده بر نوشتار «داستانی از unicode» داشته باشید تا اطلاعات تکمیلیتری از این نوشتار به دست آورید. مبحث کلیدهای کنترلی از زمانی آغاز شد که کدهای اسکی ایجاد شدند و به دو دستهی c0 و c1 تقسیم شدند. خود کدهای اسکی هم بر اساس کدهای تلگراف ایجاد شدند و بسیاری از کلیدهای کنترلی هم از آنجا به استاندارد اسکی پیوستند و برای ارتباط و کنترل دستگاههایی چون چاپگرها و تهیه اطلاعات متا در مورد طلاعاتی که قرار بود در نوار مغناطیسی ذخیره شوند به کار رفتند. به عنوان نمونه کد 10 به عنوان line feed در چاپگر، یک خط کاغذ را به سمت داخل میکشید و کد 13 هم باعث میشد چاپگر به ابتدای سطر بازگردد. البته بیشتر این کاراکترها در پردازش متون به خصوص امروزه استفاده نمیشوند و فقط یک سری از آنها رایج هستند؛ مثل دو موردی که در بالا و در همین خط به آنها اشاره شد.
دستهی c0 از کد 0 آغاز شده و تا کد 31 ادامه مییابد. دو کد بعدی که کدهای Space و DEL هستند در هیچ گروهی قرار نمیگیرند. گروه c1 از کدهای 128 آغاز شده و تا 159 ادامه مییابند که جدول این گروهها و کلیدها کنترلی را میتوانید مشاهده کنید. برای مثال اولین کلید کنترلی که کد آن 0 است به نام نال است که در قدیم هم برای بستن رشتهها در زبان سی از آن استفاده میکردیم.
هر چند به مرور زمان هم تعدادی از همین کلیدهای کنترلی کاربرد خود را از دست دادند و برای آنها شکلک هایی چون خنده، قلب، نت موسیقی و ... را قرار دادند ولی گاهی اوقات برنامه نویسها هنوز در برنامههای خود از کد آنها برای کارهایی چون انجام عملیات بیتی استفاده میکنند.
استفادههای C0
کلیدهای کنترلی این دسته بیشتر برای منظم ساختن متنهای ساده و همچنین ایجاد ارتباط در پروتکل ارتباطی و دستگاههای مختلف به کار میرفت؛ ارسال فرمانهایی چون آغاز و توقف کار یا انجام عملی خاص توسط هر یک از این کلیدها صورت میگرفت. دستگاههایی چون کارت پانچها، ماشین تایپ و موارد مشابه، از این نوع هستند. با اینکه عمر این دستگاه به سر آمد ولی کلیدهای کنترلی جان سالم به در بردند.
استفادههای C1
این دسته در اواخر سال 1970 اضافه شدند و بیشتر برای ارتباط با چاپگر و صفحهی نمایش به کار میرفتند؛ مثل پیمایشهای افقی و عمودی، تعریف ناحیهای برای پر کردن فرم و Line-Break و کلیدهای انتقالی (شیفت) برای پشتیبانی از کلیدهای کنترلی و قابل چاپ بیشتر. 2 تا از کلیدها هم برای استفادهی خصوصی برنامه نویس کنار گذاشته شدند و 4 تا هم رزرو شده برای استفادهی آینده، تا بعدا استانداردسازی شوند.
کلیدهای کنترلی در سی شارپ
بسیاری از ما از علامت \ در کدهایمان برای قرار دادن کلیدهای کنترلی استفاده میکنیم مثل r\n\ که ترکیب دو کد CR و LF است.
برای شناسایی یک کلید کنترلی در سی شارپ از متد ایستای Char.IsControl استفاده مینماییم. کد زیر در مجموعهی MSDN برای نشان دادن قابلیت این متد نوشته شده است که در طی یک حلقه رنجی از کد پوینتها را بررسی کرده و نتیجه را به صورت شش ستونی در کنسول نمایش میدهد. یا کد مشابه دیگر که بر اساس دسیمال نمایش میدهد.
using System; public class ControlChars { public static void Main() { int charsWritten = 0; for (int ctr = 0x00; ctr <= 0xFFFF; ctr++) { char ch = Convert.ToChar(ctr); if (char.IsControl(ch)) { Console.Write(@"\U{0:X4} ", ctr); charsWritten++; if (charsWritten % 6 == 0) Console.WriteLine(); } } } } // The example displays the following output to the console: // \U0000 \U0001 \U0002 \U0003 \U0004 \U0005 // \U0006 \U0007 \U0008 \U0009 \U000A \U000B // \U000C \U000D \U000E \U000F \U0010 \U0011 // \U0012 \U0013 \U0014 \U0015 \U0016 \U0017 // \U0018 \U0019 \U001A \U001B \U001C \U001D // \U001E \U001F \U007F \U0080 \U0081 \U0082 // \U0083 \U0084 \U0085 \U0086 \U0087 \U0088 // \U0089 \U008A \U008B \U008C \U008D \U008E // \U008F \U0090 \U0091 \U0092 \U0093 \U0094 // \U0095 \U0096 \U0097 \U0098 \U0099 \U009A // \U009B \U009C \U009D \U009E \U009F
آیا هنوز برنامه نویسها از کلیدهای کنترلی استفاده میکنند؟
این سوال بستگی به برنامهای دارد که شما مینویسید. باید گفت هنوز بسیاری از آنها در بسیاری از برنامهها استفاده میشوند. مانند بعضی از درایورها برای ارسال اطلاعات به سمت یک قطعه یا دستگاه یا حتی از شما میخواهند برنامهای بنویسید که با دستگاههای قدیمی ارتباط برقرار کند. برنامههایی که نیاز به کار با رشتهها دارند و ...
لیست زیر مشخص میکند که کدامیک از کلیدهای کنترلی تا چه اندازه امروزه توسط برنامه نویسان استفاده میشوند.
Null | استفاده روزمرهای از آن در همهی برنامهها وجود دارد و نیاز به معرفی ندارد. |
Transmission Control | این کلیدها که 10 عدد هستند شامل SOH , ACK , DLE , ENQ , EOT , ETB , ETX , Nak , STX , SYN هستند. کاربردشان در انتقال اطلاعات بود ولی امروزه استفاده از آنها به شدت کم شده است و انتقال دادهها با سوکت TCP/IP و HTTP و FTP و دیگر پروتکولها به سرانجام رسید و گاها برای بعضی کاربردهای ویژه استفاده میشوند. |
BEL | این مورد واقعا کاربردش را از دست داده است. وظیفه قبلیاش ارسال یه هشدار یا یک زنگ اخطار به کاربر بود. مثلا برای اینکه ماشین تایپ به کاربر هشدار بدهد به آخر خط رسیده است، یک کد BELL به سمت آن ارسال میکرد. |
Format Effectors | کدهای این دسته عبارتند از BS , CR , FF , HT , HTJ , HTS , IND , LF , NEL , PLD , PLU , RI , VT , VTS هستند که احتمالا مهمترین کدهایی هستند که امروزه از آنها استفاده میشود. کاربردشان در فرمت بندی یا قالب بندی متون نوشته شده یا همان کلیدهای قابل چاپ میباشد. CR و LF که همیشه معرف حضور ما هستند و بودنشان در سیستم یک امر حیاتی است. HT که همان tab است. BS که همان Backspace است. FF و VT هم که امروزه به ندرت استفاده میشوند. |
Device Control | هنوز برای ارتباط با دستگاههای مختلف مثل کار با پورتها استفاده میشوند. کلیدهای معروف آن DC1 و DC3 هستند که به XON و XOFF هم شناخته میشوند. یکی از کاربردهای آن. |
SUB | یک نماد جایگزین که استفادهی خود را از دست داده است. موقعیکه نمادی نامعتبر بود یا خطایی رخ میداد، این نماد جایگزین آن میشد. امروزه بیشتر از علامت ؟ در متون استفاده میشود. در یک صفحه کلید استاندارد این کد توسط فشرده شدن Ctrl+Z ارسال میشود. |
CAN , EM | کاربردی امروزه ندارد. CAN برای کنترل خطا به کار میرفت و EM در نوارهای مغناطیسی. |
Information Separators | شامل 4 کلید FS ,GS , RS و US میشود که برای جداسازی دادهها از یکدیگر به کار میروند؛ ولی بهخاطر جایگزینی آنها با اسنادی مثل XML یا دیتابیسها، استفاده از آنها تا حدودی به پایان رسیده است. |
SP | همان کلید space است که نیاز به معرفی ندارد و کارش گویای همه چیز هست. |
DELL | همان کلید Delete است. |
NBSP | این کلید همان کاراکتر ;nbsp& است که در کدهای HTML استفاده میشود. |
SHY | علامت - یا Hyphen است که به شدت استفاده از آن کم شده است. |