TypeScript 1.0 منتشر شد
- Boolean
- Unsigned int
- Signed int
- Floating point numbers
- Char
- String types
- Arrays
- Tuples
- Type aliasing
Boolean
در Rust، نوع داده بولین با کلمه کلیدی bool نشان داده میشود. این نوع داده، فقط میتواند دو مقدار داشته باشد: true یا false و معمولاً در دستورات شرطی و حلقهها برای کنترل جریان یک برنامه استفاده میشود.
let is_rust_awesome: bool = true;
Unsigned int
اعداد صحیح بدون علامت در Rust با کلمه کلیدی u و سپس تعداد بیتهایی که عدد صحیح باید داشته باشد، نشان داده میشوند. به عنوان مثال، u8 یک عدد صحیح بدون علامت 8 بیتی را نشان میدهد. محدودهی یک عدد صحیح بدون علامت از 0 تا 2^n - 1 است که n تعداد بیتها است.
let x: u8 = 255;
Signed int
اعداد صحیح علامتدار در Rust با کلمهی کلیدی i و سپس تعداد بیتهایی که عدد صحیح باید داشته باشد، نشان داده میشوند. به عنوان مثال، i32، یک عدد صحیح علامتدار 32 بیتی را نشان میدهد. محدوده یک عدد صحیح علامتدار از -2^(n-1) تا 2^(n-1) - 1 است که n تعداد بیتها است.
let x: i32 = -2147483648;
Floating point numbers
اعداد ممیز شناور در Rust با کلمات کلیدی f32 یا f64 نشان داده میشوند که به ترتیب مخفف اعداد ممیز شناور 32 بیتی و 64 بیتی هستند. این نوع دادهها برای نمایش اعداد واقعی با اعشار استفاده میشوند.
let x: f32 = 3.14;
Char
نوع داده char در Rust، نشان دهنده یک کاراکتر یونیکد است؛ برخلاف رشتههایی که با گیومههای دوتایی (") نشان داده میشوند.
let c: char = 'a';
String types
در Rust دو نوع رشته وجود دارد: String و str. نوع String، یک نوع رشتهای heap-allocated و قابل رشد است؛ در حالیکه str (تلفظ "string slice") یک نوع رشتهای است که به یک برش از یک رشته در حافظه اشاره میکند:
let s1: String = String::from("hello"); let s2: &str = "world";
Arrays
آرایهها در Rust، مجموعههایی با اندازهی ثابت از عناصر از یک نوع هستند. آنها با براکت مربع ([]) و نوع عناصر داخل آرایه نشان داده میشوند.
let arr: [i32; 5] = [1, 2, 3, 4, 5];
Tuples
تاپلها در Rust، مجموعهای از عناصر از انواع مختلف هستند. آنها با پرانتز (()) و انواع عناصر داخل تاپل نشان داده میشوند.
let tup: (i32, f64, u8) = (500, 6.4, 1);
Type aliasing
تایپ aliasing در Rust، به شما امکان میدهد تا نام جدیدی را به یک نوع موجود بدهید. این میتواند برای خوانایی بیشتر کد یا ساده کردن انواع پیچیده مفید باشد.
type Age = u32; let age: Age = 30;
سادهترین تعریف MVVM، نهایت استفاده از امکانات Binding موجود در WPF و Silverlight است. اما خوب، همیشه همه چیز بر وفق مراد نیست. مثلا کنترل WebBrowser را در WPF در نظر بگیرید. فرض کنید که میخواهیم خاصیت Source آنرا در ViewModel مقدار دهی کنیم تا صفحهای را نمایش دهد. بلافاصله با خطای زیر متوقف خواهیم شد:
A 'Binding' cannot be set on the 'Source' property of type 'WebBrowser'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
بله؛ این خاصیت از نوع DependencyProperty نیست و نمیتوان چیزی را به آن Bind کرد. بنابراین این نکته مهم را توسعه دهندههای کنترلهای WPF و Silverlight همیشه باید بخاطر داشته باشند که اگر قرار است کنترلهای شما MVVM friendly باشند باید کمی بیشتر زحمت کشیده و بجای تعریف خواص ساده دات نتی، خواص مورد نظر را از نوع DependencyProperty تعریف کنید.
الان که تعریف نشده چه باید کرد؟
پاسخ متداول آن این است: مهم نیست؛ خودمان میتوانیم اینکار را انجام دهیم! یک Attached property یا به عبارتی یک Behavior را تعریف و سپس به کمک آن عملیات Binding را میسر خواهیم ساخت. برای مثال:
در این Attached property قصد داریم یک خاصیت جدید به نام BindableSource را جهت کنترل WebBrowser تعریف کنیم:
using System;
using System.Windows;
using System.Windows.Controls;
namespace WebBrowserSample.Behaviors
{
public static class WebBrowserBehaviors
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource",
typeof(object),
typeof(WebBrowserBehaviors),
new UIPropertyMetadata(null, BindableSourcePropertyChanged));
public static object GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}
public static void SetBindableSource(DependencyObject obj, object value)
{
obj.SetValue(BindableSourceProperty, value);
}
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser == null) return;
Uri uri = null;
if (e.NewValue is string)
{
var uriString = e.NewValue as string;
uri = string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString);
}
else if (e.NewValue is Uri)
{
uri = e.NewValue as Uri;
}
if (uri != null) browser.Source = uri;
}
}
}
یک مثال ساده از استفادهی آن هم به صورت زیر میتواند باشد:
ابتدا ViewModel مرتبط با فرم برنامه را تهیه خواهیم کرد. اینجا چون یک خاصیت را قرار است Bind کنیم، همینجا داخل ViewModel آنرا تعریف کردهایم. اگر تعداد آنها بیشتر بود بهتر است به یک کلاس مجزا مثلا GuiModel منتقل شوند.
using System;
using System.ComponentModel;
namespace WebBrowserSample.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
Uri _sourceUri;
public Uri SourceUri
{
get { return _sourceUri; }
set
{
_sourceUri = value;
raisePropertyChanged("SourceUri");
}
}
public MainWindowViewModel()
{
SourceUri = new Uri(@"C:\path\arrow.png");
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
در ادامه بجای استفاده از خاصیت Source که قابلیت Binding ندارد، از Behavior سفارشی تعریف شده استفاده خواهیم کرد. ابتدا باید فضای نام آن تعریف شود، سپس BindableSource مرتبط آن در دسترس خواهد بود:
<Window x:Class="WebBrowserSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:WebBrowserSample.ViewModels"
xmlns:B="clr-namespace:WebBrowserSample.Behaviors"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<VM:MainWindowViewModel x:Key="vmMainWindowViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">
<WebBrowser B:WebBrowserBehaviors.BindableSource="{Binding SourceUri}" />
</Grid>
</Window>
نمونه مشابه این مورد را در مثال «استفاده از کنترلهای Active-X در WPF» پیشتر در این سایت دیدهاید.
EF Code First #8
ادامه بحث بررسی جزئیات نحوه نگاشت کلاسها به جداول، توسط EF Code first
استفاده از Viewهای SQL Server در EF Code first
از Viewها عموما همانند یک جدول فقط خواندنی استفاده میشود. بنابراین نحوه نگاشت اطلاعات یک کلاس به یک View دقیقا همانند نحوه نگاشت اطلاعات یک کلاس به یک جدول است و تمام نکاتی که تا کنون بررسی شدند، در اینجا نیز صادق است. اما ...
الف) بر اساس تنظیمات توکار EF Code first، نام مفرد کلاسها، حین نگاشت به جداول، تبدیل به اسم جمع میشوند. بنابراین اگر View ما در سمت بانک اطلاعاتی چنین تعریفی دارد:
Create VIEW EmployeesView
AS
SELECT id,
FirstName
FROM Employees
در سمت کدهای برنامه نیاز است به این شکل تعریف شود:
using System.ComponentModel.DataAnnotations;
namespace EF_Sample04.Models
{
[Table("EmployeesView")]
public class EmployeesView
{
public int Id { set; get; }
public string FirstName { set; get; }
}
}
در اینجا به کمک ویژگی Table، نام دقیق این View را در بانک اطلاعاتی مشخص کردهایم. به این ترتیب تنظیمات توکار EF بازنویسی خواهد شد و دیگر به دنبال EmployeesViews نخواهد گشت؛ یا جدول متناظر با آنرا به صورت خودکار ایجاد نخواهد کرد.
ب) View شما نیاز است دارای یک فیلد Primary key نیز باشد.
ج) اگر از مهاجرت خودکار توسط MigrateDatabaseToLatestVersion استفاده کنیم، پیغام خطای زیر را دریافت خواهیم کرد:
There is already an object named 'EmployeesView' in the database.
علت این است که هنوز جدول dbo.__MigrationHistory از وجود آن مطلع نشده است، زیرا یک View، خارج از برنامه و در سمت بانک اطلاعاتی اضافه میشود.
برای حل این مشکل میتوان همانطور که در قسمتهای قبل نیز عنوان شد، EF را طوری تنظیم کرد تا کاری با بانک اطلاعاتی نداشته باشد:
Database.SetInitializer<Sample04Context>(null);
به این ترتیب EmployeesView در همین لحظه قابل استفاده است.
و یا به حالت امن مهاجرت دستی سوئیچ کنید:
Add-Migration Init -IgnoreChanges
Update-Database
پارامتر IgnoreChanges سبب میشود تا متدهای Up و Down کلاس مهاجرت تولید شده، خالی باشد. یعنی زمانیکه دستور Update-Database انجام میشود، نه Viewایی دراپ خواهد شد و نه جدول اضافهای ایجاد میگردد. فقط جدول dbo.__MigrationHistory به روز میشود که هدف اصلی ما نیز همین است.
همچنین در این حالت کنترل کاملی بر روی کلاسهای Up و Down وجود دارد. میتوان CreateTable اضافی را به سادگی از این کلاسها حذف کرد.
ضمن اینکه باید دقت داشت یکی از اهداف کار با ORMs، فراهم شدن امکان استفاده از بانکهای اطلاعاتی مختلف، بدون اعمال تغییری در کدهای برنامه میباشد (فقط تغییر کانکشن استرینگ، به علاوه تعیین Provider جدید، باید جهت این مهاجرت کفایت کند). بنابراین اگر از View استفاده میکنید، این برنامه به SQL Server گره خواهد خورد و دیگر از سایر بانکهای اطلاعاتی که از این مفهوم پشتیبانی نمیکنند، نمیتوان به سادگی استفاده کرد.
استفاده از فیلدهای XML اس کیوال سرور
در حال حاضر پشتیبانی توکاری توسط EF Code first از فیلدهای ویژه XML اس کیوال سرور وجود ندارد؛ اما استفاده از آنها با رعایت چند نکته ساده، به نحو زیر است:
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
namespace EF_Sample04.Models
{
public class MyXMLTable
{
public int Id { get; set; }
[Column(TypeName = "xml")]
public string XmlValue { get; set; }
[NotMapped]
public XElement XmlValueWrapper
{
get { return XElement.Parse(XmlValue); }
set { XmlValue = value.ToString(); }
}
}
}
در اینجا توسط TypeName ویژگی Column، نوع توکار xml مشخص شده است. این فیلد در طرف کدهای کلاسهای برنامه، به صورت string تعریف میشود. سپس اگر نیاز بود به این خاصیت توسط LINQ to XML دسترسی یافت، میتوان یک فیلد محاسباتی را همانند خاصیت XmlValueWrapper فوق تعریف کرد. نکته دیگری را که باید به آن دقت داشت، استفاده از ویژگی NotMapped میباشد، تا EF سعی نکند خاصیتی از نوع XElement را (یک CLR Property) به بانک اطلاعاتی نگاشت کند.
و همچنین اگر علاقمند هستید که این قابلیت به صورت توکار اضافه شود، میتوانید اینجا رای دهید!
نحوه تعریف Composite keys در EF Code first
کلاس نوع فعالیت زیر را درنظر بگیرید:
namespace EF_Sample04.Models
{
public class ActivityType
{
public int UserId { set; get; }
public int ActivityID { get; set; }
}
}
در جدول متناظر با این کلاس، نباید دو رکورد تکراری حاوی شماره کاربری و شماره فعالیت یکسانی باهم وجود داشته باشند. بنابراین بهتر است بر روی این دو فیلد، یک کلید ترکیبی تعریف کرد:
using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;
namespace EF_Sample04.Mappings
{
public class ActivityTypeConfig : EntityTypeConfiguration<ActivityType>
{
public ActivityTypeConfig()
{
this.HasKey(x => new { x.ActivityID, x.UserId });
}
}
}
در اینجا نحوه معرفی بیش از یک کلید را در متد HasKey ملاحظه میکنید.
یک نکته:
اینبار اگر سعی کنیم مثلا از متد db.ActivityTypes.Find با یک پارامتر استفاده کنیم، پیغام خطای «The number of primary key values passed must match number of primary key values defined on the entity» را دریافت خواهیم کرد. برای رفع آن باید هر دو کلید، در این متد قید شوند:
var activity1 = db.ActivityTypes.Find(4, 1);
ترتیب آنها هم بر اساس ترتیبی که در کلاس ActivityTypeConfig، ذکر شده است، مشخص میگردد. بنابراین در این مثال، اولین پارامتر متد Find، به ActivityID اشاره میکند و دومین پارامتر به UserId.
بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)
سناریوهای کاربردی بسیاری را جهت جداول خود ارجاع دهنده میتوان متصور شد و عموما تمام آنها برای مدل سازی اطلاعات چند سطحی کاربرد دارند. برای مثال یک کارمند را درنظر بگیرید. مدیر این شخص هم یک کارمند است. مسئول این مدیر هم یک کارمند است و الی آخر. نمونه دیگر آن، طراحی منوهای چند سطحی هستند و یا یک مشتری را درنظر بگیرید. مشتری دیگری که توسط این مشتری معرفی شده است نیز یک مشتری است. این مشتری نیز میتواند یک مشتری دیگر را به شما معرفی کند و این سلسله مراتب به همین ترتیب میتواند ادامه پیدا کند.
در طراحی بانکهای اطلاعاتی، برای ایجاد یک چنین جداولی، یک کلید خارجی را که به کلید اصلی همان جدول اشاره میکند، ایجاد خواهند کرد؛ اما در EF Code first چطور؟
using System.Collections.Generic;
namespace EF_Sample04.Models
{
public class Employee
{
public int Id { set; get; }
public string FirstName { get; set; }
public string LastName { get; set; }
//public int? ManagerID { get; set; }
public virtual Employee Manager { get; set; }
}
}
در این کلاس، خاصیت Manager دارای ارجاعی است به همان کلاس؛ یعنی یک کارمند میتواند مسئول کارمند دیگری باشد. برای تعریف نگاشت این کلاس به بانک اطلاعاتی میتوان از روش زیر استفاده کرد:
using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;
namespace EF_Sample04.Mappings
{
public class EmployeeConfig : EntityTypeConfiguration<Employee>
{
public EmployeeConfig()
{
this.HasOptional(x => x.Manager)
.WithMany()
//.HasForeignKey(x => x.ManagerID)
.WillCascadeOnDelete(false);
}
}
}
با توجه به اینکه یک کارمند میتواند مسئولی نداشته باشد (خودش مدیر ارشد است)، به کمک متد HasOptional مشخص کردهایم که فیلد Manager_Id را که میخواهی به این کلاس اضافه کنی باید نال پذیر باشد. توسط متد WithMany طرف دیگر رابطه مشخص شده است.
اگر نیاز بود فیلد Manager_Id اضافه شده نام دیگری داشته باشد، یک خاصیت nullable مانند ManagerID را که در کلاس Employee مشاهده میکنید، اضافه نمائید. سپس در طرف تعاریف نگاشتها به کمک متد HasForeignKey، باید صریحا عنوان کرد که این خاصیت، همان کلید خارجی است. از این نکته در سایر حالات تعاریف نگاشتها نیز میتوان استفاده کرد، خصوصا اگر از یک بانک اطلاعاتی موجود قرار است استفاده شود و از نامهای دیگری بجز نامهای پیش فرض EF استفاده کرده است.
مثالهای این سری رو از این آدرس هم میتونید دریافت کنید: (^)
معرفی کتابخانه InfiniteEnumFlags
Enumهای دات نت با [Flags] attribute, ویژگی قدرتمندی است که امکان ذخیره و ترکیب چندین گزینه یا Feature را تنها به صورت یک مقدار ثابت فراهم میکند که از طریق Bitwise operatorها میتوانیم به ترکیب چندین Enum بپردازیم و یا از طریق این مقدار ثابت به تک تک اعضای تشکیل دهنده آن برسیم. ولی مشکل بزرگی این این ویژگی دارد محدودیت آن است که برای Enum هایی از نوع int تنها 32 آیتم و از نوع long تنها 64 مورد را پشتیبانی میکند. این مشکل سبب میشود در اکثر سناریوها به سراغ این ویژگی نرویم,
به طور مثال برای تعریف دسترسیهای یک نرم افزار به صورت Strongly Type به احتمال زیاد با بزرگتر شدن برنامه در آینده به مشکل برخورد میکنیم.
InfiniteEnumFlags کتابخانه کوچکی است که تمام امکانات [Flags] را در اختیار ما میگذارد و میتواند حدود 2.1 میلیارد آیتم را پشتیبانی کند.
public class Permission : InfiniteEnum<Permission> { public static readonly Flag<Permission> None = new(-1); public static readonly Flag<Permission> ViewRoles = new(0); public static readonly Flag<Permission> ManageRoles = new(1); public static readonly Flag<Permission> ViewUsers = new(2); public static readonly Flag<Permission> ManageUsers = new(3); public static readonly Flag<Permission> ConfigureAccessControl = new(4); public static readonly Flag<Permission> Counter = new(5); public static readonly Flag<Permission> Forecast = new(6); public static readonly Flag<Permission> ViewAccessControl = new(7); // We can support up to 2,147,483,647 items }
مثال استفاده از آن برای تعریف سطح دسترسیها در برنامههای Asp.net core در فولدر Example این مخزن میتوانید پیدا کنید.
git clone --recurse-submodules https://github.com/alirezanet/InfiniteEnumFlags.git
"این آزمایشات رو اگر در هر سیستم دیگر با هر Config اجرا کنید نتیجه کلی تغییر نخواهد کرد و فقط از نظر زمان اجرا تفاوت خواهیم داشت نه در نتیجه کلی."
static void Main(string[] args) { //Action<int> func = Console.WriteLine; Action<int> func = number => number++; do { try { Console.Write("Iteration: "); var iterations = Convert.ToInt32(Console.ReadLine()); Console.Write("Loop Type (for:0, foreach:1, List.ForEach:2, Array.ForEach:3): "); var loopType = Console.ReadLine(); switch (loopType) { case "0": Console.WriteLine("FOR loop test for {0} iterations", iterations.ToString("0,0")); TestFor(iterations, func); break; case "1": Console.WriteLine("FOREACH loop test for {0} iterations", iterations.ToString("0,0")); TestForEach(iterations, func); break; case "2": Console.WriteLine("LIST.FOREACH test for {0} iterations", iterations.ToString("0,0")); TestListForEach(iterations, func); break; case "3": Console.WriteLine("ARRAY.FOREACH test for {0} iterations", iterations.ToString("0,0")); TestArrayForEach(iterations, func); break; } } catch (Exception ex) { Console.WriteLine(ex); } Console.Write("Continue?(Y/N)"); Console.WriteLine(""); } while (Console.ReadKey(true).Key != ConsoleKey.N); Console.WriteLine("Press any key to exit"); Console.ReadKey(); } static void TestFor(int iterations, Action<int> func) { StartupTest(func); var watch = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { func(i); } watch.Stop(); ShowResults("for loop test: ", watch); } static void TestForEach(int iterations, Action<int> func) { StartupTest(func); var list = Enumerable.Range(0, iterations); var watch = Stopwatch.StartNew(); foreach (var item in list) { func(item); } watch.Stop(); ShowResults("foreach loop test: ", watch); } static void TestListForEach(int iterations, Action<int> func) { StartupTest(func); var list = Enumerable.Range(0, iterations).ToList(); var watch = Stopwatch.StartNew(); list.ForEach(func); watch.Stop(); ShowResults("list.ForEach test: ", watch); } static void TestArrayForEach(int iterations, Action<int> func) { StartupTest(func); var array = Enumerable.Range(0, iterations).ToArray(); var watch = Stopwatch.StartNew(); Array.ForEach(array, func); watch.Stop(); ShowResults("Array.ForEach test: ", watch); } static void StartupTest(Action<int> func) { // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // warm up func(0); } static void ShowResults(string description, Stopwatch watch) { Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds); }