• Next : یک عدد تصادفی را برای ما تولید میکند.
• NextByte : آرایهای از بایتها را که با اعداد تصادفی پر شدهاند تولید میکند.
• NextDouble : یک عدد تصادفی را بین 0.0 و 1.0 باز میگرداند.
بررسی متد Next
متد Next سه Overload مختلف دارد و این امکان را برای شما مهیا میکند تا 2 عدد را به عنوان بازه تولید اعداد تصادفی انتخاب کنید (حد پایین و بالای بازه).
تولید یک عدد تصادفی:
var rand = new Random().Next();
var rand = new Random().Next(1000);
کد زیر عددی تصادفی را بین محدوده تعیین شده تولید میکند:
public static int RandomNumber(int min, int max) { var rand = new Random(); return rand.Next(min, max); }
بررسی متد NextDouble
قطعه کد زیر (به کمک توابع کلاس Random) رشتهای تصادفی را با طول مشخصی برای ما تولید میکند؛ با قابلیت تعیین بزرگ و یا کوچک بودن کاراکترهای رشته تصادفی:
public static string RandomString(int size, bool lowerCase) { StringBuilder builder = new StringBuilder(); Random random = new Random(); char ch; for (int i = 0; i < size; i++) { //تولید عدد و تبدیل آن به کاراکتر ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); builder.Append(ch); } if (lowerCase) return builder.ToString().ToLower(); return builder.ToString(); }
public string RandomPassword() { StringBuilder builder = new StringBuilder(); builder.Append(RandomString(4, true)); builder.Append(RandomNumber(1000, 9999)); builder.Append(RandomString(2, false)); return builder.ToString(); }
همانطور که در ابتدای مطلب اشاره شد، خروجی این تابع آرایهای از بایتها میباشد و هر خانهی آن عددی است تصادفی که بزرگتر و یا مساوی 0 و کوچکتر از مقدار Maximum نوع داده Byte است. کد زیر نحوه استفاده از این تابع را نشان میدهد:
byte[] b = new byte[10]; Random rnd = new Random(); rnd.NextBytes(b); for (int i = 0; i < b.Length; i++) { Console.WriteLine(b[i]); }
153 115 86 5 161 190 249 228
تقریبا تمام اعمال کار با شبکه در Silverlight از مدل asynchronous programming پیروی میکنند؛ از فراخوانی یک متد وب سرویس تا دریافت اطلاعات از وب و غیره. اگر در سایر فناوریهای موجود در دات نت فریم ورک برای مثال جهت کار با یک وب سرویس هر دو متد همزمان و غیرهمزمان در اختیار برنامه نویس هستند اما اینجا خیر. اینجا فقط روشهای غیرهمزمان مرسوم هستند و بس. خیلی هم خوب. یک چارچوب کاری خوب باید روش استفادهی صحیح از کتابخانههای موجود را نیز ترویج کند و این مورد حداقل در Silverlight اتفاق افتاده است.
برای مثال فراخوانیهای زیر را در نظر بگیرید:
private int n1, n2;
private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}
private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}
private void ThirdCall(int number)
{
n2 = number;
// etc
}
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
private void FetchNumbers()
{
int n1, n2;
Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}
به عبارتی میخواهیم کل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلی برنامه را قفل نکنند) اما پی در پی انجام شوند تا مدیریت آنها سادهتر شوند (هر لحظه دقیقا بدانیم که کجا هستیم) و همچنین کدهای تولیدی نیز خواناتر باشند.
روش استانداری که توسط الگوهای برنامه نویسی برای حل این مساله پیشنهاد میشود، استفاده از الگوی coroutines است. توسط این الگو میتوان چندین متد Async را در حالت معلق قرار داده و سپس در هر زمانی که نیاز به آنها بود عملیات آنها را از سر گرفت.
دات نت فریم ورک حالت ویژهای از coroutines را توسط Iterators پشتیبانی میکند (از C# 2.0 به بعد) که در ابتدا نیاز است از دیدگاه این مساله مروری بر آنها داشته باشیم. مثال بعد یک enumerator را به همراه yield return ارائه داده است:
using System;
using System.Collections.Generic;
using System.Threading;
namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}
static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}
static void Main()
{
printAll();
}
}
}
کامپایلر سی شارپ در عمل یک state machine را برای پیاده سازی این عملیات به صورت خودکار تولید خواهد کرد:
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;
case 3:
this.<>1__state = -1;
break;
}
return false;
}
در حین استفاده از یک IEnumerator ابتدا در وضعیت شیء Current آن قرار خواهیم داشت و تا زمانیکه متد MoveNext آن فراخوانی نشود هیچ اتفاق دیگری رخ نخواهد داد. هر بار که متد MoveNext این enumerator فرخوانی گردد (برای مثال توسط یک حلقهی foreach) اجرای متد integerList ادامه خواهد یافت تا به yield return بعدی برسیم (سایر اعمال تعریف شده در حالت تعلیق قرار دارند) و همینطور الی آخر.
از همین قابلیت جهت مدیریت اعمال Async پی در پی نیز میتوان استفاده کرد. State machine فوق تا پایان اولین عملیات تعریف شده صبر میکند تا به yield return برسد. سپس با فراخوانی متد MoveNext به عملیات بعدی رهنمون خواهیم شد. به این صورت دیدگاهی پی در پی از یک سلسه عملیات غیرهمزمان حاصل میگردد.
خوب ما الان نیاز به یک کلاس داریم که بتواند enumerator ایی از این دست را به صورت خودکار مرحله به مرحله آن هم پس از پایان واقعی عملیات Async قبلی (یا مرحلهی قبلی)، اجرا کند. قبل از اختراع چرخ باید متذکر شد که دیگران اینکار را انجام دادهاند و کتابخانههای رایگان و یا سورس بازی برای این منظور موجود است.
ادامه دارد ...
EF Code First #3
بررسی تعاریف نگاشتها به کمک متادیتا در EF Code first
در قسمت قبل مروری سطحی داشتیم بر امکانات مهیای جهت تعاریف نگاشتها در EF Code first. در این قسمت، حالت استفاده از متادیتا یا همان data annotations را با جزئیات بیشتری بررسی خواهیم کرد.
برای این منظور پروژه کنسول جدیدی را آغاز نمائید. همچنین به کمک NuGet، ارجاعات لازم را به اسمبلی EF، اضافه کنید. در ادامه مدلهای زیر را به پروژه اضافه نمائید؛ یک شخص که تعدادی پروژه منتسب میتواند داشته باشد:
using System;
using System.Collections.Generic;
namespace EF_Sample02.Models
{
public class User
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public string Email { set; get; }
public string Description { set; get; }
public byte[] Photo { set; get; }
public IList<Project> Projects { set; get; }
}
}
using System;
namespace EF_Sample02.Models
{
public class Project
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Title { set; get; }
public string Description { set; get; }
public virtual User User { set; get; }
}
}
به خاصیت public virtual User User در کلاس Project اصطلاحا Navigation property هم گفته میشود.
دو کلاس زیر را نیز جهت تعریف کلاس Context که بیانگر کلاسهای شرکت کننده در تشکیل بانک اطلاعاتی هستند و همچنین کلاس آغاز کننده بانک اطلاعاتی سفارشی را به همراه تعدادی رکورد پیش فرض مشخص میکنند، به پروژه اضافه نمائید.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using EF_Sample02.Models;
namespace EF_Sample02
{
public class Sample2Context : DbContext
{
public DbSet<User> Users { set; get; }
public DbSet<Project> Projects { set; get; }
}
public class Sample2DbInitializer : DropCreateDatabaseAlways<Sample2Context>
{
protected override void Seed(Sample2Context context)
{
context.Users.Add(new User
{
AddDate = DateTime.Now,
Name = "Vahid",
LastName = "N.",
Email = "name@site.com",
Description = "-",
Projects = new List<Project>
{
new Project
{
Title = "Project 1",
AddDate = DateTime.Now.AddDays(-10),
Description = "..."
}
}
});
base.Seed(context);
}
}
}
به علاوه در فایل کانفیگ برنامه، تنظیمات رشته اتصالی را نیز اضافه نمائید:
<connectionStrings>
<add
name="Sample2Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
همانطور که ملاحظه میکنید، در اینجا name به نام کلاس مشتق شده از DbContext اشاره میکند (یکی از قراردادهای توکار EF Code first است).
یک نکته:
مرسوم است کلاسهای مدل را در یک class library جداگانه اضافه کنند به نام DomainClasses و کلاسهای مرتبط با DbContext را در پروژه class library دیگری به نام DataLayer. هیچکدام از این پروژهها نیازی به فایل کانفیگ و تنظیمات رشته اتصالی ندارند؛ زیرا اطلاعات لازم را از فایل کانفیگ پروژه اصلی که این دو پروژه class library را به خود الحاق کرده، دریافت میکنند. دو پروژه class library اضافه شده تنها باید ارجاعاتی را به اسمبلیهای EF و data annotations داشته باشند.
در ادامه به کمک متد Database.SetInitializer که در قسمت دوم به بررسی آن پرداختیم و با استفاده از کلاس سفارشی Sample2DbInitializer فوق، نسبت به ایجاد یک بانک اطلاعاتی خالی تشکیل شده بر اساس تعاریف کلاسهای دومین پروژه، اقدام خواهیم کرد:
using System;
using System.Data.Entity;
namespace EF_Sample02
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new Sample2DbInitializer());
using (var db = new Sample2Context())
{
var project1 = db.Projects.Find(1);
Console.WriteLine(project1.Title);
}
}
}
}
تا زمانیکه وهلهای از Sample2Context ساخته نشود و همچنین یک کوئری نیز به بانک اطلاعاتی ارسال نگردد، Sample2DbInitializer در عمل فراخوانی نخواهد شد.
ساختار بانک اطلاعاتی پیش فرض تشکیل شده نیز مطابق اسکریپت زیر است:
CREATE TABLE [dbo].[Users](
[Id] [int] IDENTITY(1,1) NOT NULL,
[AddDate] [datetime] NOT NULL,
[Name] [nvarchar](max) NULL,
[LastName] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL,
[Photo] [varbinary](max) NULL,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[Projects](
[Id] [int] IDENTITY(1,1) NOT NULL,
[AddDate] [datetime] NOT NULL,
[Title] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL,
[User_Id] [int] NULL,
CONSTRAINT [PK_Projects] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Projects] WITH CHECK ADD CONSTRAINT [FK_Projects_Users_User_Id] FOREIGN KEY([User_Id])
REFERENCES [dbo].[Users] ([Id])
GO
ALTER TABLE [dbo].[Projects] CHECK CONSTRAINT [FK_Projects_Users_User_Id]
GO
توضیحاتی در مورد ساختار فوق، جهت یادآوری مباحث دو قسمت قبل:
- خواصی با نام Id تبدیل به primary key و identity field شدهاند.
- نام جداول، همان نام خواص تعریف شده در کلاس Context است.
- تمام رشتهها به nvarchar از نوع max نگاشت شدهاند و null پذیر میباشند.
- خاصیت تصویر که با آرایهای از بایتها تعریف شده به varbinary از نوع max نگاشت شده است.
- بر اساس ارتباط بین کلاسها فیلد User_Id در جدول Projects اضافه شده است که توسط قیدی به نام FK_Projects_Users_User_Id، جهت تعریف کلید خارجی عمل میکند. این نام گذاری پیش فرض هم بر اساس نام خواص در دو کلاس انجام میشود.
- schema پیش فرض بکارگرفته شده، dbo است.
- null پذیری پیش فرض فیلدها بر اساس اصول زبان مورد استفاده تعیین شده است. برای مثال در سی شارپ، نوع int نال پذیر نیست یا نوع DateTime نیز به همین ترتیب یک value type است. بنابراین در اینجا این دو نوع به صورت not null تعریف شدهاند (صرفنظر از اینکه در SQL Server هر دو نوع یاد شده، null پذیر هم میتوانند باشند). بدیهی است امکان تعریف nullable types نیز وجود دارد.
مروری بر انواع متادیتای قابل استفاده در EF Code first
1) Key
همانطور که ملاحظه کردید اگر نام خاصیتی Id یا ClassName+Id باشد، به صورت خودکار به عنوان primary key جدول، مورد استفاده قرار خواهد گرفت. این یک قرارداد توکار است.
اگر یک چنین خاصیتی با نامهای ذکر شده در کلاس وجود نداشته باشد، میتوان با مزین سازی خاصیتی مفروض با ویژگی Key که در فضای نام System.ComponentModel.DataAnnotations قرار دارد، آنرا به عنوان Primary key معرفی نمود. برای مثال:
public class Project
{
[Key]
public int ThisIsMyPrimaryKey { set; get; }
و ضمنا باید دقت داشت که حین کار با ORMs فرقی نمیکند EF باشد یا سایر فریم ورکهای دیگر، داشتن یک key جهت عملکرد صحیح فریم ورک، ضروری است. بر اساس یک Key است که Entity معنا پیدا میکند.
2) Required
ویژگی Required که در فضای نام System.ComponentModel.DataAnnotations تعریف شده است، سبب خواهد شد یک خاصیت به صورت not null در بانک اطلاعاتی تعریف شود. همچنین در مباحث اعتبارسنجی برنامه، پیش از ارسال اطلاعات به سرور نیز نقش خواهد داشت. در صورت نال بودن خاصیتی که با ویژگی Required مزین شده است، یک استثنای اعتبارسنجی پیش از ذخیره سازی اطلاعات در بانک اطلاعاتی صادر میگردد. این ویژگی علاوه بر EF Code first در ASP.NET MVC نیز به نحو یکسانی تاثیرگذار است.
3) MaxLength و MinLength
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند (اما در اسمبلی EntityFramework.dll تعریف شدهاند و جزو اسمبلی پایه System.ComponentModel.DataAnnotations.dll نیستند). در ذیل نمونهای از تعریف اینها را مشاهده میکنید. همچنین باید درنظر داشت که روش دیگر تعریف متادیتا، ترکیب آنها در یک سطر نیز میباشد. یعنی الزامی ندارد در هر سطر یک متادیتا را تعریف کرد:
[MaxLength(50, ErrorMessage = "حداکثر 50 حرف"), MinLength(4, ErrorMessage = "حداقل 4 حرف")]
public string Title { set; get; }
ویژگی MaxLength بر روی طول فیلد تعریف شده در بانک اطلاعاتی تاثیر دارد. برای مثال در اینجا فیلد Title از نوع nvarchar با طول 30 تعریف خواهد شد.
ویژگی MinLength در بانک اطلاعاتی معنایی ندارد.
هر دوی این ویژگیها در پروسه اعتبار سنجی اطلاعات مدل دریافتی تاثیر دارند. برای مثال در اینجا اگر طول عنوان کمتر از 4 حرف باشد، یک استثنای اعتبارسنجی صادر خواهد شد.
ویژگی دیگری نیز به نام StringLength وجود دارد که جهت تعیین حداکثر طول رشتهها به کار میرود. این ویژگی سازگاری بیشتر با ASP.NET MVC دارد از این جهت که Client side validation آنرا نیز فعال میکند.
4) Table و Column
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند، اما در اسمبلی EntityFramework.dll تعریف شدهاند. بنابراین اگر تعاریف مدلهای شما در پروژه Class library جداگانهای قراردارند، نیاز خواهد بود تا ارجاعی را به اسمبلی EntityFramework.dll نیز داشته باشند.
اگر از نام پیش فرض جداول تشکیل شده خرسند نیستید، ویژگی Table را بر روی یک کلاس قرار داده و نام دیگری را تعریف کنید. همچنین اگر Schema کاربری رشته اتصالی به بانک اطلاعاتی شما dbo نیست، باید آنرا در اینجا صریحا ذکر کنید تا کوئریهای تشکیل شده به درستی بر روی بانک اطلاعاتی اجرا گردند:
[Table("tblProject", Schema="guest")]
public class Project
توسط ویژگی Column سه خاصیت یک فیلد بانک اطلاعاتی را میتوان تعیین کرد:
[Column("DateStarted", Order = 4, TypeName = "date")]
public DateTime AddDate { set; get; }
به صورت پیش فرض، خاصیت فوق با همین نام AddDate در بانک اطلاعاتی ظاهر میگردد. اگر برای مثال قرار است از یک بانک اطلاعاتی قدیمی استفاده شود یا قرار نیست از شیوه نامگذاری خواص در سی شارپ در یک بانک اطلاعاتی پیروی شود، توسط ویژگی Column میتوان این تعاریف را سفارشی نمود.
توسط پارامتر Order آن که از صفر شروع میشود، ترتیب قرارگیری فیلدها در حین تشکیل یک جدول مشخص میگردد.
اگر نیاز است نوع فیلد تشکیل شده را نیز سفارشی سازی نمائید، میتوان از پارامتر TypeName استفاده کرد. برای مثال در اینجا علاقمندیم از نوع date مهیا در SQL Server 2008 استفاده کنیم و نه از نوع datetime پیش فرض آن.
نکتهای در مورد Order:
Order پیش فرض تمام خواصی که قرار است به بانک اطلاعاتی نگاشت شوند، به int.MaxValue تنظیم شدهاند. به این معنا که تنظیم فوق با Order=4 سبب خواهد شد تا این فیلد، پیش از تمام فیلدهای دیگر قرار گیرد. بنابراین نیاز است Order اولین خاصیت تعریف شده را به صفر تنظیم نمود. (البته اگر واقعا نیاز به تنظیم دستی Order داشتید)
نکاتی در مورد تنظیمات ارث بری در حالت استفاده از متادیتا:
حداقل سه حالت ارث بری را در EF code first میتوان تعریف و مدیریت کرد:
الف) Table per Hierarchy - TPH
حالت پیش فرض است. نیازی به هیچگونه تنظیمی ندارد. معنای آن این است که «لطفا تمام اطلاعات کلاسهایی را که از هم ارث بری کردهاند در یک جدول بانک اطلاعاتی قرار بده». فرض کنید یک کلاس پایه شخص را دارید که کلاسهای بازیکن و مربی از آن ارث بری میکنند. زمانیکه کلاس پایه شخص توسط DbSet در کلاس مشتق شده از DbContext در معرض استفاده EF قرار میگیرد، بدون نیاز به هیچ تنظیمی، تمام این سه کلاس، تبدیل به یک جدول شخص در بانک اطلاعاتی خواهند شد. یعنی یک table به ازای سلسله مراتبی (Hierarchy) که تعریف شده.
ب) Table per Type - TPT
به این معنا است که به ازای هر نوع، باید یک جدول تشکیل شود. به عبارتی در مثال قبل، یک جدول برای شخص، یک جدول برای مربی و یک جدول برای بازیکن تشکیل خواهد شد. دو جدول مربی و بازیکن با یک کلید خارجی به جدول شخص مرتبط میشوند. تنها تنظیمی که در اینجا نیاز است، قرار دادن ویژگی Table بر روی نام کلاسهای بازیکن و مربی است. به این ترتیب حالت پیش فرض الف (TPH) اعمال نخواهد شد.
ج) Table per Concrete Type - TPC
در این حالت فقط دو جدول برای بازیکن و مربی تشکیل میشوند و جدولی برای شخص تشکیل نخواهد شد. خواص کلاس شخص، در هر دو جدول مربی و بازیکن به صورت جداگانهای تکرار خواهد شد. تنظیم این مورد نیاز به استفاده از Fluent API دارد.
توضیحات بیشتر این موارد به همراه مثال، موکول خواهد شد به مباحث استفاده از Fluent API که برای تعریف تنظیمات پیشرفته نگاشتها طراحی شده است. استفاده از متادیتا تنها قسمت کوچکی از تواناییهای Fluent API را شامل میشود.
5) ConcurrencyCheck و Timestamp
هر دوی این ویژگیها در فضای نام System.ComponentModel.DataAnnotations و اسمبلی به همین نام تعریف شدهاند.
در EF Code first دو راه برای مدیریت مسایل همزمانی وجود دارد:
[ConcurrencyCheck]
public string Name { set; get; }
[Timestamp]
public byte[] RowVersion { set; get; }
زمانیکه از ویژگی ConcurrencyCheck استفاده میشود، تغییر خاصی در سمت بانک اطلاعاتی صورت نخواهد گرفت، اما در برنامه، کوئریهای update و delete ایی که توسط EF صادر میشوند، اینبار اندکی متفاوت خواهند بود. برای مثال برنامه جاری را به نحو زیر تغییر دهید:
using System;
using System.Data.Entity;
namespace EF_Sample02
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new Sample2DbInitializer());
using (var db = new Sample2Context())
{
//update
var user = db.Users.Find(1);
user.Name = "User name 1";
db.SaveChanges();
}
}
}
}
متد Find بر اساس primary key عمل میکند. به این ترتیب، اول رکورد یافت شده و سپس نام آن تغییر کرده و در ادامه، اطلاعات ذخیره خواهند شد.
اکنون اگر توسط SQL Server Profiler کوئری update حاصل را بررسی کنیم، به نحو زیر خواهد بود:
exec sp_executesql N'update [dbo].[Users]
set [Name] = @0
where (([Id] = @1) and ([Name] = @2))
',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'User name 1',@1=1,@2=N'Vahid'
همانطور که ملاحظه میکنید، برای به روز رسانی فقط از primary key جهت یافتن رکورد استفاده نکرده، بلکه فیلد Name را نیز دخالت داده است. از این جهت که مطمئن شود در این بین، رکوردی که در حال به روز رسانی آن هستیم، توسط کاربر دیگری در شبکه تغییر نکرده باشد و اگر در این بین تغییری رخ داده باشد، یک استثناء صادر خواهد شد.
همین رفتار در مورد delete نیز وجود دارد:
//delete
var user = db.Users.Find(1);
db.Users.Remove(user);
db.SaveChanges();
exec sp_executesql N'delete [dbo].[Users]
where (([Id] = @0) and ([Name] = @1))',N'@0 int,@1 nvarchar(max) ',@0=1,@1=N'Vahid'
در اینجا نیز به علت مزین بودن خاصیت Name به ویژگی ConcurrencyCheck، فقط همان رکوردی که یافت شده باید حذف شود و نه نمونه تغییر یافته آن توسط کاربری دیگر در شبکه.
البته در این مثال شاید این پروسه تنها چند میلی ثانیه به نظر برسد. اما در برنامهای با رابط کاربری، شخصی ممکن است اطلاعات یک رکورد را در یک صفحه دریافت کرده و 5 دقیقه بعد بر روی دکمه save کلیک کند. در این بین ممکن است شخص دیگری در شبکه همین رکورد را تغییر داده باشد. بنابراین اطلاعاتی را که شخص مشاهده میکند، فاقد اعتبار شدهاند.
ConcurrencyCheck را بر روی هر فیلدی میتوان بکاربرد، اما ویژگی Timestamp کاربرد مشخص و محدودی دارد. باید به خاصیتی از نوع byte array اعمال شود (که نمونهای از آنرا در بالا در خاصیت public byte[] RowVersion مشاهده نمودید). علاوه بر آن، این ویژگی بر روی بانک اطلاعاتی نیز تاثیر دارد (نوع فیلد را در SQL Server تبدیل به timestamp میکند و نه از نوع varbinary مانند فیلد تصویر). SQL Server با این نوع فیلد به خوبی آشنا است و قابلیت مقدار دهی خودکار آنرا دارد. بنابراین نیازی نیست در حین تشکیل اشیاء در برنامه، قید شود.
پس از آن، این فیلد مقدار دهی شده به صورت خودکار توسط بانک اطلاعاتی، در تمام updateها و deleteهای EF Code first حضور خواهد داشت:
exec sp_executesql N'delete [dbo].[Users]
where ((([Id] = @0) and ([Name] = @1)) and ([RowVersion] = @2))',N'@0 int,@1 nvarchar(max) ,
@2 binary(8)',@0=1,@1=N'Vahid',@2=0x00000000000007D1
از این جهت که اطمینان حاصل شود، واقعا مشغول به روز رسانی یا حذف رکوردی هستیم که در ابتدای عملیات از بانک اطلاعاتی دریافت کردهایم. اگر در این بین RowVesrion تغییر کرده باشد، یعنی کاربر دیگری در شبکه این رکورد را تغییر داده و ما در حال حاضر مشغول به کار با رکوردی غیرمعتبر هستیم.
بنابراین استفاده از Timestamp را میتوان به عنوان یکی از best practices طراحی برنامههای چند کاربره ASP.NET درنظر داشت.
6) NotMapped و DatabaseGenerated
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند، اما در اسمبلی EntityFramework.dll تعریف شدهاند.
به کمک ویژگی DatabaseGenerated، مشخص خواهیم کرد که این فیلد قرار است توسط بانک اطلاعاتی تولید شود. برای مثال خواصی از نوع public int Id به صورت خودکار به فیلدهایی از نوع identity که توسط بانک اطلاعاتی تولید میشوند، نگاشت خواهند شد و نیازی نیست تا به صورت صریح از ویژگی DatabaseGenerated جهت مزین سازی آنها کمک گرفت. البته اگر علاقمند نیستید که primary key شما از نوع identity باشد، میتوانید از گزینه DatabaseGeneratedOption.None استفاده نمائید:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { set; get; }
DatabaseGeneratedOption در اینجا یک enum است که به نحو زیر تعریف شده است:
public enum DatabaseGeneratedOption
{
None = 0,
Identity = 1,
Computed = 2
}
تا اینجا حالتهای None و Identity آن، بحث شدند.
در SQL Server امکان تعریف فیلدهای محاسباتی و Computed با T-SQL نویسی نیز وجود دارد. این نوع فیلدها در هربار insert یا update یک رکورد، به صورت خودکار توسط بانک اطلاعاتی مقدار دهی میشوند. بنابراین اگر قرار است خاصیتی به این نوع فیلدها در SQL Server نگاشت شود، میتوان از گزینه DatabaseGeneratedOption.Computed استفاده کرد.
یا اگر برای فیلدی در بانک اطلاعاتی default value تعریف کردهاید، مثلا برای فیلد date متد getdate توکار SQL Server را به عنوان پیش فرض درنظر گرفتهاید و قرار هم نیست توسط برنامه مقدار دهی شود، باز هم میتوان آنرا از نوع DatabaseGeneratedOption.Computed تعریف کرد.
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس User درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? AddDate).
همچنین اگر یک خاصیت محاسباتی در کلاسی به صورت ReadOnly تعریف شده است (توسط کدهای مثلا سی شارپ یا وی بی):
[NotMapped]
public string FullName
{
get { return Name + " " + LastName; }
}
بدیهی است نیازی نیست تا آنرا به یک فیلد بانک اطلاعاتی نگاشت کرد. این نوع خواص را با ویژگی NotMapped میتوان مزین کرد.
همچنین باید دقت داشت در این حالت، از این نوع خواص دیگر نمیتوان در کوئریهای EF استفاده کرد. چون نهایتا این کوئریها قرار هستند به عبارات SQL ترجمه شوند و چنین فیلدی در جدول بانک اطلاعاتی وجود ندارد. البته بدیهی است امکان تهیه کوئری LINQ to Objects (کوئری از اطلاعات درون حافظه) همیشه مهیا است و اهمیتی ندارد که این خاصیت درون بانک اطلاعاتی معادلی دارد یا خیر.
7) ComplexType
ComplexType یا Component mapping مربوط به حالتی است که شما یک سری خواص را در یک کلاس تعریف میکنید، اما قصد ندارید اینها واقعا تبدیل به یک جدول مجزا (به همراه کلید خارجی) در بانک اطلاعاتی شوند. میخواهید این خواص دقیقا در همان جدول اصلی کنار مابقی خواص قرار گیرند؛ اما در طرف کدهای ما به شکل یک کلاس مجزا تعریف و مدیریت شوند.
یک مثال:
کلاس زیر را به همراه ویژگی ComplexType به برنامه مطلب جاری اضافه نمائید:
using System.ComponentModel.DataAnnotations;
namespace EF_Sample02.Models
{
[ComplexType]
public class InterestComponent
{
[MaxLength(450, ErrorMessage = "حداکثر 450 حرف")]
public string Interest1 { get; set; }
[MaxLength(450, ErrorMessage = "حداکثر 450 حرف")]
public string Interest2 { get; set; }
}
}
سپس خاصیت زیر را نیز به کلاس User اضافه کنید:
public InterestComponent Interests { set; get; }
همانطور که ملاحظه میکنید کلاس InterestComponent فاقد Id است؛ بنابراین هدف از آن تعریف یک Entity نیست و قرار هم نیست در کلاس مشتق شده از DbContext تعریف شود. از آن صرفا جهت نظم بخشیدن به یک سری خاصیت مرتبط و همخانواده استفاده شده است (مثلا آدرس یک، آدرس 2، تا آدرس 10 یک شخص، یا تلفن یک تلفن 2 یا موبایل 10 یک شخص).
اکنون اگر پروژه را اجرا نمائیم، ساختار جدول کاربر به نحو زیر تغییر خواهد کرد:
CREATE TABLE [dbo].[Users](
---...
[Interests_Interest1] [nvarchar](450) NULL,
[Interests_Interest2] [nvarchar](450) NULL,
---...
در اینجا خواص کلاس InterestComponent، داخل همان کلاس User تعریف شدهاند و نه در یک جدول مجزا. تنها در سمت کدهای ما است که مدیریت آنها منطقیتر شدهاند.
یک نکته:
یکی از الگوهایی که حین تشکیل مدلهای برنامه عموما مورد استفاده قرار میگیرد، null object pattern نام دارد. برای مثال:
namespace EF_Sample02.Models
{
public class User
{
public InterestComponent Interests { set; get; }
public User()
{
Interests = new InterestComponent();
}
}
}
در اینجا در سازنده کلاس User، به خاصیت Interests وهلهای از کلاس InterestComponent نسبت داده شده است. به این ترتیب دیگر در کدهای برنامه مدام نیازی نخواهد بود تا بررسی شود که آیا Interests نال است یا خیر. همچنین استفاده از این الگو حین کار با یک ComplexType ضروری است؛ زیرا EF امکان ثبت رکورد جاری را در صورت نال بودن خاصیت Interests (صرفنظر از اینکه خواص آن مقدار دهی شدهاند یا خیر) نخواهد داد.
8) ForeignKey
این ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارد، اما در اسمبلی EntityFramework.dll تعریف شدهاست.
اگر از قراردادهای پیش فرض نامگذاری کلیدهای خارجی در EF Code first خرسند نیستید، میتوانید توسط ویژگی ForeignKey، نامگذاری مورد نظر خود را اعمال نمائید. باید دقت داشت که ویژگی ForeignKey را باید به یک Reference property اعمال کرد. همچنین در این حالت، کلید خارجی را با یک value type نیز میتوان نمایش داد:
[ForeignKey("FK_User_Id")]
public virtual User User { set; get; }
public int FK_User_Id { set; get; }
در اینجا فیلد اضافی دوم FK_User_Id به جدول Project اضافه نخواهد شد (چون توسط ویژگی ForeignKey تعریف شده است و فقط یکبار تعریف میشود). اما در این حالت نیز وجود Reference property ضروری است.
9) InverseProperty
این ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارد، اما در اسمبلی EntityFramework.dll تعریف شدهاست.
از ویژگی InverseProperty برای تعریف روابط دو طرفه استفاده میشود.
برای مثال دو کلاس زیر را درنظر بگیرید:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}
[InverseProperty("Books")]
public Author Author {get; set;}
}
public class Author
{
public int ID {get; set;}
public string Name {get; set;}
[InverseProperty("Author")]
public virtual ICollection<Book> Books {get; set;}
}
این دو کلاس همانند کلاسهای User و Project فوق هستند. ذکر ویژگی InverseProperty برای مشخص سازی ارتباطات بین این دو غیرضروری است و قراردادهای توکار EF Code first یک چنین مواردی را به خوبی مدیریت میکنند.
اما اکنون مثال زیر را درنظر بگیرید:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}
public Author FirstAuthor {get; set;}
public Author SecondAuthor {get; set;}
}
public class Author
{
public int ID {get; set;}
public string Name {get; set;}
public virtual ICollection<Book> BooksAsFirstAuthor {get; set;}
public virtual ICollection<Book> BooksAsSecondAuthor {get; set;}
}
این مثال ویژهای است از کتابخانهای که کتابهای آن، تنها توسط دو نویسنده نوشته شدهاند. اگر برنامه را بر اساس این دو کلاس اجرا کنیم، EF Code first قادر نخواهد بود تشخیص دهد، روابط کدام به کدام هستند و در جدول Books چهار کلید خارجی را ایجاد میکند. برای مدیریت این مساله و تعین ابتدا و انتهای روابط میتوان از ویژگی InverseProperty کمک گرفت:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}
[InverseProperty("BooksAsFirstAuthor")]
public Author FirstAuthor {get; set;}
[InverseProperty("BooksAsSecondAuthor")]
public Author SecondAuthor {get; set;}
}
public class Author
{
public int ID {get; set;}
public string Name {get; set;}
[InverseProperty("FirstAuthor")]
public virtual ICollection<Book> BooksAsFirstAuthor {get; set;}
[InverseProperty("SecondAuthor")]
public virtual ICollection<Book> BooksAsSecondAuthor {get; set;}
}
اینبار اگر برنامه را اجرا کنیم، بین این دو جدول تنها دو رابطه تشکیل خواهد شد و نه چهار رابطه؛ چون EF اکنون میداند که ابتدا و انتهای روابط کجا است. همچنین ذکر ویژگی InverseProperty در یک سر رابطه کفایت میکند و نیازی به ذکر آن در طرف دوم نیست.
CREATE PROCEDURE [dbo].[spr_Admin_Replace_Ye_Ke_InAllTables] AS BEGIN BEGIN TRAN --ی--%u06CC --ی--%u064A --ک--%u06A9 --ک--%u0643 DECLARE @Ye_Farsi NCHAR(1), @Ye_Arabi NCHAR(1), @Ke_Farsi NCHAR(1), @Ke_Arabi NCHAR(1) SET @Ye_Farsi = NCHAR(0X06CC) SET @Ye_Arabi = NCHAR(0X064A) SET @Ke_Farsi = NCHAR(0X06A9) SET @Ke_Arabi = NCHAR(0X0643) --SELECT @Ye_Farsi, UNICODE(@Ye_Farsi) AS Ye_Farsi_Code, @Ye_Arabi, UNICODE(@Ye_Arabi) AS Ye_Arabi_Code,@Ke_Farsi, UNICODE(@Ke_Farsi) AS Ke_Farsi_Code, @Ke_Arabi, UNICODE(@Ke_Arabi) AS Ke_Arabi_Code --SELECT * FROM sys.types DECLARE xcur CURSOR FOR -- a cursor for string columns SELECT sys.tables.name AS TableName, sys.columns.name AS ColumnName FROM sys.tables INNER JOIN sys.columns ON sys.tables.object_id = sys.columns.object_id WHERE sys.columns.system_type_id IN (35, 99, 167, 175, 231, 239) OPEN xcur DECLARE @SqlString nvarchar(1000), @TName nvarchar(255), @CName nvarchar(255), @ret int FETCH NEXT FROM xcur INTO @TName, @CName WHILE @@FETCH_STATUS = 0 BEGIN BEGIN TRY SET @SqlString = N'UPDATE ' + @TName + ' SET ' + @CName + ' = REPLACE( REPLACE(' + @CName + ',''' + @Ye_Farsi + ''',''' + @Ye_Arabi + ''') ,''' + @Ke_Farsi + ''',''' + @Ke_Arabi + ''')'; EXEC @ret = sp_executesql @SqlString PRINT @ret END TRY BEGIN CATCH PRINT @SqlString PRINT ERROR_MESSAGE() END CATCH FETCH NEXT FROM xcur INTO @TName, @CName END CLOSE xcur DEALLOCATE xcur ROLLBACK TRAN END
ی و ک موجود در کلمات ورودی توسط کاربر را با ی و ک درست(همانها که در دیتابیس هستند)، جایگزین کنیم، و بعد عمل مورد نظر را انجام دهیم.
public class YeKeLetters { public static char Ye_Farsi = '\x06CC'; // ی %u06CC public static char Ye_Arabi = '\x064A'; // ی %u064A public static char Ke_Farsi = '\x06A9'; // ک %u06A9 public static char Ke_Arabi = '\x0643'; // ک %u0643 }
ساخت یک Microservice با NET 5.
This video shows how to create a microservice from scratch using .NET 5. You will learn:
• How to create a .NET 5 microservice from scratch
• Build and debug a .NET 5 project in VS Code
• Interact with your microservice endpoints via Open API and Postman
• Keep configuration and secrets separate from your service code
• Simplify http requests to external endpoints
• Deal with transient errors on external services
• Report the health of the service and its dependencies
• Produce logs suited for a microservice environment
کار یک کامپایلر ترجمه قطعهای از اطلاعات به چیز دیگری است. کامپایلر سیشارپ، machine code معادل دستورات دات نتی را تهیه نمیکند. Machine code، کدی است که مستقیما بر روی CPU قابل اجرا است. در دات نت این مرحله به CLR یا Common language runtime واگذار شده است تا کار اجرای نهایی کدهای تهیه شده توسط کامپایلر سیشارپ را انجام دهد.
بنابراین زمانیکه در VS.NET سعی در اجرای یک قطعه کد مینمائیم، مراحل ذیل رخ میدهند:
الف) فایلهای سیشارپ پروژه، توسط کامپایلر بارگذاری میشوند.
ب) کامپایلر کدهای این فایلها را پردازش میکند.
ج) سپس چیزی را به نام MSIL تولید میکند.
د) در ادامه فایل خروجی نهایی، با افزودن PE Headers تولید میشود. توسط PE headers مشخص میشود که فایل تولیدی نهایی آیا اجرایی است، یا یک DLL میباشد و امثال آن.
ه) و در آخر، فایل اجرایی تولیدی توسط CLR بارگذاری و اجرا میشود.
MSIL چیست؟
MSIL مخفف Microsoft intermediate language است. به آن CIL یا Common intermediate language هم گفته میشود و این دقیقا همان کدی است که توسط CLR خوانده و اجرا میشود. MSIL یک زبان طراحی شده مبتنی بر پشتهها است و بسیار شبیه به سایر زبانهای اسمبلی موجود میباشد.
یک سؤال: آیا قطعه کدهای ذیل، کدهای IL یکسانی را تولید میکنند؟
namespace FastReflectionTests { public class Test { public void Method1() { var x = 10; var y = 20; if (x == 10) { if (y == 20) { } } } public void Method2() { var x = 10; var y = 20; if (x == 10 && y == 20) { } } } }
.class public auto ansi beforefieldinit FastReflectionTests.Test extends [mscorlib]System.Object { // Methods .method public hidebysig instance void Method1 () cil managed { // Method begins at RVA 0x3bd0 // Code size 17 (0x11) .maxstack 2 .locals init ( [0] int32 x, [1] int32 y ) IL_0000: ldc.i4.s 10 IL_0002: stloc.0 IL_0003: ldc.i4.s 20 IL_0005: stloc.1 IL_0006: ldloc.0 IL_0007: ldc.i4.s 10 IL_0009: bne.un.s IL_0010 IL_000b: ldloc.1 IL_000c: ldc.i4.s 20 IL_000e: pop IL_000f: pop IL_0010: ret } // end of method Test::Method1 .method public hidebysig instance void Method2 () cil managed { // Method begins at RVA 0x3bf0 // Code size 17 (0x11) .maxstack 2 .locals init ( [0] int32 x, [1] int32 y ) IL_0000: ldc.i4.s 10 IL_0002: stloc.0 IL_0003: ldc.i4.s 20 IL_0005: stloc.1 IL_0006: ldloc.0 IL_0007: ldc.i4.s 10 IL_0009: bne.un.s IL_0010 IL_000b: ldloc.1 IL_000c: ldc.i4.s 20 IL_000e: pop IL_000f: pop IL_0010: ret } // end of method Test::Method2 .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x3c0d // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Test::.ctor } // end of class FastReflectionTests.Test
این کدها در حالت کامپایل Release تهیه شدهاند و در این حالت، کامپایلر یک سری بهینه سازیهایی را جهت بهبود سرعت و کاهش تعداد OpCodes مورد نیاز برای اجرا برنامه، اعمال میکند.
بررسی OpCodes مقدماتی
الف) OpCodes ریاضی
مانند Add، Sub، Mul و Div
ب) OpCodes کنترل جریان برنامه
مانند Jmp، Beq، Bge، Ble، Bne، Call و Ret
برای پرش به یک برچسب، بررسی تساوی و بزرگتر یا کوچک بودن، فراخوانی متدها و بازگشت دادن مقادیر
ج) OpCodes مدیریت آرگومانها
مانند Ldarg، Ldarg_0 تا Ldarg_3 ، Ldc_I4 و Ldc_I4_1 تا Ldc_I4_8
برای بارگذاری آرگومانها و همچنین بارگذاری مقادیر قرار گرفته شده بر روی پشته ارزیابی.
برای توضیحات بهتر این موارد میتوان کدهای IL فوق را بررسی کرد:
IL_0000: ldc.i4.s 10 IL_0002: stloc.0 IL_0003: ldc.i4.s 20 IL_0005: stloc.1 IL_0006: ldloc.0 IL_0007: ldc.i4.s 10 IL_0009: bne.un.s IL_0010 IL_000b: ldloc.1 IL_000c: ldc.i4.s 20 IL_000e: pop IL_000f: pop
Stack چیست و MSIL چگونه عمل میکنید؟
Stack یکی از انواع بسیار متداول ساختار دادهها است و اگر بخواهیم خارج از دنیای رایانهها مثالی را برای آن ارائه دهیم میتوان به تعدادی برگه کاغذ که بر روی یکدیگر قرار گرفتهاند، اشاره کرد. زمانیکه نیاز باشد تا برگهای از این پشته برداشته شود، باید از بالاترین سطح آن شروع کرد که به آن LIFO یا Last in First out نیز گفته میشود. چیزی که آخر از همه بر روی پشته قرار میگیرد، در ابتدا برداشته و خارج خواهد شد.
در دات نت، برای قرار دادن اطلاعات بر روی Stack از متد Push و برای بازیابی از متد Pop استفاده میشود. استفاده از متد Pop، سبب خذف آن شیء از پشته نیز میگردد.
MSIL نیز یک Stack based language است. MSIL برای مدیریت یک سری از موارد از Stack استفاده میکند؛ مانند: پارامترهای متدها، مقادیر بازگشتی و انجام محاسبات در متدها. OpCodes کار قرار دادن و بازیابی مقادیر را از Stack به عهده دارند. به تمام اینها در MSIL، پشته ارزیابی یا Evaluation stack نیز میگویند.
یک مثال: فرض کنید میخواهید جمع 5+10 را توسط MSIL شبیه سازی کنیم.
الف) مقدار 5 بر روی پشته ارزیابی قرار داده میشود.
ب) مقدار 10 بر روی پشته ارزیابی قرار داده میشود. این مورد سبب میشود که 5 یک سطح به عقب رانده شود. به این ترتیب اکنون 10 بر روی پشته است و پس از آن 5 قرار خواهد داشت.
ج) سپس OpCode ایی مساوی Add فراخوانی میشود.
د) این OpCode سبب میشود تا دو مقدار موجود در پشته Pop شوند.
ه) سپس Add، حاصل عملیات را مجددا بر روی پشته قرار میدهد.
یک استثناء
در MSIL برای مدیریت متغیرهای محلی تعریف شده در سطح یک تابع، از Stack استفاده نمیشود. این مورد شبیه سایر زبانهای اسمبلی است که در آنها میتوان مقادیر را در برچسبها یا رجیسترهای خاصی نیز ذخیره کرد.
- برای به حداکثر رسانی استفاده مجدد از کد، type safety و کارایی است.
- بیشترین استفاده مشترک از جنریکها جهت ساختن کالکشن کلاسها (collection classes) است.
- تا حد ممکن از جنریک کالکشن کلاسها (generic collection classes) جدید فضای نام System.Collections.Generic بجای کلاسهایی مانند ArrayList در فضای نام System.Collections استفاده شود.
- شما میتوانید اینترفیس جنریک ، کلاس جنریک ، متد جنریک و عامل جنریک سفارشی خودتان تهیه کنید.
- جنریک کلاسها، ممکن است در دسترسی به متدهایی با نوع دادهای خاص محدود شود.
- بوسیله reflection، میتوانید اطلاعاتی که در یک جنریک در زمان اجرا (run-time) قرار دارد بدست آورید.
- کلاسهای جنریک
- اینترفیسهای جنریک
- متدهای جنریک
- عاملهای جنریک
using System; using System.Collections.Generic; namespace GenericApplication { public class MyGenericArray<T> { // تعریف یک آرایه از نوع جنریک private T[] array; public MyGenericArray(int size) { array = new T[size + 1]; } // بدست آوردن یک آیتم جنریک از آرایه جنریک public T getItem(int index) { return array[index]; } // افزودن یک آیتم جنریک به آرایه جنریک public void setItem(int index, T value) { array[index] = value; } } }
class Tester { static void Main(string[] args) { // تعریف یک آرایه از نوع عدد صحیح MyGenericArray<int> intArray = new MyGenericArray<int>(5); // افزودن اعداد صحیح به آرایه ای از نوع عدد صحیح for (int c = 0; c < 5; c++) { intArray.setItem(c, c*5); } // بدست آوردن آیتمهای آرایه ای از نوع عدد صحیح for (int c = 0; c < 5; c++) { Console.Write(intArray.getItem(c) + " "); } Console.WriteLine(); // تعریف یک آرایه از نوع کاراکتر MyGenericArray<char> charArray = new MyGenericArray<char>(5); // افزودن کاراکترها به آرایه ای از نوع کاراکتر for (int c = 0; c < 5; c++) { charArray.setItem(c, (char)(c+97)); } // بدست آوردن آیتمهای آرایه ای از نوع کاراکتر for (int c = 0; c< 5; c++) { Console.Write(charArray.getItem(c) + " "); } Console.WriteLine(); Console.ReadKey(); } }
0 5 10 15 20 a b c d e
- Filter : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات فیلترینگ اعمال میشود. مثال :
Filter = "Name==Ali,Age>>10";
- SortBy : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات چیدمان یا سورتینگ با استفاده از نام فیلد انجام میشود. مثال :
SortBy = "Age";
- IsSortAsc: یک پراپرتی از نوع bool است که مشخص کننده چیدمان به صورت نزولی و یا صعودی است.
- Page : یک پراپرتی از نوع عددی short است. از این پراپرتی برای عملیات Pagination یا صفحه بندی استفاده میشود که مشخص کننده شماره صفحه درخواستی است.
- PageSize : یک پراپرتی از نوع عددی int است که برای مشخص کردن تعداد رکورد در هر صفحه استفاده میشود.
ApplyFiltering | از این متد برای اعمال فیلترینگ روی یک IQueryable استفاده میشود. این متد یک رشته متنی (string) ویا یک GridifyQuery دریافت کرده و پس از اعمال فیلترینگ یک IQueryable بازمیگرداند. |
ApplyOrdering | از این متد برای اعمال چیدمان یا Sorting روی یک IQueryable استفاده میشود. پس از اعمال چیدمان، یک IQueryable را بازمیگرداند. |
ApplyPaging | از این متد برای اعمال صفحه بندی (Pagination) استفاده میشود. پس از اعمال صفحه بندی یک IQueryable را بازمیگرداند. |
ApplyOrderingAndPaging | از این متد برای اعمال همزمان چیدمان و صفحه بندی استفاده میشود که یک IQueryable را باز میگرداند. |
ApplyFilterAndOrdering | از این متد برای اعمال همزمان فیلترینگ و چیدمان استفاده میشود که یک IQueryalbe را باز میگرداند. |
ApplyEverything | از این متد برای اعمال عملیات صفحه بندی، چیدمان و فیلترینگ استفاده میشود که یک IQueryable را باز میگرداند. |
GridifyQueryable | این متد مشابه ApplyEverything است که مقدار یک <QueryablePaging<T را برمیگرداند و دارای یک خصیصه اضافی TotalItems است که در عملیات صفحه بندی عموما نیاز داریم. (تعداد کل رکوردهای موجود در پایگاه داده، با توجه به فیلتر اعمال شده) |
Gridify | متدهای قبلی فقط به query موجود ما یکسری شرط را اضافه میکردند. ولی مسئولیت اجرای query به عهده ما بود. (مثلا با استفاده از ToList.). متد Gridify تمامی شرطها را باتوجه به GridifyQuery دریافتی اعمال کرده، سپس اطلاعات را بارگذاری کرده و یک <Paging<T را بازمیگرداند که کاملا قابل استفاده و بهینه شده برای دیتاگریدها میباشد. |
| |
GetFilteringExpression | این متد expression معادل فیلتر string نوشته شده شما را برمیگرداند که میتوانید از آن به طور مثال در متد Where در Linq استفاده نمایید. |
GetOrderingExpression | این متد expression انتخاب فیلد برای Orderby و OrderByDescending را باتوجه به مقدار وارد شده در فیلد SortBy بازمیگرداند. |
Filtering Operators:
همانطور که در تصویر بالا مشاهده میکنید، برای اعمال فیلترینگهای پیچیده میتوانیم از چهار اپراتور , | ( ) استفاده کنیم. به همین جهت اگر نیاز داشتید که در مقدار جستجوی خود از این علائم استفاده کنید، باید قبل از هرکدام از آنها، علامت \ را اضافه کنید.
مثال رجاکس escape character در JavaScript :
let esc = (v) => v.replace(/([(),|])/g, '\\$1')
مثال #C :
var value = "(test,test2)"; var esc = Regex.Replace(value, "([(),|])", "\\$1" ); // esc = \(test\,test2\)
در بخش بعد با امکانات Mapper توکار Gridify و شخصی سازی آن آشنا خواهیم شد.