3. مطالعه موردی با SQL Server Express 2012
3.1. Introduction
نمونههای موجود در اینترنت برای Entity Framework عمدتاً نمونههایی هستند که از سرور SQL استفاده میکنند. این کاملاً طبیعی است. احتمالاً SGBD پرکاربردترین نسخه در دنیای سازمانی است. ما این روند را دنبال خواهیم کرد. سپس مثالها به تمامی پایگاههای دادهای که در بخش 1.2 ذکر شدهاند، گسترش داده خواهند شد.
3.2. نصب ابزارها
ما نصب ابزارها را شرح نخواهیم داد. این کار به تعداد زیادی اسکرینشات نیاز دارد که به سرعت از تاریخ میافتند. این کاری است (که البته همیشه آسان نیست) که آن را به خواننده واگذار میکنیم.
ما باید ابزارهای زیر را نصب کنیم:
- SGBD SQL Server Express 2012: [http://www.microsoft.com/fr-fr/download/details.aspx?id=29062]. نسخه «With Tools» را دانلود کنید که شامل یک ابزار مدیریت در کنار SGBD است:
پس از نصب SGBD، آن را اجرا کنید:
![]() |
![]() |
- [1]: از منوی استارت، «مدیر پیکربندی سرور SQL» را اجرا کنید؛
- [2]: در این مدیر، سرور را راهاندازی کنید؛
- [3]: اکنون راهاندازی شده است.
اکنون ابزار مدیریت سرور SQL را راهاندازی خواهیم کرد:
![]() |
- [1]: از منوی استارت، «SQL Server Management Studio» را اجرا کنید؛
- [2]: ابزار مدیریت.
اکنون به سرور متصل میشویم:
![]() |
- در [1]، Object Explorer را باز کنید؛
- در [2]، جزئیات اتصال را وارد میکنیم:
- [3]: سرور (محلی) (به کروشههای لازم توجه کنید) به سروری اشاره دارد که روی ماشین نصب شده است،
- [4]: احراز هویت ویندوز را انتخاب کنید. برای موفقیت این اتصال، باید مدیر سیستم خود باشید،
![]() |
- [6]: شما وارد شدهاید؛
- [7]: شما میخواهید برخی از ویژگیهای سرور را تغییر دهید؛
![]() |
- [8]: ما درخواست میکنیم که دو روش احراز هویت وجود داشته باشد:
- احراز هویت ویندوز، همانطور که همین حالا استفاده شد. یک کاربر ویندوز با مجوزهای صحیح میتواند سپس وارد شود،
- SQL احراز هویت سرور. کاربر باید یکی از کاربران ثبتشده در SGBD باشد؛
پس از انجام این کار، میتوان ویژگیهای سرور را تأیید کرد؛
- [9]: ویرایش ویژگیهای کاربر «sa» (مدیر سیستم)؛
![]() |
- در [10]، برای کاربر رمز عبور تعیین کنید. در ادامه این سند، این رمز عبور sqlserver2012 است؛
![]() |
- در [10]، به آنها اجازه ورود بدهید؛
- در [11]، اتصال فعال میشود. اکنون میتوان جادوگر را تأیید کرد؛
- در [12]، از سرور خارج میشویم.
اکنون، مجدداً با نام کاربری sa/sqlserver2012 وارد میشویم:
![]() |
- در [1]، دوباره متصل میشویم؛
- در [2]، احراز هویت در سرور SQL انجام میشود؛
- در [3]، کاربر sa است؛
- در [4]، رمز عبور آنها sqlserver2012 است؛
- در 5، ما وارد میشویم؛
![]() |
- در [6]، شما وارد شدهاید.
اکنون یک پایگاه دادهٔ نمایشی ایجاد خواهیم کرد:
![]() |
- در [1]، یک BD جدید ایجاد کنید؛
- در [2]، نام آن «demo» خواهد بود؛
- در [3] تأیید میکنیم؛
![]() |
- در [4]، پایگاه داده ایجاد میشود؛
- در 5، یک جدول جدید در پایگاه داده demo ایجاد میشود؛
![]() |
![]() |
![]() |
![]() |
- در [6]، ما جدولی با دو ستون تعریف میکنیم: ID و NOM;
- در [7]، ستون [ID] بهعنوان کلید اصلی تعیین شده است؛
- در [8]، کلید اصلی با یک کلید نشان داده میشود؛
- در [9]، جدول ذخیره میشود؛
- در [10]، برای آن نامی انتخاب میکنیم؛
- در [11]، برای اینکه جدول در پایگاه داده [demo] ظاهر شود، پایگاه داده باید تازه شود؛
- در [12]، جدول [PERSONNES] با موفقیت ایجاد شده است.
این تمام چیزی است که در حال حاضر برای استفاده از ابزار مدیریت سرور SQL باید بدانیم.
3.3. سرور تعبیهشده (localdb)\v11.0
VS Express 2012 با یک سرور تعبیهشده SQL عرضه میشود. ما در اینجا فرض میکنیم که VS Express 2012 نصب شده است [http://www.microsoft.com/visualstudio/fra/downloads]. ما VS 2012 [1] را راهاندازی میکنیم:
![]() |
ما ابزار مدیریت سرور SQL 2012 را راهاندازی میکنیم و به [2] وارد میشویم و به [3] لاگین میکنیم.
![]() |
- در [4]، به سرور (localdb)\v11.0 وارد شوید؛
- در 5، با استفاده از احراز هویت ویندوز؛
- در [6]، یک اتصال موفق، پایگاههای دادهٔ سرور را نمایش میدهد. همانند قبل، میتوانیم یک پایگاه دادهٔ جدید ایجاد کنیم.
ما در VS 2012 از این سرور تعبیهشده استفاده نخواهیم کرد.
3.4. ایجاد پایگاه داده از طریق انتیتها
Entity Framework 5 Code First به شما امکان میدهد از طریق انتیتها یک پایگاه داده ایجاد کنید. این چیزی است که اکنون به آن خواهیم پرداخت. با استفاده از VS Express 2012، یک پروژه کنسول اولیه در C# ایجاد میکنیم:
![]() |
![]() |
- در [1]، تعریف پروژه؛
- در [2]، پروژهای که ایجاد کردهایم.
تمام پروژههای ما به Entity Framework 5 ، DLL نیاز خواهند داشت. آن را اضافه خواهیم کرد:
![]() |
- در [1]، ابزار NuGet به شما امکان میدهد وابستگیها را دانلود کنید؛
![]() |
- به [2]؛ این وابستگی Entity Framework را دانلود میکند؛
- در [3]، مرجع به پروژه اضافه شده است.
میتوانید با مشاهدهٔ ویژگیهای مرجع افزوده شده، اطلاعات بیشتری کسب کنید:
![]() |
- در [1]، نسخه DLL. نسخه ۵ مورد نیاز است؛
- در [2]، مکان آن در سیستم فایل: <solution>\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll که در آن <solution> پوشه مربوط به راهحل VS است. تمام بستههای اضافه شده توسط NuGet در پوشه <solution>/packages قرار میگیرند؛
- در [3]، فایلی به نام [packages.config] ایجاد شد. محتویات آن به شرح زیر است:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="5.0.0" targetFramework="net45" />
</packages>
این فهرست بستههای واردشده توسط NuGet را نشان میدهد.
بیایید به پروژه VS بازگردیم و یک پوشه به نام [Models] در داخل پروژه ایجاد کنیم:
![]() |
- در [1]، افزودن یک پوشه به پروژه؛
- در [2]، این [Models] نامیده خواهد شد.
ما این رویه را برای قرار دادن تعاریف اجزای خود در پوشه [Models] ادامه خواهیم داد.
برای ساختن اجزای ما، از تعریف پایگاه داده MySQL که در پروژه NHibernate استفاده میشود، بهره خواهیم برد. بیایید نقش اجزای EF را به یاد بیاوریم:
![]() |
این انتیتها باید نمایانگر جداول در پایگاه داده باشند. لایه دسترسی به دادهها به جای کار مستقیم با جداول از این انتیتها استفاده میکند. بیایید با جدول [MEDECINS] شروع کنیم:
3.4.1. اِنتیتی [Medecin]
این شامل اطلاعاتی دربارهٔ پزشکان تحت مدیریت برنامهٔ [RdvMedecins] است.
![]() | ![]() |
- ID: شماره شناسایی پزشک – کلید اصلی جدول
- VERSION: شمارهای که نسخهٔ سطر در جدول را شناسایی میکند. این شماره هر بار که تغییری در سطر اعمال میشود، یک واحد افزایش مییابد.
- NOM: نام خانوادگی پزشک
- PRENOM: نام کوچک آنها
- TITRE: عنوان آنها (خانم، بانو، آقای)
میتوانیم با کلاس زیر شروع کنیم [Medecin]:
using System;
[Table("MEDECINS", Schema = "dbo")]
namespace RdvMedecins.Entites
{
public class Medecin
{
//دادهها
public int Id { get; set; }
public string Titre { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
}
- خط ۳: کلاس [Medecin] با جدول [MEDECINS] در پایگاه داده مرتبط است. این جدول در یک اسکیما به نام "dbo" قرار خواهد گرفت.
ما این کلاس را در فایلی به نام [Entites.cs] [1] قرار میدهیم. این همان جایی است که تمام اجزای خود را در آن قرار خواهیم داد.
![]() |
هنوز در پوشه [Models]، فایل زیر را ایجاد میکنیم: [Context.cs]:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// زمینه
public class RdvMedecinsContext : DbContext
{
// پزشکان
public DbSet<Medecin> Medecins { get; set; }
}
// ابتدایسازی پایگاه داده
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
}
}
- خط ۸: کلاس [RdvMedecinsContext] نمایانگر زمینه پایداری، c.-à-d خواهد بود. تمام انتیتهایی که توسط ORM مدیریت میشوند. این کلاس باید از کلاس [System.Data.Entity.DbContext] مشتق شود؛
- خط ۱۱: فیلد [Medecins] نمایانگر موجودیتهای نوع [Medecin] در زمینه پایداری خواهد بود. این از نوع DbSet<Medecin> است. به طور کلی به ازای هر جدول در پایگاه داده، یک موجودیت [DbSet] وجود خواهد داشت؛
- خط ۱۵: یک کلاس [RdvMedecinsInitializer] برای راهاندازی پایگاه داده ایجاد شده تعریف شده است. در اینجا، این کلاس از کلاس [DropCreateDataBaseAlways] مشتق شده است که همانطور که از نامش پیداست، اگر پایگاه داده از قبل وجود داشته باشد آن را حذف کرده و سپس دوباره ایجاد میکند. این امر در فاز توسعه BD مفید است. پارامتر کلاس [DropCreateDataBaseAlways] نوع زمینه پایداری (persistence context) مرتبط با پایگاه داده است. کلاسهای والد دیگر به غیر از [DropCreateDataBaseAlways] نیز میتوانند برای کلاس инициализация (initialisation class) استفاده شوند:
- [DropCreateDatabaseIfModelChanges]: پایگاه داده را در صورتی که اشیاء تغییر کرده باشند، دوباره ایجاد میکند،
- [CreateDatabaseIfNotExists]: اگر پایگاه داده وجود نداشته باشد، آن را ایجاد میکند؛
اکنون باید یک برنامهٔ اصلی ایجاد کنیم. این برنامه به شرح زیر خواهد بود: [CreateDB_01.cs]:
using System;
using System.Data.Entity;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class CreateDB_01
{
static void Main(string[] args)
{
// ایجاد پایگاه داده
Database.SetInitializer(new RdvMedecinsInitializer());
using (var context = new RdvMedecinsContext())
{
context.Database.Initialize(false);
}
}
}
}
- خط ۱۲: [System.Data.Entity.DataBase] کلاسی است که متدهای ایستا برای مدیریت پایگاه داده مرتبط با یک زمینه پایداری فراهم میکند. متد ایستا [SetInitializer] امکان مشخص کردن کلاس inicialization پایگاه داده را فراهم میکند. این کار باعث اجرای inicialization نمیشود؛
- خط ۱۳: برای کار با یک زمینه پایداری، باید آن را نمونه سازی کنید. این کاری است که در اینجا انجام میشود. از یک عبارت
usingاستفاده میشود تا زمینه به طور خودکار هنگام پایان عبارت بسته شود. بنابراین، در خط ۱۷، زمینه بسته میشود؛ - خط ۱۵: ما بهطور صریح ایجاد پایگاهدادهٔ مرتبط با زمینهٔ پایداری [RdvMedecinsContext] را راهاندازی میکنیم. پارامتر false نشان میدهد که این عملیات نباید در صورتی انجام شود که قبلاً برای این زمینه اجرا شده باشد. در اینجا، میتوانستیم به همان آسانی از true نیز استفاده کنیم.
هنگام کار با یک پایگاه داده، پارامترهای اتصال معمولاً در فایل [App.config] ذخیره میشوند. توجه داشته باشید که در حال حاضر، آنها در آنجا وجود ندارند:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- برای اطلاعات بیشتر درباره پیکربندی Entity Framework، به http://go.microsoft.com/fwlink/?LinkID=237468 مراجعه کنید. -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
</configuration>
جزئیات فوق زمانی که وابستگی Entity Framework به مراجع پروژه اضافه شد، در [App.config] نوشته شد.
پس از راهاندازی سرور SQL Express (این مهم است)، بیایید پروژه را اجرا کنیم (Ctrl-F5):
![]() | ![]() |
اجرا باید بدون هیچ خطایی کامل شود. اکنون ابزار مدیریت سرور SQL را باز کرده و نما را تازه کنید:
![]() |
میتوانیم ببینیم که یک پایگاه داده با نام کامل کلاس [RdvMedecinsContext] ایجاد شده و شامل یک جدول [dbo.MEDECINS] است. (این نامی است که ما برای آن انتخاب کردهایم) با ستونهایی که با نامهای فیلدهای موجوده [Medecin] مطابقت دارند. اگر کد با موفقیت اجرا شده باشد اما پایگاه دادهٔ مذکور نمایش داده نشود، باید سرور تعبیهشده (localdb)\v11.0 را بررسی کنید (به صفحهٔ ۱۹ مراجعه کنید). در VS 2012 Pro، این سرور در صورتی استفاده میشود که سرور SQL هنگام اجرای کد فعال نباشد. در VS 2012 Express، اینطور نیست.
بیایید ساختار جدول [MEDECINS] را بررسی کنیم:
- این شامل نامهای فیلد از انتیت [Medecin] است؛
- ستون [Id] کلید اصلی است. این یک کنوانسیون در EF است: اگر یک موجودیت E دارای فیلد Id یا Eid (MedecinId) باشد، آنگاه این ستون کلید اصلی در جدول مرتبط است؛
- انواع ستونها در جدول همان انواع فیلدهای موجوده هستند؛
- برای ستونهای «عنوان»، «نام خانوادگی» و «نام»، از نوع [nvarchar(max)] استفاده شده است. میتوانیم دقیقتر باشیم: ۵ کاراکتر برای عنوان، ۳۰ کاراکتر برای نام خانوادگی و نام؛
- ستونهای «عنوان»، «نام خانوادگی» و «نام» ممکن است مقدار NULL را داشته باشند. ما قصد داریم این را تغییر دهیم.
بیایید ویژگیهای کلید اصلی [Id] را بررسی کنیم:
![]() |
در [1]، میبینیم که کلید اصلی از نوع [Identité] است، که به این معنی است که مقدار آن به طور خودکار توسط سرور SQL تولید میشود. ما این استراتژی را برای تمام ورودیهای SGBD به کار خواهیم برد.
ما با استفاده از حاشیهنویسیها کمتر به قراردادهای EF تکیه خواهیم کرد. کد موجودیت در [Entites.cs] به صورت زیر درمیآید:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// دادهها
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Required]
[Column("VERSION")]
public int Version { get; set; }
}
}
- ردههای ۲ و ۳: حاشیهنویسیها در فضاهای نام [System.ComponentModel.DataAnnotations] (Key, Required, MaxLength) و [System.ComponentModel.DataAnnotations.Schema] (Column) یافت میشوند. توضیحات بیشتری را میتوان در فضاهای نام URL و [http://msdn.microsoft.com/en-us/data/gg193958.aspx] یافت؛
- خط ۱۱: [Key] کلید اصلی را مشخص میکند؛
- خط ۱۲: [Column] نام ستون متناظر با فیلد را مشخص میکند؛
- خط ۱۴: [Required] نشان میدهد که این فیلد اجباری است (SQL، NOT، NULL);
- خط ۱۵: [MaxLength] حداکثر طول رشتهٔ کاراکتری را تعیین میکند، [MinLength] حداقل طول آن را؛
بیایید پروژه را با این تعریف جدید از موجودیت [Medecin] اجرا کنیم. پایگاه داده حاصل به شرح زیر است:
![]() |
- ستونها دارای نامهایی هستند که ما به آنها اختصاص دادهایم؛
- توضیحات [Required] به SQL، NOT و NULL نگاشت شده است؛
- توضیح [MaxLength(N)] به نوع SQL nvarchar(N) تبدیل شد.
در برنامه NHibernate، ستون [VERSION] برای جلوگیری از دسترسی همزمان به همان سطر در یک جدول گنجانده شد. اصل کار به شرح زیر است:
- یک فرآیند به نام P1 در زمان T1 سطر L را از جدول [MEDECINS] میخواند. این سطر دارای نسخه V1 است؛
- فرآیندی به نام P2 در زمان T2، سطر یکسانی L را از جدول [MEDECINS] میخواند. این سطر نسخه V1 را دارد زیرا فرآیند P1 هنوز تغییر خود را commit نکرده است؛
- فرآیند P1 تغییر خود را در سطر L ثبت میکند. نسخه سطر L سپس به V2 = V1 + 1 تغییر میکند؛
- فرآیند P2 تغییر خود را در خط L ثبت میکند. سپس ORM یک استثنا پرتاب میکند زیرا فرآیند P2 نسخهای از سطر L با شماره نسخه V1 دارد که با نسخه V2 موجود در پایگاه داده متفاوت است.
این روش به عنوان کنترل همزمانی خوشبینانه شناخته میشود. در EF 5، یک فیلد که این نقش را ایفا میکند باید یکی از دو ویژگی زیر را داشته باشد: [Timestamp] یا [ConcurrencyCheck]. سرور SQL از نوع [timestamp] است. برای ستونی از این نوع، مقدار به طور خودکار توسط سرور SQL هر زمان که یک سطر درج یا بهروزرسانی میشود، تولید میشود. چنین ستونی سپس میتواند برای مدیریت دسترسی همزمان استفاده شود. برای بازگشت به مثال قبلی، فرآیند P2 یک timestamp را پیدا خواهد کرد که با آنچه خوانده بود متفاوت است، زیرا اصلاح انجامشده توسط فرآیند P1 در این بین آن را تغییر داده است.
اِنتیتهی ما [Medecin] به شرح زیر تکامل مییابد:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// دادهها
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
}
- خطوط ۲۶–۲۸: ستون جدید با ویژگی [Timestamp] در خط ۲۷. نوع فیلد باید byte[] باشد (خط ۲۸). نام فیلد میتواند هر چیزی باشد. ما ویژگی [Required] را به آن اختصاص نمیدهیم، زیرا این مقدار توسط برنامه ارائه نمیشود بلکه توسط خود SGBD فراهم میگردد.
اگر پروژه با این موجودیت جدید اجرا شود، پایگاه داده به شرح زیر تغییر میکند:
![]() |
یک نکتهٔ نهایی باقی مانده است. زمینهٔ پایداری میداند که یک ساباِنتیتی باید در پایگاه داده درج شود، زیرا در آن مرحله کلید اصلی آن null است. درج در پایگاه داده است که مقدار را به کلید اصلی اختصاص میدهد. در اینجا، نوع int که به کلید اصلی [Id] اختصاص یافته است، مناسب نیست زیرا این نوع مقدار null را قبول نمیکند. بنابراین نوع **int?** به آن اختصاص داده میشود که مقادیر int و همچنین نشانگر null را میپذیرد. بنابراین، موجودیت [Medecin] مورد استفاده به شکل زیر خواهد بود:
public class Medecin
{
// data
[Key]
[Column("ID")]
public int? Id { get; set; }
...
اکنون باید بررسی کنیم که چگونه میتوان مفهوم کلید خارجی بین جداول را در یک انتیتي نمایش داد.
3.4.2. اِنتیتی [Creneau]
جدول [CRENEAUX] بازههای زمانی را که در آنها ثبت دادههای RV امکانپذیر است، فهرست میکند:
![]() |
![]() |
- ID: شمارهای که شکاف زمانی را شناسایی میکند – کلید اصلی جدول
- VERSION: شمارهای که نسخه ردیف در جدول را شناسایی میکند. این شماره هر بار که تغییری در ردیف ایجاد میشود، ۱ واحد افزایش مییابد.
- ID_MEDECIN: شماره شناسه پزشک متعلق به این اسلات – کلید خارجی روی ستون MEDECINS (ID).
- HDEBUT: زمان شروع اسلات
- MDEBUT: دقیقه شروع اسلات
- HFIN: زمان پایان اسلات
- MFIN: دقایق پایان اسلات
ردهی دوم جدول [CRENEAUX] (به [1] بالا مراجعه کنید) نشان میدهد که برای مثال، اسلات شمارهٔ ۲ از ساعت ۸:۲۰ شروع و در ساعت ۸:۴۰ پایان مییابد و به پزشک شمارهٔ ۱ اختصاص داده شده است. (خانم ماری PELISSIER).
بر اساس آنچه میدانیم، میتوانیم موجودیت [Creneau] را در [Entites.cs] به شرح زیر تعریف کنیم:
[Table("CRENEAUX", Schema = "dbo")]
public class Creneau
{
// data
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("HDEBUT")]
public int Hdebut { get; set; }
[Required]
[Column("MDEBUT")]
public int Mdebut { get; set; }
[Required]
[Column("HFIN")]
public int Hfin { get; set; }
[Required]
[Column("MFIN")]
public int Mfin { get; set; }
[Required]
public virtual Medecin Medecin { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
تنها تغییر در خطوط ۲۰–۲۱ است. اینکه جدول [CRENEAUX] یک کلید خارجی روی جدول [MEDECINS] دارد، در موجوده [Creneau] با وجود یک ارجاع به موجوده [Medecin] منعکس شده است، خط 21. نام فیلد اهمیتی ندارد؛ تنها نوع آن اهمیت دارد. این خاصیت باید با استفاده از کلمه کلیدی virtual به صورت مجازی تعریف شود. دلیل این امر آن است که EF ملزم است تمام آنچه به اصطلاح ویژگیهای ناوبری نامیده میشوند را مجدداً تعریف کند، یعنی آنهایی که با یک کلید خارجی مطابقت دارند و امکان ناوبری بین جداول را فراهم میکنند.
برای آزمایش این انتیت جدید، باید چند تغییر در [Context.cs] ایجاد کنیم:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// زمینه
public class RdvMedecinsContext : DbContext
{
// ذینفعان
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
}
// راهاندازی پایگاه داده
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
}
}
خط ۱۲ نشان میدهد که اکنون زمینه یک انتیت دیگر برای مدیریت دارد. وقتی پروژه را اجرا میکنیم، پایگاه داده جدید زیر را به دست میآوریم:
![]() |
جدول [CRENEAUX] در واقع ایجاد شده است، و ویژگی جدید، وجود کلیدهای خارجی [1] و [2] است. نام آن از نام فیلد متناظر در انتیت (Medecin) با پسوند «_Id» تولید شده است. برای مشاهده ویژگیهای این کلید خارجی، سعی میکنیم آن را به [3] تغییر دهیم.
![]() |
تصویر بالا نشان میدهد که [Medecin_Id] یک کلید خارجی در جدول [CRENEAUX] است و به کلید اصلی [ID] در جدول [MEDECINS] ارجاع میدهد.
اگر اجزا را برای یک پایگاه داده موجود ایجاد کنید، ستون کلید خارجی لزوماً [Medecin_Id] نامگذاری نخواهد شد. برای سایر ستونها دیدیم که حاشیهنویسی [Column] این مشکل را حل کرد. به طرز عجیبی، برای یک کلید خارجی پیچیدهتر است. باید به شرح زیر عمل کنید:
public class Creneau
{
// دادهها
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
- خطوط ۵–۷: یک فیلد از نوع کلید خارجی (int) ایجاد کنید. با استفاده از ویژگی [Column]، نام ستونی را که کلید خارجی در جدول مرتبط با این انتیت (entity) خواهد بود، مشخص کنید؛
- خط ۹: انوتیشن [ForeignKey] را به فیلدی با نوع [Medecin] اضافه کنید. آرگومان این انوتیشن نام فیلد (نه نام ستون) مرتبط با ستون کلید خارجی در جدول است.
اجرای پروژه این بار جدول زیر را ایجاد میکند:
![]() |
همانطور که در بالا نشان داده شده است، ستون کلید خارجی واقعاً نامی را دارد که ما به آن دادهایم. توجه داشته باشید که فیلدها:
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
تنها به یک ستون، یعنی ستون [MEDECIN_ID]، منجر شده است. با این حال، وجود فیلد [MedecinId] مهم است. هنگام خواندن یک سطر از جدول [CRENEAUX]، مقدار از ستون [MEDECIN_ID] دریافت میشود، یعنی مقدار کلید خارجی در جدول [MEDECINS]. این امر اغلب مفید است.
میدان [Medecin] بالا رابطهٔ چند به یک را که بین موجودیت [Creneau] و موجودیت [Medecin] برقرار است، منعکس میکند. چندین شیء [Creneau] به یک شیء [Medecin] متصل هستند. رابطه معکوس، که در آن یک شیء [Medecin] با چندین شیء [Creneau] مرتبط است، را میتوان با استفاده از یک فیلد اضافی در موجودیت [Medecin] مدلسازی کرد:
public class Medecin
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
...
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
در خط ۸، فیلد [Creneaux] اضافه شده است؛ این یک مجموعه از اشیاء [Creneau] است. این فیلد به ما دسترسی به تمام اسلاتهای قرار ملاقات دکتر را میدهد.
وقتی پروژه را دوباره اجرا میکنیم، میبینیم که جدول [MEDECINS] تغییر نکرده است:
![]() |
هیچ ستونی اضافه نشده است. رابطه کلید خارجی بین جدول [CRENEAUX] و جدول [MEDECINS] برای تولید فیلدهای مرتبط با EF کافی است:
public class Medecin
{
...
public ICollection<Creneau> Creneaux { get; set; }
...
}
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
ما اصول اولیه را میدانیم. اکنون میتوانیم با ایجاد دو موجودیت دیگر کار را به پایان برسانیم.
3.4.3. انتیتیهای [Client] و [Rv]
با استفاده از آنچه آموختهایم، میتوانیم انتیتهای [Client] و [Rv] را بنویسیم. انتیت [Client] حاوی اطلاعاتی دربارهٔ مشتریانی است که توسط برنامهٔ [RdvMedecins] مدیریت میشوند.
![]() | ![]() |
- ID: شناسه مشتری – کلید اصلی جدول
- VERSION: شمارهای که نسخهٔ سطر در جدول را شناسایی میکند. این شماره هر بار که تغییری در سطر اعمال میشود، یک واحد افزایش مییابد.
- NOM: نام خانوادگی مشتری
- PRENOM: نام کوچک آنها
- TITRE: عنوان آنها (خانم، بانو، آقا)
واحد [Client] میتواند به صورت زیر باشد:
[Table("CLIENTS", Schema = "dbo")]
public class Client
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// Rvs مشتری
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
کلاس [Client] تقریباً با کلاس [Medecin] یکسان است. آنها میتوانند از یک کلاس والد مشترک مشتق شده باشند. ویژگی جدید در خط ۲۱ قرار دارد. این ویژگی نشاندهنده این است که یک مشتری ممکن است چندین قرار ملاقات داشته باشد و از وجود یک کلید خارجی از جدول [RVS] به جدول [CLIENTS] استنتاج شده است.
اِنتیت «[Rv]» یک قرار ملاقات را نشان میدهد:
![]() |
- ID: شماره شناسه که به طور منحصربهفرد RV را شناسایی میکند – کلید اصلی
- JOUR: روز RV
- ID_CRENEAU: بازه زمانی برای RV – کلید خارجی در ستون [ID] از جدول [CRENEAUX] – هم بازه زمانی و هم پزشک مربوطه را مشخص میکند.
- ID_CLIENT: شماره مشتری که برای او رزرو انجام میشود – کلید خارجی در ستون [ID] از جدول [CLIENTS]
پدیده [Rv] میتواند به شرح زیر باشد:
[Table("MEDECINS", Schema = "dbo")]
public class Rv
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("JOUR")]
public DateTime Jour { get; set; }
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
- ردههای ۵–۷: کلید اصلی؛
- ردههای ۸–۱۰: تاریخ قرار ملاقات؛
- ردههای ۱۱–۱۲: کلید خارجی از جدول [RVS] به جدول [CLIENTS];
- ردههای ۱۳–۱۵: مشتری دارای قرار ملاقات؛
- ردههای ۱۶–۱۷: کلید خارجی از جدول [RVS] به جدول [CRENEAUX];
- ردههای ۱۸–۲۰: بازه زمانی قرار ملاقات؛
- خطوط ۲۱–۲۳: فیلد کنترل دسترسی همزمان.
در خط ۱۷، ما یک رابطه چند به یک را میبینیم: یک بازه زمانی واحد میتواند به چندین قرار ملاقات (نه در یک روز) مربوط باشد. رابطه معکوس را میتوان در موجودیت [Creneau] منعکس کرد:
public class Creneau
{
// Rvs اسلات
public ICollection<Rv> Rvs { get; set; }
...
}
خط ۴: مجموعه قرارهای رزروشده برای این بازه زمانی.
وقتی پروژه اجرا میشود، پایگاه داده تولیدشده به شرح زیر است:
![]() |
جدولهای [MEDECINS] و [CRENEAUX] تغییر نکردهاند. جدولهای [CLIENTS] و [RVS] به شرح زیر هستند:
![]() | ![]() |
این همانطور که انتظار میرفت است. هنوز چند جزئیات باقی مانده است که باید مرتب شوند:
- مدیریت نام پایگاه داده. در اینجا، این توسط EF تولید شد؛
- پر کردن پایگاه داده با دادهها.
3.4.4. تنظیم نام پایگاه داده
برای تعیین نام پایگاهداده تولیدشده توسط EF، از یک رشته اتصال تعریفشده در [App.config] استفاده خواهیم کرد. این فایل پیکربندی به شرح زیر بهروزرسانی میشود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- برای اطلاعات بیشتر درباره پیکربندی Entity Framework، به http://go.microsoft.com/fwlink/?LinkID=237468 مراجعه کنید. -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- رشته اتصال پایگاه داده -->
<connectionStrings>
<add name="RdvMedecinsContext"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- ارائهدهنده کارخانه -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
- خطوط ۱۵–۱۹: رشته اتصال پایگاه داده؛
- خط 16: ویژگی [name] نام کلاس [RdvMedecinsContext] را که برای زمینه پایداری استفاده میشود، میگیرد. مهم است که این نکته را در نظر داشته باشید. این محدودیت را میتوان در سازنده زمینه دور زد:
// سازنده
public RdvMedecinsContext()
: base("monContexte")
{
}
در این مورد، مقدار ممکن است name="monContexte" باشد. این چیزی است که بعداً در سند خواهیم دید.
- خط ۱۷: رشته اتصال. [Data Source]: نام سروری که SGBD و [Initial Catalog] روی آن قرار دارند: نام پایگاه داده، در این مورد [rdvmedecins-ef]؛ [User Id]: مالک اتصال؛ [Password]: رمز عبور مالک. خواننده باید این رشته را متناسب با محیط خود تطبیق دهد؛
- خطوط 21–29: یک [DbProviderFactory] را تعریف کنید. من نمیدانم این چیست. با قضاوت از روی نام، ممکن است کلاسی باشد که برای تولید لایه [ADO.NET] استفاده میشود که EF را از SGBD جدا میکند:
![]() |
در واقع، این خطوط برای سرور SQL غیرضروری هستند، اما من مجبور شدم آنها را برای سایر نمونههای SGBD اضافه کنم. بنابراین، فقط برای ثبت، آنها را اینجا گنجاندهام. این خطوط هیچ مشکلی ایجاد نمیکنند. تنها نکته مهم، نسخه در خط ۲۷ است. این نسخه مربوط به DLL و [System.Data] است که در مراجع پروژه فهرست شدهاند:
![]() |
خب، آمادهایم. پروژه را اجرا میکنیم و پایگاه داده زیر را به دست میآوریم: [rdvmedecins-ef]:
![]() |
این پایگاه داده نهایی ما خواهد بود. تنها کاری که باقی مانده، پر کردن آن با دادهها است.
3.4.5. پر کردن پایگاه داده
کلاس راهاندازی پایگاه داده را میتوان برای وارد کردن دادهها به آن استفاده کرد:
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
// ابتداییسازی پایگاه داده
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
protected override void Seed(RdvMedecinsContext context)
{
base.Seed(context);
// راهاندازی پایگاه داده
// بیماران
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// پزشکان
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// زمانبندیها
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// قرارها
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
}
}
}
- خط ۶: inicialization در متد [Seed] انجام میشود. این متد در کلاس والد وجود دارد. در اینجا مجدداً تعریف شده است. آرگومان، زمینه پایداری (persistence context) برنامه [RdvMedecinsContext] است؛
- خط ۸: آرگومان به کلاس والد ارسال میشود؛ احتمالاً کلاس والد، زمینه پایداری ارسالشده را باز میکند، زیرا پس از آن دیگر به آن نیازی نیست؛
- خطوط ۱۱–۱۶: ایجاد ۴ کلاینت؛
- خطوط 17–20: اینها به زمینه پایداری اضافه میشوند، دقیقتر به پزشکان آن. به متد [Add] توجه کنید که این کار را ممکن میسازد. مهم است که در اینجا تعریف زمینه را به یاد بیاوریم:
public class RdvMedecinsContext : DbContext
{
// نهادها
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Rv> Rvs { get; set; }
...
همچنین ذکر شده است که مشتریان به زمینه متصل شدهاند، یعنی اکنون توسط EF مدیریت میشوند. قبلاً از آن جدا بودند. آنها به صورت اشیاء وجود داشتند اما توسط EF مدیریت نمیشدند؛
- خطوط ۲۱–۲۷: ایجاد ۴ پزشک؛
- خطوط ۲۸–۳۱: آنها در زمینه پایداری قرار داده میشوند؛
- خطوط ۳۳–۷۰: ایجاد اسلاتهای زمانی. خطوط ۳۴–۵۷ برای پزشک medecins[0]؛ خطوط ۵۸–۶۹ برای پزشک medecins[1]. سایر پزشکان هیچ اسلات زمانی ندارند؛
- خطوط ۷۱–۷۴: این بازههای زمانی در زمینه پایداری قرار داده میشوند؛
- خط ۷۶: ایجاد یک قرار ملاقات برای اولین مشتری با استفاده از اولین بازه زمانی و قرار دادن آن در زمینه پایداری.
وقتی پروژه اجرا میشود، پایگاه داده زیر تولید میشود:
![]() | ![]() |
در بالا میتوانیم جدول [CLIENTS] را مشاهده کنیم که پر شده است.
3.4.6. اصلاح اشیاء
در حال حاضر، کلاسهای [Medecin] و [Client] عملاً یکسان هستند. در واقع، اگر فیلدهای اضافه شده برای مدیریت پایداری با EF 5 را حذف کنیم، آنها یکسان خواهند بود. ما آنها را از کلاسی به نام [Personne] مشتق خواهیم کرد. این دو موجودیت سپس به شکل زیر درمیآیند:
// یک شخص
public abstract class Personne
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// امضا
public override string ToString()
{
return String.Format("[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
...
}
// خدمات عمومی
private string dump(byte[] timestamp)
{
...
}
}
[Table("MEDECINS", Schema = "dbo")]
public class Medecin : Personne
{
// ساعات نوبت دکتر
public ICollection<Creneau> Creneaux { get; set; }
// امضا
public override string ToString()
{
return String.Format("Medecin {0}", base.ToString());
}
}
[Table("CLIENTS", Schema = "dbo")]
public class Client : Personne
{
// قرارهای ملاقات مشتری
public ICollection<Rv> Rvs { get; set; }
// امضا
public override string ToString()
{
return String.Format("Client {0}", base.ToString());
}
}
وقتی پروژه اجرا میشود، همان پایگاه داده تولید میشود. EF 5 هر یک از کلاسهای سطح پایینتر در سلسلهمراتب ارثبری را به یک جدول جداگانه نگاشت کرده است. در واقع، EF 5 از استراتژیهای مختلف تولید جدول برای نمایش ارثبری انتیت استفاده میکند. در اینجا به آنها نمیپردازیم. برای مثال، میتوانید مقاله « : Entity Framework Code First Inheritance – Table Per Hierarchy and Table Per Type» را در URL [http://www.codeproject.com/Articles/393228/Entity-Framework-Code-First-Inheritance-Table-Per] بخوانید.
اکنون از این نسخهٔ انتیتها استفاده خواهیم کرد.
3.4.7. افزودن محدودیتها به پایگاه داده
یک جزئیات دیگر باقی مانده است. جدول [RVS] برای قرار ملاقاتها به شرح زیر است:
![]() |
این جدول باید یک محدودیت یکتایی داشته باشد: برای هر روز معین، نوبت یک پزشک فقط یک بار میتواند برای یک قرار ملاقات رزرو شود. به زبان جدول، این بدان معناست که جفت (JOUR, CRENEAU_ID) باید یکتا باشد. مطمئن نیستم که آیا این قید را میتوان مستقیماً در کد، چه روی اشیاء (entities) و چه در زمینه (context)، بیان کرد. احتمالاً امکانپذیر است، اما من آن را بررسی نکردهام. ما رویکرد متفاوتی را در پیش میگیریم. برای افزودن این قید از یک کلاینت مدیریت سرور SQL استفاده خواهیم کرد.
با استفاده از «SQL Server Management Studio»، راه سادهای برای افزودن این قید جز با اجرای دستور SQL که آن را ایجاد میکند، نیافتهام:
![]() |
- در [1]، یک پرسوجو SQL برای پایگاه داده [rdvmedecins-ef] ایجاد میشود؛
- در [2]، پرسوجوی SQL که محدودیت یکتایی را ایجاد میکند؛
- در [3]، اجرای این پرسوجو یک نمایه جدید در جدول [RVS] ایجاد کرد.
ابزارهای مدیریتی دیگری برای سرور SQL وجود دارد. در اینجا، از ابزار رایگان SQL Server Manager برای EMS SQL استفاده خواهیم کرد. پس از نصب، آن را اجرا میکنیم:
![]() |
- در [1]، یک پایگاه داده ذخیره میکنیم؛
- در [2]، به سرور (محلی) متصل میشویم؛
- در [3]، با استفاده از احراز هویت سرور SQL؛
- در [4]، با استفاده از نام کاربری «sa»؛
- در 5، و رمز عبور sqlserver2012؛
- در [6]، به مرحله بعدی میرویم؛
![]() |
- در [7]، پایگاه داده [rdvmedecins-ef] را انتخاب کنید؛
- در [8]، جادوگر را تکمیل کنید؛
- در [9]، پایگاه داده در درخت پایگاه داده ظاهر میشود. در [10] به آن وارد شوید؛
- در [11]، شما وارد شدهاید.
"SQL Manager Lite for SQL Server" به شما امکان میدهد یک محدودیت منحصربهفرد روی جدول [RVS] ایجاد کنید.
![]() |
- در [1]، میتوانیم محدودیت منحصربهفردی را که قبلاً ایجاد کردیم ببینیم؛
- در [2]، آن را حذف میکنیم؛
- در [3]، نمایهای که با این محدودیت یکتایی مرتبط بود، ناپدید شده است.
ما محدودیت حذفشده را دوباره ایجاد میکنیم:
![]() |
- در [1]، ما یک نمایه جدید برای جدول [RVS] ایجاد میکنیم؛
- آن را به [2] تغییر نام دهید؛
- [3] یک محدودیت یکتایی است؛
- در [4]، روی ستونهای JOUR و CRENEAU_ID؛
زبانهی DDL کد SQL را که باید اجرا شود، در اختیار ما قرار میدهد:
![]() |
- در [6]، فرمان SQL کامپایل میشود؛
![]() |
- به [7]، و تأیید؛
- به [8]، شاخص جدید ظاهر شده است.
رابط ارائهشده توسط «SQL Manager Lite for SQL server» مشابه رابط ارائهشده توسط «SQL Server Management Studio» است. رابطهای مشابهی را میتوان برای SGBD Oracle، PostgreSQL، Firebird و MySQL یافت. بنابراین، از این پس به کار با این خانواده از ابزارهای مدیریت SGBD ادامه خواهیم داد.
برای دسترسی به اطلاعات یک جدول، کافی است روی آن دوبار کلیک کنید:
![]() |
اطلاعات مربوط به جدول انتخابشده در برگهها در دسترس است. در بالا، میتوانیم برگه [Fields] را برای جدول [CLIENTS] ببینیم. برگه [Data] محتویات جدول را نمایش میدهد:

3.4.8. پایگاه داده نهایی
اکنون پایگاه داده نهایی خود را داریم. ما اسکریپت آن، SQL، را صادر میکنیم تا در صورت لزوم بتوانیم آن را دوباره تولید کنیم.
![]() |
- به [1]، شروع جادوگر؛
- تا [2]، سرور؛
- تا [3]، پایگاه دادهای که باید صادر شود؛
![]() |
- در [4]، نام فایلی را که اسکریپت SQL در آن ذخیره میشود، مشخص کنید؛
- در 5، رمزگذاری آن را مشخص کنید؛
- در [6]، مشخص کنید چه مواردی را میخواهید استخراج کنید (جدولها، محدودیتها، دادهها)؛
![]() |
- در [7]، میتوانید اسکریپتی را که قرار است تولید شود، دقیقتر تنظیم کنید؛
- برای [8]، جادوگر را تکمیل کنید.
اسکریپت تولید و در ویرایشگر اسکریپت بارگذاری شده است. میتوانید کد تولیدشده را در SQL مشاهده کنید. اکنون با استفاده از این اسکریپت پایگاه داده را بازسازی خواهیم کرد.
![]() |
- در [1]، پایگاه داده حذف میشود؛
- در [2] و [3]، آن را دوباره ایجاد میکنیم؛
![]() |
- در [4]، ما احراز هویت میکنیم؛
- در 5، اسکریپت SQL برای ایجاد پایگاه داده اجرا میشود؛
![]() |
- در [6]، آن را در «مدیر SQL» ذخیره کنید؛
- در [7]، به پایگاه دادهای که بهتازگی ایجاد شده است متصل میشویم؛
![]() |
- در [8]، پایگاه داده در حال حاضر شامل هیچ جدولی نیست؛
- در [9a]، ویرایشگر اسکریپت باز میشود (SQL);
![]() |
- در [9b]، اسکریپت SQL که قبلاً ایجاد شده بود، باز میشود؛
- در [10]، آن را اجرا کنید؛
![]() |
- در [11]، جداول ایجاد شدهاند؛
- در [12]، دادهها در آنها وارد میشوند؛
![]() |
- در [14]، شرط یکتایی را که برای جدول [RVS] ایجاد کرده بودیم، مییابیم.
اکنون با این پایگاه داده موجود کار خواهیم کرد. اگر نابود یا خراب شود، میدانیم چگونه آن را دوباره تولید کنیم.
3.5. کار با پایگاه داده با استفاده از Entity Framework
ما خواهیم:
- افزودن، حذف و اصلاح عناصر در پایگاه داده؛
- استعلام از پایگاه داده با استفاده از LINQ به Entities؛
- مدیریت دسترسی همزمان به همان عنصر پایگاه داده؛
- درک مفاهیم بارگذاری تنبل (lazy loading) و بارگذاری مشتاق (eager loading)؛
- کشف کنیم که بهروزرسانیهای پایگاه داده از طریق زمینه پایداری در یک تراکنش انجام میشوند.
3.5.1. حذف آیتمها از زمینه پایداری
ما یک پایگاه داده پرشده داریم. قصد داریم آن را خالی کنیم. ما یک کلاس جدید به نام [Erase.cs] در پروژه فعلی [1] ایجاد میکنیم:
![]() |
کلاس [Erase] به شرح زیر است:
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Erase
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// پاکسازی پایگاه داده فعلی
// مشتریان
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
// پزشکان
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
//ذخیرهٔ زمینهٔ پایداری
context.SaveChanges();
}
}
}
}
- خط ۹: عملیات روی یک زمینه پایداری همیشه در داخل یک بند [using] انجام میشوند. این امر تضمین میکند که با خروج از [using]، زمینه بسته شده باشد؛
- خط ۱۳: بر روی زمینهٔ مشتریان [context.Clients] حلقه زده میشود. تمام مشتریان در پایگاه داده در زمینهٔ پایداری قرار خواهند گرفت؛
- خط ۱۵: برای هر یک از آنها، عملیات [Remove] انجام میشود که آنها را از زمینه حذف میکند. در واقع، آنها هنوز در زمینه هستند اما در وضعیت «حذفشده» قرار دارند؛
- خطوط ۱۸–۲۱: همین کار را برای پزشکان نیز انجام میدهیم؛
- خط ۲۳: زمینه پایداری در پایگاه داده ذخیره میشود.
هنگام ذخیرهٔ زمینه در پایگاه داده، موجودیتهای درون زمینه که:
- کلید اصلیشان null است، مشمول عملیاتی با شناسه SQL INSERT هستند؛
- در وضعیت «حذفشده» هستند، مشمول عملیات SQL DELETE میباشند؛
- در وضعیت «تغییر یافته» هستند، مشمول عملیات SQL و UPDATE میباشند؛
همانطور که بعداً خواهیم دید، این عملیات SQL در داخل یک تراکنش انجام میشوند. اگر هر یک از آنها با شکست مواجه شود، همه چیزهایی که قبلاً انجام شده است، برگشت داده میشوند.
بیایید برنامه [Erase] را به عنوان شیء شروع جدید برای پروژه [1] تعیین کنیم، سپس پروژه را اجرا کنیم.
![]() |
بیایید پایگاه داده را بررسی کنیم. خواهیم دید که تمام جداول در [2] خالی هستند. این موضوع تعجبآور است، زیرا ما فقط درخواست حذف پزشکان و مشتریان را داده بودیم. این از طریق مکانیزم کلیدهای خارجی است که جداول دیگر به صورت آبشاری خالی شدهاند.
تعریف کلید خارجی از جدول [CRENEAUX] به جدول [MEDECINS] توسط ارائهدهنده EF به شرح زیر تعریف شده بود:
![]() |
- در [1]، جدول [CRENEAUX] را انتخاب کنید؛
- در [2]، برگه کلیدهای خارجی را انتخاب کنید؛
- در [3]، کلید خارجی را ویرایش کنید؛
![]() |
- در [4]، در برگه DDL، تعریف قید کلید خارجی SQL؛
- در جدول 5، شرط ON DELETE CASCADE تضمین میکند که حذف یک پزشک منجر به حذف بازههای زمانی مرتبط با او شود.
محدودیتهای کلید خارجی برای جدول [RVS] به شیوهای مشابه تعریف شدهاند:
- ردههای ۱–۶: حذف یک مشتری، قرارهای ملاقات مرتبط با او را نیز حذف میکند؛
- خطوط ۱–۶: حذف یک بازه زمانی همچنین تمام قرارهای مرتبط با آن را حذف میکند.
3.5.2. افزودن عناصر به زمینه پایداری
حالا که پایگاه داده را خالی کردهایم، دوباره آن را پر میکنیم. ما برنامه [Fill.cs] [1] را به پروژه اضافه میکنیم.
![]() |
برنامه [Fill.cs] به شرح زیر است:
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Fill
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// پاک کردن پایگاه دادهٔ فعلی
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// آن را ریست کنید
// کلاینتها
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// پزشکان
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// زمانبندیها
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// قرارها
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
// ما زمینه پایداری را ذخیره میکنیم
context.SaveChanges();
}
}
}
}
- خط ۱۰: زمینه پایداری باز میشود؛
- خطوط ۱۳–۲۰: سطرها از جداول [CLIENTS] و [MEDECINS] در زمینه پایداری قرار داده شده و سپس از آن حذف میشوند. ما همیناکنون دیدیم که این کار پایگاه داده را بهطور کامل خالی میکند؛
- خطوط ۲۲–۸۸: رکوردها به زمینه پایداری اضافه میشوند. همه آنها دارای کلید اصلی null هستند. بنابراین، آنها در پایگاه داده درج خواهند شد؛
- خط ۹۰: تغییرات اعمالشده بر روی زمینه با پایگاه داده همگامسازی میشود. سپس پایگاه داده تحت یک سری عملیات قرار میگیرد: SQL و DELETE، و به دنبال آن یک سری عملیات دیگر: SQL و INSERT؛
ما برنامه [Fill] را بهعنوان شیء شروع جدید برای پروژه [1] تعیین میکنیم، سپس پروژه را اجرا میکنیم.
![]() |
در [2] میتوانیم ببینیم که جداول پر شدهاند.
3.5.3. نمایش محتویات پایگاه داده
اکنون با استفاده از پرسوجوی LINQ، محتویات پایگاه داده را نمایش میدهیم. LINQ (زبان پرسوجوی INtegrated) در سال ۲۰۰۷ همراه با چارچوب .NET نسخه ۳.۵ معرفی شد. این زبان بهعنوان توسعهای بر زبانهای .NET و c.a.d عمل میکند و با زبان یکپارچه شده است، بهطوریکه نحوی آن توسط کامپایلر بررسی میشود. این امکان را فراهم میکند که پرسوجوها بر روی مجموعههای مختلف با سینتکسی مشابه زبان پرسوجوی پایگاه داده SQL (Structured Query Language) اجرا شوند. گونههای مختلفی از LINQ وجود دارد:
- LINQ به Object، برای پرسوجوی مجموعههای درونحافظه؛
- LINQ به XML، برای پرسوجوی XML؛
- LINQ به Entity، برای پرسوجو در پایگاههای داده؛
برای وجود داشتن، LINQ به تعدادی افزونه در زبانهای .NET متکی است. این افزونهها را میتوان خارج از LINQ نیز استفاده کرد. ما در اینجا آنها را ارائه نمیدهیم، بلکه صرفاً دو مرجع را معرفی میکنیم که خواننده میتواند در آنها توضیحات عمیقی درباره LINQ بیابد:
- *LINQ in Action*، نوشتهٔ فابریس مارگری، استیو آیکرت و جیم وولی، منتشر شده توسط منینگ؛
- *راهنمای جیبی LINQ*، نوشتهٔ جوزف و بن البهاری، منتشر شده توسط اوریلی.
من کتاب اول را خواندهام و آن را عالی یافتم. کتاب دوم را نخواندهام، اما زمانی که LINQ منتشر شد، کتاب «C# 3.0 in a Nutshell» از همین نویسندگان را خواندم. من این کتاب را بسیار بالاتر از میانگین کتابهایی که معمولاً میخوانم یافتم. به نظر میرسد سایر کتابهای این دو نویسنده نیز از استاندارد مشابهی برخوردار هستند. ما همچنین از LINQPad، یک ابزار آموزشی LINQ نوشتهٔ جوزف البهاری، استفاده خواهیم کرد.
ما قصد داریم اِنتیتِیها را در پایگاه داده نمایش دهیم. برای این کار، دو متد نمایش به کلاسهای آنها اضافه خواهیم کرد. بیایید با اِنتیتِی [Medecin] شروع کنیم:
// یک دکتر
public class Medecin
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// زمانبندیهای دکتر
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// امضا
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return ToString();
}
// ابزار
private string dump(byte[] timestamp){
string str = "";
foreach (byte b in timestamp)
{
str += b;
}
return str;
}
}
- خطوط ۲۷–۳۰: متد ToString کلاس. توجه داشته باشید که این متد، مجموعه را در خط ۲۱ نمایش نمیدهد؛
- خطوط ۳۲–۳۷: متد ShortIdentity که همان کار را انجام میدهد.
در اینجا، برای ارزیابی تأثیر دو روش قبلی، باید مفاهیم بارگذاری تنبل (Lazy) و بارگذاری مشتاق (Eager) را توضیح دهیم. دیدیم که یک انتیت ممکن است به انتیت دیگری وابستگی داشته باشد. این وابستگیها از دو نوع هستند:
- یکبهچند، مانند مورد بالا، که در آن یک پزشک به چندین بازه زمانی متصل است؛
- چند به یک، مانند انتیت [Creneau] در زیر، که در آن چندین بازه زمانی به یک پزشک متصل شدهاند؛
public class Creneau
{
// دادهها
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
وقتی وابستگیها همزمان با اِنتِیتیهایی که به آنها متصل هستند بارگذاری میشوند، این حالت «بارگذاری مشتاق» (Eager Loading) نامیده میشود. در غیر این صورت، این حالت «بارگذاری تنبل» (Lazy Loading) نامیده میشود: وابستگیها تنها زمانی بارگذاری میشوند که برای اولین بار به آنها ارجاع داده شود. به طور پیشفرض، EF 5 از بارگذاری تنبل استفاده میکند: وابستگیها همزمان با شیء بارگذاری نمیشوند.
بیایید به متد [ToString] خود در بالا نگاهی بیندازیم:
// اسلاتهای نوبتدهی دکتر
public ICollection<Creneau> Creneaux { get; set; }
// امضا
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return ToString();
}
متد [ToString] وابستگی [Creneaux] را در خط ۲ نمایش نمیدهد. اگر این کار را انجام میداد، بارگذاری تمام اسلاتهای دکتر را قبل از اجرا اجباری میکرد. برای جلوگیری از این بارگذاری پرهزینه، وابستگی در امضای اینتیتی گنجانده نشد. به طور کلی، ما در هر اینتیتی دو امضا خواهیم داشت:
- یک متد ToString که موجودیت و هر وابستگی یکبهچندی را که ممکن است داشته باشد، نمایش میدهد. همانطور که توضیح داده شد، این کار بارگذاری وابستگی را تحریک میکند؛
- یک روش ShortIdentity که به هیچ وابستگیای ارجاع نخواهد داد. در نتیجه، هیچ وابستگیای بارگذاری نخواهد شد؛
روشهای نمایش برای سایر اِنتیتهها به شرح زیر خواهد بود:
اِنتیت [Client]:
public class Client
{
// دادهها
...
// قرارهای ملاقات بیمار
public ICollection<Rv> Rvs { get; set; }
// امضا
public override string ToString()
{
return String.Format("Client[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return ToString();
}
}
- خطوط ۹–۱۲: متد [ToString] وابستگی در خط ۶ را نمایش نمیدهد؛
وجودیهی [Creneau]:
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
//جزئیات شکاف
public ICollection<Rv> Rvs { get; set; }
// امضا
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Timestamp, MedecinId, dump(Timestamp));
}
}
- خط ۱۶: متد [ToString] به وابستگی در خط ۹ ارجاع میدهد. این امر باعث میشود که بارگذاری شود؛
- خط ۱۱: وابستگی [Rvs] ارجاع نشده است. بارگذاری نخواهد شد؛
- خطوط ۲۱–۲۲: متد [ShortIdentity] دیگر به مرجع [Medecin] در خط ۹ ارجاع نمیدهد. بنابراین این مرجع بارگذاری نخواهد شد.
واحد [Rv]:
public class Rv
{
// دادهها
...
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
// امضا
public override string ToString()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, Client, Creneau, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, ClientId, CreneauId, dump(Timestamp));
}
}
- خطوط ۱۷–۲۰: متد [ToString] به وابستگیهای موجود در خطوط ۹ و ۱۴ ارجاع میدهد. این امر باعث میشود که آنها بارگذاری شوند؛
- خطوط 17–20: متد [ShortIdentity] از این امر جلوگیری میکند، بنابراین وابستگیها بارگذاری نخواهند شد.
در نتیجه، باید در مورد متدهای [ToString] موجودیتها دقت کرد. اگر این مورد در نظر گرفته نشود، نمایش یک جدول در صورتی که وابستگیهای فراوانی داشته باشد، ممکن است نیمی از پایگاه داده را بارگذاری کند.
با توضیح این موارد، کد جدید زیر را مینویسیم: [Dump.cs]:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class Dump
{
static void Main(string[] args)
{
// خروجی پایگاه داده
using (var context = new RdvMedecinsContext())
{
// بیماران
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// پزشکان
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// فواصل زمانی
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// قرارها
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
ما خطوط ۱۷ تا ۲۱ را که اشیاء [Client] را نمایش میدهند، توضیح خواهیم داد. توضیح ارائهشده برای سایر اشیاء نیز صدق میکند.
// مشتریان
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
- خط ۳: کلمه کلیدی
varدر C# 3.0 معرفی شد. این امکان را به شما میدهد که از مشخص کردن نوع دقیق یک متغیر خودداری کنید. سپس کامپایلر نوع را از روی نوع عبارت اختصاصیافته به متغیر استنباط میکند؛ - خط ۳: عبارت تخصیصیافته به متغیر clients یک پرسوجوی LINQ به Entity است. این عبارت شامل کلمات کلیدی زبان SQL است که به LINQ منتقل شدهاند. سینتکسی که در اینجا استفاده شده به شرح زیر است:
from variable in DbSet select variable
سینتکس عمومیتر برای LINQ به این صورت است:
from variable in collection select variable
مجموعه تکرار خواهد شد و برای هر عنصر در آن، متغیر ارزیابی میشود. این تنها زمانی اتفاق میافتد که متغیر [clients] در خط ۳ توسط حلقه for/each در خطوط ۴–۷ تکرار شود. تا زمانی که این اتفاق نیفتد، متغیر [clients] صرفاً یک پرسوجوی ارزیابینشده است؛
- خط ۴: پرسوجوی [clients] مورد پردازش قرار میگیرد. این کار باعث میشود پرسوجو ارزیابی شود. سطرهای جدول [CLIENTS] یکی یکی وارد زمینه پایداری (persistence context) میشوند؛
- خط ۶: متد [ToString] از انتیت [Client] برای نمایش استفاده میشود. هیچ وابستگیای بارگذاری نشده است؛
بیایید به خطوط کد زیر برویم:
- خطوط ۲۴–۲۸: سطرهای جدول [MEDECINS] وارد زمینه پایداری شده و نمایش داده میشوند. هیچ وابستگیای بارگذاری نشده است؛
- خطوط ۳۱–۳۵: سطرهای جدول [CRENEAUX] وارد زمینه پایداری شده و نمایش داده میشوند. ما دیدیم که متد [ToString] این انتها، وابستگی [Medecin] را نمایش میدهد. با این حال، این وابستگی قبلاً بارگذاری شده است. بنابراین، دوباره بارگذاری نخواهد شد؛
- خطوط ۳۸–۴۲: سطرهای جدول [RVS] وارد زمینه پایداری شده و نمایش داده میشوند. ما دیدیم که متد [ToString] این انتها وابستگیهای [Client] و [Creneau] را نمایش داد. با این حال، این وابستگیها قبلاً بارگذاری شدهاند. بنابراین، بارگذاری جدیدی انجام نخواهد شد.
شایان ذکر است که ترتیب نمایش دلخواهی نیست. اگر ما میخواستیم ابتدا اشیاء [Rv] را نمایش دهیم، متد [ToString] برای آن شیء، بارگذاری اشیاء [Client] و [Creneau] مرتبط با آن قرار ملاقاتها را تحریک میکرد. سایر موارد بارگذاری نمیشدند. آنها بعداً در یک نمای متفاوت بارگذاری میشدند. این موضوع بر عملکرد تأثیر میگذارد. کد قبلی برای نمایش همهٔ موجودیتها به چهار دستور SQL نیاز دارد. حال فرض کنید که ابتدا جدول [RVS] مربوط به قرارها را پرسوجو میکنیم. یک پرسوجوی اولیه SQL برای جدول [RVS] لازم است. سپس، متد [ToString] از انتیت [Rv]، در صورت لزوم، بارگذاری انتیتهای مرتبط [Client] و [Creneau] را تحریک خواهد کرد. برای هر یک یک پرسوجوی SQL لازم است. با فرض وجود N2 مشتری و N3 بازه زمانی، و اینکه همه این موجودیتها در جدول [RVS] ارجاع شدهاند، نمایش این جدول به ۱ + N2 + N3 + SQL پرسوجو نیاز دارد. بنابراین، عملکرد آن نسبت به نسخه بررسیشده پایینتر است. برای نمایش جدول [RVS] به همراه وابستگیهای آن، نیاز به یک پیوند (join) بین جدولها است. این کار را میتوان با استفاده از LINQ انجام داد. به این موضوع با یک مثال باز خواهیم گشت. فعلاً، به خاطر خواهیم داشت که باید به پرسوجوهای SQL که زیربنای کد LINQ ما هستند، توجه کنیم.
ما پروژه را برای اجرای این کد جدید، [1] و [2]، پیکربندی میکنیم و سپس آن را اجرا میکنیم:
![]() |
خروجی کنسول به شرح زیر است:
3.5.4. یادگیری LINQ با LINQPad
در بالا، ما از پرسوجوهای LINQ به Entity برای نمایش محتویات جداول پایگاه داده استفاده کردیم. جوزف البهاری برنامهای برای نمایش اشکال مختلف LINQ نوشته است. اکنون آن را ارائه خواهیم داد.
LINQPad در آدرسهای زیر URL [http://www.linqpad.net/] در دسترس است. پس از نصب، آن را [1] اجرا میکنیم:
![]() |
مبتدیان میتوانند با مثالهای موجود در برگههای [Samples] و [2] که شامل طیف گستردهای از مثالها هستند، شروع کنند. بیایید مثال [3] را انتخاب کنیم که سپس در یک پنجرهٔ جداگانه [4] نمایش داده میشود. کد کامل این مثال به شرح زیر است:
// اکنون برای یک عبارت پرسوجوی ساده LINQ-to-Objects (توجه کنید که نقطهویرگول وجود ندارد):
from word in "The quick brown fox jumps over the lazy dog".Split()
orderby word.Length
select word
// بهراحتی میتوانید این را ویرایش کنید... (هیچکس تماشا نمیکند!) از شما خواسته میشود هر تغییری را ذخیره کنید
// تغییرات را در یک فایل جداگانه ذخیره کنید.
//
//نکته: میتوانید با انتخاب بخشی از پرسوجو و سپس فشردن F5 آن بخش را اجرا کنید.
خطوط ۳–۵ نمونهای از پرسوجوی LINQ به Object هستند. پرسوجوی LINQ از سینتکس زیر پیروی میکند:
from variable in collection orderby élément1 select élément2
- که متغیر نشاندهنده عنصر فعلی در مجموعه است. در مثال ما، این مجموعه شامل لیست کلماتی است که از تقسیم رشته حاصل شده است؛
- این مجموعه بر اساس پارامتر
</span>**<span style="color: #000000">orderby</span>**<span style="color: #000000">élément1مرتب شده است. در مثال ما، مجموعه کلمات بر اساس طول مرتب خواهد شد؛ - کلمه کلیدی
selectمشخص میکند که چه چیزی را میخواهیم از عنصر فعلیvariableدر مجموعه استخراج کنیم. در مثال ما، این کلمه خواهد بود.
بیایید این پرسوجو LINQ را اجرا کنیم:
![]() |
- در [1]: یک عبارت LINQ توسط [F5] یا از طریق دکمه اجرا اجرا میشود؛
- در [2]: نمایش. کلمات به ترتیب طولشان نمایش داده میشوند. این مثال ساده قدرت LINQ را نشان میدهد؛
- در [3]، میتوان نمونههای بیشتری را دانلود کرد، بهویژه نمونههایی از کتاب «LINQ in action» [4]؛
![]() |
- در 5، ما مثالی را از کتاب انتخاب کردهایم؛
string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };
// واژهها را بر اساس طول گروهبندی کنید
var groups =
from word in words
orderby word ascending
group word by word.Length into lengthGroups
orderby lengthGroups.Key descending
select new { Length = lengthGroups.Key, Words = lengthGroups };
// هر گروه را چاپ کنید
foreach (var group in groups)
{
Console.WriteLine("Words of length " + group.Length);
foreach (string word in group.Words)
Console.WriteLine(" " + word);
}
- خط ۴: یک پرسوجوی جدید LINQ با کلمات کلیدی جدید؛
- خط ۵: مجموعهٔ بازیابیشده آرایهٔ کلمات خط ۱ است؛
- خط ۶: مجموعه بر اساس واژهها به ترتیب الفبا مرتب شده است؛
- خط ۷: مجموعه گروهبندی میشود (کلیدواژه «into») در یک مجموعه جدید lengthGroups. lengthGroups.Key عامل گروهبندی (کلیدواژه «by») را نشان میدهد، که در این مورد طول کلمات است. lengthGroups کلمات دارای عامل گروهبندی یکسان، یعنی طول یکسان، را با هم گروهبندی میکند؛
- خط ۸: مجموعه lengthGroups بر اساس کلید گروهبندی به ترتیب نزولی مرتب میشود، یعنی در اینجا بر اساس طول کلمه به ترتیب نزولی؛
- خط ۹: از این مجموعه، اشیاء جدید (کلاسهای ناشناس) با دو فیلد ایجاد میشوند:
- Length: طول کلمات،
- Words: واژگان با آن طول؛
در اینجا، میتوانیم بهویژه مزیت کلمه کلیدی var در خط ۴ را ببینیم. از آنجایی که ما در خط ۹ از یک کلاس ناشناس استفاده کردهایم، نمیتوانیم نوع متغیر groups را مشخص کنیم. با این حال، کامپایلر یک نام داخلی به کلاس ناشناس اختصاص میدهد و از آن برای نوعدهی به متغیر groups استفاده میکند. سپس قادر خواهد بود تشخیص دهد که آیا متغیر groups به درستی استفاده میشود یا خیر.
- خط ۱۲: پرسوجوی خط ۴ پردازش میشود. تنها در این نقطه است که ارزیابی میشود. به یاد داشته باشید که اجرای آن یک مجموعه از اشیاء را تولید خواهد کرد، همانطور که در خط ۹ مشخص شده است؛
- خط ۱۴: ویژگی Length عنصر فعلی نمایش داده میشود، یعنی طول کلمه؛
- خطوط ۱۵–۱۷: هر عنصر از مجموعه ویژگی Words نمایش داده میشود، یعنی مجموعه کلماتی که طول آنها قبلاً نمایش داده شد.
وقتی این پرسوجو را اجرا میکنیم، نتیجه زیر را در LINQPad به دست میآوریم:
![]() |
اکنون که چند مثال از پرسوجوهای [LINQ to Object] را دیدیم، بیایید به چند پرسوجوی [LINQ to Entity] نگاهی بیندازیم که به ما امکان میدهند از پایگاههای داده پرسوجو کنیم. ابتدا به پایگاه داده SQL که ایجاد و پر کردهایم متصل میشویم:
![]() |
- در [1]، ما یک اتصال به یک پایگاه داده اضافه میکنیم؛
- در [2]، وسیلهای برای دسترسی به منبع داده. برای دسترسی به پایگاه داده سرور SQL، از [LINQPad Driver] استفاده خواهیم کرد؛
- در [3]، همچنین میتوان یک زمینه پایداری [DbContext] را که در یک assembly .exe یا .dll تعریف شده است، بازیابی کرد (گزینه ۳). متأسفانه، تا به امروز (۸ اکتبر ۲۰۱۲)، Entity Framework 5 پشتیبانی نمیشود؛
- در [4]، امکان دانلود درایورهایی برای SGBD غیر از سرور SQL وجود دارد؛
- در 5، میتوانید درایور را برای SGBD، MySQL و Oracle دانلود کنید؛
![]() |
- در [6]، درایور دانلود شده؛
- در [7]، ما به پایگاه داده سرور SQL متصل میشویم؛
![]() |
- در [8]، پایگاه داده روی سرور (محلی) قرار دارد؛
- در [9]، ما با استفاده از احراز هویت sa / sqlserver2012 متصل میشویم؛
- در [10]، به پایگاه داده [rdvmedecins-ef] که ایجاد کردهایم؛
- در [11]، میتوانید اتصال را آزمایش کنید؛
- در [12]، ما جادوگر را تکمیل میکنیم؛
- در [13]، اتصال در LINQPad ظاهر میشود.
این اشیاء از جدول [rdvmedecins-ef] ایجاد شدهاند. آنها به شرح زیر هستند:
![]() |
- در [1]، [CLIENTS] نمایانگر تمام موجودیتها در [Client] است. هر موجودیت دارای:
- ویژگیها (ID, TITRE, NOM, PRENOM, TIMESTAMP)،
- یک رابطه یکبهچند با [CLIENTRVS]؛
- که در آن [2] و [CRENEAUXes] نمایانگر مجموعهٔ موجودیتها [Creneau] هستند. هر موجودیت دارای:
- ویژگیها (ID, HDEBUT, MDEBUT, HFIN, MFIN, MEDECIN_ID, TIMESTAMP)،
- یک رابطه یکبهچند [CRENEAURVS],
- یک رابطه چند به یک [MEDECIN];
- در [3]، انتیت [MEDECINS] نماینده مجموعه انتیتهای [Medecin] است. هر انتیت دارای:
- ویژگیها (ID, TITRE, NOM, PRENOM, TIMESTAMP)،
- یک رابطه یکبهچند [MEDECINCRENEAUXes]؛
- در [4]، موجوده [RVS] نمایانگر مجموعه موجودههای [Rv] است. هر موجوده دارای:
- ویژگیها (ID, JOUR, CLIET_ID, CRENEAU_ID, TIMESTAMP)،
- یک رابطه چند به یک با [CLIENT]،
- یک رابطهٔ چندبهیک [CRENEAU].
توجه کنید که نامهای ویژگیهای بالا با آنچه تاکنون استفاده کردهایم متفاوت است. این موضوع اهمیت چندانی ندارد. ما صرفاً میخواهیم اصول اولیه پرسوجوی پایگاه داده را بیاموزیم.
بیایید ببینیم چگونه میتوانیم از این پایگاه دادهٔ انتیتها پرسوجو کنیم. برای مثال، ما میخواهیم فهرستی از پزشکان را بر اساس TITRE و NOM مرتبشده دریافت کنیم:
![]() |
- در [1]، یک پرسوجوی جدید ایجاد میکنیم؛
- در [2]، متن پرسوجو؛
![]() |
- در [3]، نتیجه پرسوجو؛
- در [4]، همان پرسوجو با استفاده از عبارتهای لامبدا. یک پرسوجو با استفاده از عبارتهای لامبدا کمتر خواناست تا یک پرسوجوی مبتنی بر متن، و ممکن است ترجیح داده شود که از آنها اجتناب کرد. با این حال، آنها گاهی ضروری هستند زیرا امکان انجام کارهای خاصی را میدهند که پرسوجوهای مبتنی بر متن قادر به انجامشان نیستند. یک عبارت لامبدا به تابعی با یک پارامتر ورودی a و یک پارامتر خروجی b، در قالب a=>b اشاره دارد. متد OrderBy فوق یک عبارت لامبدا را به عنوان تنها پارامتر خود میپذیرد. این امر معیاری را که یک مجموعه بر اساس آن مرتب میشود، در اختیار متد قرار میدهد. بنابراین، MEDECINS.OrderBy(m=>m.TITRE) فهرست پزشکان مرتبشده بر اساس عناوینشان است. این عبارت باید بهعنوان یک خط لوله روی یک مجموعه خوانده شود. مجموعه پزشکان بهعنوان ورودی به متد OrderBy ارسال میشود. این متد موجودات [Medecin] را یکییکی پردازش خواهد کرد. در عبارت لامبدا
m=>m.TITRE،mنمایانگر ورودی تابع لامبدا است. میتوان آن را به دلخواه نامگذاری کرد. در اینجا، ورودی تابع لامبدا یک انتیت[Medecin]خواهد بود. تابع m=>m.TITRE به این صورت خوانده میشود: اگر ورودی من (یک موجودیت [Medecin]) را با فراخوانی m مشخص کنم، خروجی من m.TITRE، یعنی عنوان پزشک، خواهد بود. MEDECINS.OrderBy(m=>m.TITRE) به نوبه خود یک مجموعه است: مجموعهای از پزشکان مرتبشده بر اساس عنوان. این مجموعه جدید را میتوان به متد دیگری پاس کرد؛ در مثال، متد ThenBy. این بر همان اصل کار میکند. از آن برای مشخص کردن پارامترهای اضافی جهت مرتبسازی مجموعه استفاده میشود.
خواندن کد لامبدا معادل کدی که معمولاً تایپ میکنیم، راه خوبی برای یادگیری آن است؛
![]() |
- در 5، دستور SQL به پایگاه داده صادر میشود. در اینجا نیز باید این کد را با دقت بخوانیم. این کد به ما امکان میدهد تا هزینه واقعی یک پرسوجوی LINQ را ارزیابی کنیم.
در ادامه، چند مثال از پرسوجوهای LINQ را ارائه میدهیم. در هر مورد، نتایج نمایشدادهشده و کدهای معادل لَمبدا و SQL را نشان میدهیم. برای درک این پرسوجوها، مهم است که روابط چند به یک را که موجودیتها را به یکدیگر پیوند میدهند، به یاد آوریم. این روابط هستند که به ما امکان میدهند از یک موجودیت به موجودیت دیگر حرکت کنیم. اینها به عنوان ویژگیهای ناوبری شناخته میشوند.
![]() |
// مشتریان با عنوان «آقا»، مرتبشده به ترتیب نزولی نام
نتایج:
![]() |
LINQ | |
لامبدا | |
SQL | |
// تمام نوبتهای ملاقات با پزشک مربوطه
نتایج (بخشی):
![]() |
LINQ | |
لامبدا | ![]() |
SQL | |
// تمام قرارها با مشتری و پزشک مربوطه
نتایج:
![]() |
LINQ | |
لامدا | ![]() |
SQL | |
// پزشکان بدون نوبت
نتایج:
![]() |
LINQ | |
لامبدا | ![]() |
SQL | |
برای این درخواست هیچ پرسوجویی با شناسه LINQ وجود ندارد. شما باید از عبارتهای لامبدا استفاده کنید. این عبارت به صورت زیر خوانده میشود: من مجموعهٔ پزشکان (MEDECINS) را میگیرم و تنها آن پزشکان (m) را نگه میدارم (Where) که نتوانستم در مجموعهٔ قرارها (RVS) قرار ملاقاتی (rv) با آن پزشک (m) پیدا کنم.
// زمانهای خالی خانم پلسیه
نتایج (جزئی):
![]() |
LINQ | |
لامبدا | ![]() |
SQL | |
// تعداد قرارهای ملاقات خانم پلسیه در تاریخ ۸ اکتبر ۲۰۱۲
نتایج:
![]() |
LINQ | |
لامبدا | |
SQL | |
// فهرست مشتریانی که در تاریخ ۸ اکتبر ۲۰۱۲ با خانم پلسیه قرار ملاقات گذاشتهاند
نتایج:
![]() |
LINQ | |
لامبدا | ![]() |
SQL | |
// تعداد بازههای زمانی برای هر پزشک
نتایج:
![]() |
LINQ | |
لامبدا | ![]() |
SQL | |
3.5.5. ویرایش یک موجودیت متصل به زمینه پایداری
ما عملیات زیر را روی زمینه پایداری بررسی کردهایم:
- افزودن یک عنصر به زمینه ([dbContext].[DbSet].Add);
- حذف یک عنصر از زمینه ([dbContext].[DbSet].Remove);
- پرسوجو از یک زمینه با استفاده از پرسوجوهای LINQ.
وقتی میخواهید زمینه را با پایگاه داده همگامسازی کنید، مینویسید [dbContext].SaveChanges().
![]() | ![]() |
کد [ModifyAttachedEntity] نشان میدهد چگونه یک موجودیت متصل به زمینه را اصلاح کنید:
using System;
using System.Data;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyAttachedEntity
{
static void Main(string[] args)
{
Client client1, client2, client3;
// زمینهٔ اول
using (var context = new RdvMedecinsContext())
{
// پاک کردن پایگاه دادهٔ جاری
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// افزودن مشتری
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
//پیگیری
Console.WriteLine("client1--avant");
Console.WriteLine(client1);
// ذخیره زمینه
context.SaveChanges();
//پیگیری
Console.WriteLine("client1--après");
Console.WriteLine(client1);
}
// زمینه دوم
using (var context = new RdvMedecinsContext())
{
//بازیابی client1 از client2
client2 = context.Clients.Find(client1.Id);
//پیگیری
Console.WriteLine("client2");
Console.WriteLine(client2);
// تغییر مشتری ۲
client2.Nom = "yy";
// ذخیره زمینه
context.SaveChanges();
}
// زمینه سوم
using (var context = new RdvMedecinsContext())
{
//بازیابی کلاینت2 در کلاینت3
client3 = context.Clients.Find(client2.Id);
//پیگیری
Console.WriteLine("client3");
Console.WriteLine(client3);
}
}
}
}
- خط ۱۵: زمینهٔ برنامه باز میشود؛
- خطوط ۱۸–۲۵: زمینه پاک میشود. برای دقت بیشتر، تمام سوابق از پایگاه داده به درون زمینه بازیابی شده و سپس به وضعیت «حذفشده» تنظیم میشوند. توجه داشته باشید که در این مرحله، پایگاه داده تغییر نکرده است. تا زمانی که زمینه با پایگاه داده همگامسازی نشود، پایگاه داده بدون تغییر باقی میماند. به یاد داشته باشید که حذف اشیاء [Medecin] و [Client] برای خالی کردن پایگاه داده از طریق حذفهای زنجیرهای کافی است؛
- خطوط ۲۷–۲۸: یک مشتری جدید به پایگاه داده اضافه میشود؛
- خطوط ۳۰–۳۱: مشتری قبل از ذخیره شدن در پایگاه داده نمایش داده میشود؛
- خط ۳۳: زمینه با پایگاه داده همگامسازی میشود. موجودیتهایی که بهعنوان «حذفشده» علامتگذاری شدهاند، مشمول عملیاتی با شناسه SQL DELETE خواهند شد، اِنتیتی افزودهشده تحت عملیات SQL INSERT قرار خواهد گرفت؛
- خطوط ۳۵–۳۶: مشتری پس از همگامسازی با پایگاه داده نمایش داده میشود؛
نتیجهی نمایشدادهشده در کنسول به شرح زیر است:
نکات زیر باید مورد توجه قرار گیرند:
- قبل از همگامسازی با پایگاه داده، مشتری نه کلید اصلی دارد و نه timestamp,
- پس از همگامسازی، هر دو را دارد. در اینجا باید توجه داشت که کلید اصلی طوری پیکربندی شده بود که توسط SQL Server تولید شود. به همین ترتیب، این SGBD به طور خودکار مهر زمانی را تولید میکند؛
- خط ۳۷: کانکست پایداری بسته میشود. موجودیتهایی که در آن وجود داشتند، «جدا» (detached) میشوند. آنها به صورت شیء وجود دارند اما نه به عنوان موجودیتهایی که به کانکست پایداری متصل هستند؛
- خط ۳۹: یک زمینه جدید و خالی آغاز میشود؛
- خط ۴۲: مشتری مستقیماً از طریق کلید اصلیاش از پایگاه داده بازیابی میشود. سپس وارد زمینه (context) میشود. اگر پیدا نشود، متد Find نشانگر null را برمیگرداند؛
- خطوط ۴۸–۴۹: نمایش داده میشود؛
این کار نتیجه زیر را تولید میکند:
- خط ۴۷: آن اصلاح میشود؛
- خط ۴۹: زمینه با پایگاه داده همگامسازی میشود. EF تشخیص خواهد داد که برخی از عناصر زمینه از زمان بارگذاری در آن تغییر کردهاند. برای این عناصر، دستورات SQL و UPDATE را در پایگاه داده تولید خواهد کرد. بنابراین در اینجا، همگامسازی شامل یک دستور واحد خواهد بود: UPDATE;
- خط ۵۰: زمینه دوم بسته میشود. موجودیت client2 که به زمینه متصل بود، اکنون از آن جدا میشود؛
- خط ۵۲: یک زمینه سوم خالی باز میشود؛
- خط 55: تنها مشتری موجود در پایگاه داده دوباره وارد آن میشود. ما میخواهیم ببینیم که آیا تغییری که در زمینه قبلی روی آن اعمال شده در پایگاه داده منعکس شده است یا خیر؛
- خطوط ۵۷–۵۸: مشتری نمایش داده میشود. این کار نتیجه زیر را تولید میکند:
نام مشتری در پایگاه داده بهروزرسانی شده است. شایان ذکر است که timestamp او نیز بهروزرسانی شده است.
- خط ۵۹: زمینه بسته میشود. ضمناً، باید توجه داشت که برخلاف دو مورد قبلی، نیازی به همگامسازی زمینه با پایگاه داده (SaveChanges) از قبل نبود، زیرا زمینه تغییر نکرده بود.
3.5.6. مدیریت اشیاء جداشده
بیایید به معماری لایهبندیشده یک برنامه مانند مورد مطالعه بازگردیم:
![]() |
لایه [DAO] برای دسترسی به دادهها از ORM و EF5 استفاده میکند. ما بلوکهای پایه این لایه را داریم. هر متد یک زمینه پایداری را باز میکند، عملیات لازم را روی آن انجام میدهد (درج، بهروزرسانی، حذف، پرسوجو) و سپس آن را میبندد. اشیایی که توسط لایه [DAO] مدیریت میشوند، به لایه وب ASP.NET ارسال میگردند. در این لایه، آنها خارج از زمینه پایداری هستند و در نتیجه جدا (detached) محسوب میشوند. در لایه وب، کاربر میتواند این اشیاء را اصلاح کند (افزودن، بهروزرسانی، حذف). هنگامی که به لایه [DAO] بازمیگردند، همچنان جدا هستند. با این حال، لایه [DAO] باید تغییرات انجامشده توسط کاربر را به پایگاه داده منتقل کند. بنابراین، این لایه باید با اشیاء جدا کار کند. بیایید سه سناریوی ممکن را در نظر بگیریم:
افزودن یک موجودیت جداشده
این رویه استاندارد برای افزودن است. به سادگی موجوده جداشده را به زمینه اضافه (Add) کنید و اطمینان حاصل کنید که کلید اصلی آن null است.
ویرایش یک موجودیت جداشده
میتوان از کد زیر استفاده کرد:
- متد [DbContext].Entry(detached-entity) این انتیت را در زمینه قرار میدهد؛
- وضعیت این موجودیت روی «تغییر یافته» تنظیم میشود تا مشمول دستور SQL UPDATE گردد.
حذف یک موجودیت جداشده
میتوان از کد زیر استفاده کرد:
- خط ۱: موجودیتی با کلید اصلی مشابه موجودیت جداشده را به زمینه اضافه کنید؛
- خط ۲: آن را حذف کنید:
باید توجه داشت که این نیازمند یک SELECT بهعنوان پایه است که پس از آن DELETE میآید، در حالی که معمولاً تنها DELETE کافی است. همچنین میتوان از مثال اصلاح یک انتیتهی جداشده پیروی کرد و نوشت:
از آنجایی که نتوانستم برای عملیات SQL انجام شده روی پایگاه داده، لاگها را راهاندازی کنم، نمیدانم آیا یک روش بر روش دیگر ارجح است یا خیر.
در اینجا یک مثال آورده شده است:
![]() | ![]() |
کد برنامه برای [ModifyDetachedEntities] به شرح زیر است:
using System;
using System.Data;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyDetachedEntities
{
static void Main(string[] args)
{
Client client1;
// پاک کردن پایگاه دادهٔ فعلی
Erase();
// افزودن مشتری
using (var context = new RdvMedecinsContext())
{
// ایجاد مشتری
client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
// افزودن مشتری به زمینه
context.Clients.Add(client1);
//ذخیرهٔ زمینه
context.SaveChanges();
}
// نمایش پایگاه داده
Dump("1-----------------------------");
// client1 در زمینه نیست – آن را اصلاح کنید
client1.Nom = "y";
// زمینهٔ جدید
using (var context = new RdvMedecinsContext())
{
// در اینجا، ما یک زمینه خالی داریم
// ما client1 را در حالت اصلاحشده در زمینه قرار میدهیم
context.Entry(client1).State = EntityState.Modified;
// زمینه ذخیره میشود
context.SaveChanges();
}
// نمای پیشفرض
Dump("2-----------------------------");
// حذف یک موجودیت خارج از زمینه
using (var context = new RdvMedecinsContext())
{
// در اینجا، ما یک زمینهٔ جدید و خالی داریم
// ما client1 را در حالت حذفشده در زمینه قرار میدهیم
context.Entry(client1).State = EntityState.Deleted;
//ما زمینه را ذخیره میکنیم
context.SaveChanges();
}
// نمایش پایگاه داده
Dump("3-----------------------------");
}
static void Erase()
{
// پاک کردن پایگاه داده
using (var context = new RdvMedecinsContext())
{
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// زمینه ذخیره میشود
context.SaveChanges();
}
}
static void Dump(string str)
{
Console.WriteLine(str);
// نمایش پایگاه داده
using (var context = new RdvMedecinsContext())
{
foreach (var rv in context.Rvs)
{
Console.WriteLine(rv);
}
foreach (var creneau in context.Creneaux)
{
Console.WriteLine(creneau);
}
foreach (var client in context.Clients)
{
Console.WriteLine(client);
}
foreach (var medecin in context.Medecins)
{
Console.WriteLine(medecin);
}
}
}
}
}
- خط ۱۵: پایگاه داده پاک میشود؛
- خطوط ۱۷–۲۵: یک مشتری به پایگاه داده اضافه میشود؛
- خط ۲۷: محتویات پایگاه داده را نمایش میدهد؛
- پس از خط ۲۵، زمینه پایداری دیگر وجود ندارد. بنابراین دیگر هیچ موجود متصلشدهای وجود ندارد. موجود client1 به وضعیت «جداشده» تنظیم شده است؛
- خط ۲۹: نام موجودیت جداشده تغییر میکند؛
- خط ۳۱: یک زمینهٔ خالی جدید باز میشود؛
- خط ۳۵: موجودیت جداشده client1 در وضعیت «تغییرده شده» در زمینه قرار داده میشود؛
- خط ۳۷: زمینه با پایگاه داده همگامسازی میشود؛
- خط ۳۸: آن بسته میشود؛
- خط ۴۰: پایگاه داده نمایش داده میشود؛
نام مشتری در پایگاه داده بهروزرسانی شده است. توجه داشته باشید که timestamp بهروزرسانی شده است؛
- خط ۴۲: یک زمینهٔ خالی جدید باز میشود؛
- خط ۴۶: موجودیت جداشده client1 با وضعیت «حذفشده» در زمینه قرار داده میشود؛
- خط ۴۸: زمینه با پایگاه داده همگامسازی میشود؛
- خط ۴۹: آن بسته میشود؛
- خط ۵۱: پایگاه داده نمایش داده میشود؛
این موجودیت واقعاً از پایگاه داده حذف شده است.
اکنون به بررسی دو روش بارگذاری وابستگیهای یک انتیت: بارگذاری تنبل و بارگذاری مشتاق میپردازیم.
3.5.7. بارگذاری تنبل و مشتاق
بیایید دوباره به طرحواره وابستگی چند به یک برای یکی از چهار موجودهٔ خود نگاهی بیندازیم:
![]() |
در بالا، موجوده [Creneau] دارای یک ویژگی ناوبری [Creneau.Medecin] است که به موجوده [Medecin] اشاره میکند. این یک وابستگی نامیده میشود. ما دیدهایم که وابستگیهای یکبهچند نیز وجود دارند. اصل توضیحدادهشده در اینجا برای آنها نیز صدق میکند.
به طور پیشفرض، EF 5 در حالت بارگذاری تنبل (Lazy Loading) است: هنگامی که یک موجودیت را از پایگاه داده به داخل زمینه پایداری (persistence context) فراخوانی میکند، وابستگیهای آن را فراخوانی نمیکند. این وابستگیها هنگام اولین استفاده فراخوانی خواهند شد. این یک اقدام مبتنی بر عقل سلیم است. اگر اینطور نبود، وارد کردن قرارها به زمینه، بر اساس وابستگیهای فوق، همچنین موارد زیر را نیز وارد میکرد:
- اشیاء [Creneau] مرتبط با قرارها؛
- اشیاء [Medecin] مرتبط با این بازههای زمانی؛
- وابستههای [Clients] مرتبط با قرارها.
با این حال، گاهی اوقات به یک موجودیت و وابستگیهای آن نیاز داریم. ما هر دو روش بارگذاری را نشان خواهیم داد.
![]() | ![]() |
کد برای [LazyEagerLoading] به شرح زیر است:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class LazyEagerLoading
{
// اشیاء
static Medecin[] medecins;
static Client[] clients;
static Creneau[] creneaux;
static void Main(string[] args)
{
// ابتداییسازی پایگاه داده
InitBase();
Console.WriteLine("Initialisation terminée");
//بارگذاری مشتاق
Creneau creneau;
int idCreneau = (int)creneaux[0].Id;
using (var context = new RdvMedecinsContext())
{
// اسلات شمارهٔ 0
creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
//نمایش وابستگی
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 1 suivante s'est produite : {0}", e);
}
//بارگذاری تنبل – حالت پیشفرض
using (var context = new RdvMedecinsContext())
{
// اسلات شماره 0
creneau = context.Creneaux.Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
// نمایش وابستگی
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 2 suivante s'est produite : {0}", e);
}
}
static void InitBase()
{
// ابتداییسازی پایگاه داده
using (var context = new RdvMedecinsContext())
{
//پاکسازی پایگاه دادهٔ جاری
...
// ابتداییسازی پایگاه داده
// مشتریان
clients = new Client[] {
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
...
// قرارها
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
//ذخیره کردن زمینه پایداری
context.SaveChanges();
}
}
}
}
- خط ۱۸: ما از یک پایهٔ شناختهشده، همان که تا کنون استفاده شده، شروع میکنیم. پس از این عملیات، آرایههای خطوط ۱۱–۱۳ با موجودیتهای جداشده پر میشوند؛
- خطوط 21–22: ما بر اولین بازه زمانی و پزشک مربوطه تمرکز میکنیم؛
- خط ۲۳: زمینه جدید؛
- خط ۲۶: بازه زمانی به همراه وابستگی آن (بارگذاری مشتاق) در زمینه قرار داده میشود. از آنجایی که این رفتار پیشفرض نیست، این وابستگی باید صراحتاً درخواست شود. متد
</span>**Include**<span style="color: #000000">این کار را امکانپذیر میسازد. پارامتر آن نام وابستگی در میان انتیتی وارد شده به زمینه است. پرسوجویی که این انتیت را وارد زمینه میکند از عبارتهای لامبدا استفاده میکند. متدSingleبه شما امکان میدهد تا شرطی را برای بازیابی یک انتیت واحد مشخص کنید. در اینجا، ما در پایگاه داده به دنبال انتیت[Creneau]میگردیم که کلید اصلی آن اسلات شماره 0 است؛ - خط ۲۷: انتیتای بازیابیشده نمایش داده میشود. بیایید دو متد نوشتن مورد استفاده در انتیتاها را به یاد بیاوریم:
// امضا
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5},{6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// امضای کوتاه
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, MedecinId, dump(Timestamp));
}
- خطوط ۲–۵: متد [ToString] وابستگی [Medecin] را نمایش میدهد. اگر این وابستگی قبلاً در زمینه موجود نباشد، از پایگاه داده بازیابی شده و به آن اضافه خواهد شد؛
- خطوط ۸–۱۱: متد [ShortIdentity] وابستگی [Medecin] را نمایش نمیدهد. بنابراین اگر این وابستگی در زمینه موجود نباشد، در پایگاه داده جستجو نخواهد شد؛
در این مرحله، خروجی کنسول به شرح زیر است:
- خط ۲۸: زمینه بسته میشود؛
- خطوط ۳۰–۳۷: تلاشی برای نوشتن وابستگی این عنصر به شناسه [Medecin] انجام میشود. به عنوان یادآوری، بارگذاری تنبل (Lazy Loading) به این صورت عمل میکند: اگر وابستگی موجود نباشد، اولین باری که استفاده میشود، بارگذاری میگردد. در اینجا، این وابستگی به طور معمول موجود است. خروجی به شرح زیر است:
- خطوط ۳۹–۴۴: در یک زمینهٔ جدید، اسلات شمارهٔ ۰ بار دیگر از پایگاه داده بازیابی شده و وارد زمینه میشود. در اینجا وابستگی [Medecin] بهطور صریح درخواست نشده است. بنابراین بازیابی نخواهد شد (بارگذاری تنبلانه)؛
- خط ۴۳: شناسهی کوتاه برای اسلات به صورت زیر نمایش داده میشود:
در اینجا، برای نمایش این موجودیت، مهم است که به جای ToString از ShortIdentity استفاده شود. اگر از ToString استفاده شود، وابستگی [Medecin] نمایش داده میشود و برای این کار در پایگاه داده جستجو خواهد شد. با این حال، این چیزی نیست که ما میخواهیم.
- خط ۴۴: زمینه بسته شده است؛
- خطوط ۴۶–۵۳: تلاشی برای نمایش وابستگی این انتیت انجام میشود. مهم است که این کار را خارج از زمینه انجام دهیم، در غیر این صورت در پایگاه داده جستجو شده و پیدا خواهد شد. در اینجا، ما خارج از زمینه هستیم. اِنتیتی [Creneau] جدا شده و وابستگی آن [Medecin] موجود نیست (بارگذاری تنبل). چه اتفاقی خواهد افتاد؟ نمایش صفحه به شرح زیر است:
EF متوجه شد که وابستگی [Medecin] وجود ندارد. این کلاس سعی کرد آن را بارگذاری کند، اما از آنجایی که زمینه (context) بسته بود، این عملیات دیگر امکانپذیر نبود. ما این استثنا، [System.ObjectDisposedException]، را یادداشت میکنیم، زیرا این نشانگر تلاش برای بارگذاری یک وابستگی خارج از یک زمینهٔ باز است.
اکنون بیایید همزمانی در دسترسی به اشیاء را بررسی کنیم.
3.5.8. همزمانی در دسترسی به اِنتیتهها
بیایید به تعریف موجودیت [Client] بازگردیم:
public class Client
{
// دادهها
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// Rvs مشتری
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// امضا
...
}
ما بر روی فیلد [Timestamp] در خط ۲۳ تمرکز خواهیم کرد. میدانیم که مقدار آن توسط SGBD تولید میشود. ما همچنین متوجه شدیم که حاشیهنویسی [Timestamp] در خط ۲۲ باعث شد که EF 5 از فیلد حاشیهنویسیشده برای مدیریت همزمانی دسترسی به موجودیتها استفاده کند. بیایید به یاد بیاوریم که مدیریت همزمانی دسترسی شامل چه مواردی است:
- یک فرآیند P1 در زمان T1 یک سطر L را از جدول [MEDECINS] میخواند. این سطر دارای timestamp TS1 است؛
- یک فرآیند P2 همان سطر L را از جدول [MEDECINS] در زمان T2 میخواند. این سطر دارای مقادیر timestamp و TS1 است زیرا فرآیند P1 هنوز تغییر خود را commit نکرده است؛
- فرآیند P1 تغییر خود را در خط L ثبت میکند. وضعیت خط L، که timestamp بود، سپس به TS2 تغییر میکند؛
- فرآیند P2 تغییر خود را برای خط L اعمال میکند. Theسپس ORM یک استثنا پرتاب میکند زیرا فرآیند P2 دارای timestamp TS1 برای خط L است که با timestamp QZXW2 متفاوت است.HTMLP001064ZQX در پایگاه داده یافت شد.
این به عنوان کنترل همزمانی خوشبینانه شناخته میشود. با EF 5، یک فیلد که این نقش را ایفا میکند باید یکی از دو ویژگی [Timestamp] یا [ConcurrencyCheck] را داشته باشد. سرور SQL دارای نوع [timestamp] است. برای ستونی با این نوع، مقدار به طور خودکار توسط سرور SQL هر زمان که یک سطر درج یا بهروزرسانی میشود، تولید میگردد. چنین ستونی سپس میتواند برای مدیریت دسترسی همزمان استفاده شود.
ما این دسترسی همزمان را با استفاده از دو نخ (thread) نشان خواهیم داد که همزمان یک موجودیت یکسان [Client] را در پایگاه داده تغییر میدهند. پروژه به شرح زیر پیش میرود:
![]() | ![]() |
کد برنامه [AccèsConcurrents] به شرح زیر است:
using System;
using System.Data;
using System.Linq;
using System.Threading;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// شیء مبادله شده با رشتهها
class Data
{
public int Duree { get; set; }
public string Nom { get; set; }
public Client Client { get; set; }
}
// برنامهٔ آزمایشی
class AccèsConcurrents
{
static void Main(string[] args)
{
Client client1;
using (var context = new RdvMedecinsContext())
{
// رشتهٔ اصلی
Thread.CurrentThread.Name = "main";
// پاکسازی پایگاه دادهٔ جاری
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// افزودن یک مشتری
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
// ردیابی
Console.WriteLine("{0} client1--avant sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
//پشتیبانگیری
context.SaveChanges();
//پیگیری
Console.WriteLine("{0} client1--après sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
}
// ما قصد داریم client1 را با استفاده از دو نخ اصلاح کنیم
// رشته t1
Thread t1 = new Thread(Modifie);
t1.Name = "t1";
t1.Start(new Data { Duree = 5000, Nom = "yy", Client = client1 });
// رشته t2
Thread t2 = new Thread(Modifie);
t2.Name = "t2";
t2.Start(new Data { Duree = 5000, Nom = "zz", Client = client1 });
// منتظر پایان هر دو نخ هستیم
Console.WriteLine("Thread {0} -- début attente fin des deux threads", Thread.CurrentThread.Name);
t1.Join();
t2.Join();
Console.WriteLine("Thread {0} -- fin attente fin des deux threads", Thread.CurrentThread.Name);
// ما تغییر را نمایش میدهیم – تنها یکی میتواند موفق شده باشد
using (var context = new RdvMedecinsContext())
{
//بازیابی client1 از client2
Client client2 = context.Clients.Find(client1.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
}
}
// رشته
static void Modifie(object infos)
{
...
}
- خط ۲۶: یک زمینه خالی آغاز میشود؛
- خط ۲۹: به نخ فعلی نامی داده میشود تا از دو نخی که بعداً ایجاد خواهند شد متمایز شود؛
- خطوط ۳۱–۳۸: اشیاء [Medecin] و [Client] به وضعیت «حذفشده» تنظیم میشوند؛
- خطوط ۴۰–۴۱: یک مشتری به زمینه اضافه میشود؛
- خطوط ۴۳–۴۴: این قبل از همگامسازی زمینه نمایش داده میشود؛
- خط ۴۶: همگامسازی زمینه با پایگاه داده: موجودیتهایی که در وضعیت «حذف شده» هستند از پایگاه داده حذف خواهند شد. موجودیت [Client] که در زمینه قرار گرفته است، در پایگاه داده درج خواهد شد. این تنها مورد در پایگاه داده خواهد بود؛
- خطوط ۴۷–۴۹: کلاینت پس از همگامسازی زمینه نمایش داده میشود. در این مرحله، نمایشهای صفحه به شرح زیر است:
توجه داشته باشید که پس از همگامسازی زمینه، مشتری دارای یک کلید اصلی و یک timestamp است؛
- خط ۵۰: زمینه بسته میشود؛
- خط ۵۳: یک نخ t1 با متد [Modifie] در خط ۸۴ مرتبط میشود. این بدان معناست که وقتی این نخ اجرا شود، متد [Modifie] را اجرا خواهد کرد؛
- خط ۵۴: به نخ t1 نامی داده میشود؛
- خط ۵۵: نخ t1 راهاندازی میشود. پارامترها به صورت ساختار [Data] که در خطوط ۱۲–۱۷ تعریف شده است، به آن ارسال میشوند:
- مدت زمان: این تِرد Durée ثانیه قبل از اتمام اجرای خود متوقف خواهد شد،
- Client: مرجعی به کلاینتی که باید در پایگاه داده بهروزرسانی شود،
- نام: نامی که باید به این مشتری داده شود؛
- خطوط ۵۷–۵۹: همین امر در مورد نخ دوم نیز صدق میکند. در نهایت، دو نخ تلاش خواهند کرد تا نام همان مشتری را در پایگاه داده تغییر دهند؛
- خطوط ۶۰–۶۳: پس از راهاندازی دو نخ، نخ اصلی منتظر پایان اجرای آنها میماند؛
- خط ۶۲: منتظر پایان اجرای نخ t1؛
- خط ۶۳: منتظر پایان اجرای نخ t2؛
- خط ۶۴: ما نمیدانیم که این دو نخ در چه ترتیبی به پایان میرسند. آنچه مسلم است این است که تا خط ۶۴ آنها کار خود را تمام کردهاند؛
- خطوط ۶۶–۷۲: در یک زمینهٔ جدید، برای بررسی وضعیت مشتری، از پایگاه داده پرسوجو میکنیم.
حال بیایید ببینیم که دو نخ t1 و t2 چه کاری انجام میدهند. آنها متد زیر را اجرا میکنند، [Modifie]:
static void Modifie(object infos)
{
// بازیابی پارامتر
Data data = (Data)infos;
try
{
using (var context = new RdvMedecinsContext())
{
Console.WriteLine("Début Thread {0}", Thread.CurrentThread.Name);
//بازیابی client1 از client2
Client client2 = context.Clients.Find(data.Client.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
// تغییر client2
client2.Nom = data.Nom;
// لحظهای صبر کنید
Thread.Sleep(data.Duree);
// ذخیرهٔ تغییرات
context.SaveChanges();
}
}
catch (Exception e)
{
// استثناء
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, e);
}
//پایان نخ
Console.WriteLine("Fin Thread {0}", Thread.CurrentThread.Name);
}
- خط ۴: پارامترهای نخ (Duration, Name, Client) بازیابی میشوند؛
- خط ۷: زمینه جدید؛
- خط ۱۱: کلاینت به کانکست پاس داده میشود؛
- خطوط ۱۲–۱۳: وضعیت مشتری را بررسی کنید؛
- خط ۱۵: نام مشتری تغییر میکند؛
- خط ۱۷: نخ برای Duree میلیثانیه مکث میکند. این اثر جالبی دارد. این نخ پردازندهای را که در حال اجرای آن بود آزاد میکند و راه را برای نخ دیگری باز میگذارد. در مثال ما، سه نخ داریم: main، t1 و t2. نخ main متوقف شده و منتظر پایان کار نخهای t1 و t2 است. با فرض اینکه نخ t1 ابتدا پردازنده را در اختیار دارد، اکنون آن را به نخ t2 واگذار میکند. اثر این کار آن است که نخ t2 دقیقاً همان دادههای نخ t1 را میخواند: همان مشتری با همان timestamp؛
- خط ۱۹: زمینه با پایگاه داده همگامسازی میشود. بیایید دوباره فرض کنیم که نخ t1 ابتدا بیدار میشود. این نخ مشتری را با نام «yy» ذخیره خواهد کرد. این کار را میتواند انجام دهد زیرا همان timestamp را که در پایگاه داده وجود دارد، در اختیار دارد. در نتیجه این بهروزرسانی، SGBD، timestamp را اصلاح خواهد کرد. وقتی نخ t2 به نوبه خود بیدار شود، مشتریای با timestamp خواهد داشت که با مورد موجود در پایگاه داده متفاوت است. بهروزرسانی آن رد خواهد شد.
نمایشهای صفحه به شرح زیر است:
- خط ۴: کلاینت در پایگاه داده؛
- خط ۹: کلاینتی که توسط نخ t2 خوانده شده است؛
- خط ۱۱: مشتری خواندهشده توسط نخ t1. بنابراین هر دو نخ همان دادهها را خواندهاند؛
- خط ۱۲: نخ t2 زودتر به پایان میرسد. بنابراین توانست بهروزرسانی خود را انجام دهد. نام باید به «zz» تغییر کرده باشد؛
- خط ۱۳: تار t1 یک استثنا از نوع [System.Data.OptimisticConcurrencyException] پرتاب میکند. EF تشخیص داد که timestamp صحیح را ندارد؛
- خط ۲۱: رشته t1 نیز خاتمه مییابد؛
- خط ۲۲: نخ اصلی منتظر ماندن را پایان داده است؛
- خط ۲۴: نخ اصلی مشتری را از پایگاه داده نمایش میدهد. در واقع نخ t2 برنده شده است. نام آن «zz» است. توجه کنید که timestamp تغییر کرده است.
اکنون، بیایید جنبهٔ دیگری را بررسی کنیم: تراکنشی که همگامسازی زمینهٔ پایداری با پایگاه داده را مدیریت میکند.
3.5.9. همگامسازی در یک تراکنش
جدول [CRENEAUX] دارای یک قید یکتایی است که ما بهصورت دستی اضافه کردهایم (به بخش 2.2.4، صفحه 12 مراجعه کنید):
ما به این صورت پیش خواهیم رفت: دو قرار ملاقات را همزمان برای یک پزشک، در یک روز و در یک بازه زمانی یکسان اضافه خواهیم کرد. ببینیم چه اتفاقی میافتد.
پروژه به شرح زیر در حال پیشرفت است:
![]() | ![]() |
کد برنامه [SynchronisationTransaction] به شرح زیر است:
using System;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// برنامه آزمایشی
class SynchronisationTransaction
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// خالی کردن پایگاه دادهٔ جاری
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
context.SaveChanges();
}
// ایجاد یک کلاینت
Client client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
// ایجاد یک پزشک
Medecin medecin1 = new Medecin { Nom = "xx", Prenom = "xx", Titre = "xx" };
// ایجاد یک نوبت ملاقات برای این پزشک
Creneau creneau1 = new Creneau { Hdebut = 8, Mdebut = 20, Hfin = 8, Mfin = 40, Medecin = medecin1 };
// برای این پزشک و این بیمار، در یک روز و در یک بازه زمانی یکسان، دو نوبت ایجاد کنید
Rv rv1 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
Rv rv2 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
try
{
// ما همه این اشیاء را در زمینه پایداری قرار میدهیم
using (var context = new RdvMedecinsContext())
{
context.Clients.Add(client1);
context.Creneaux.Add(creneau1);
context.Medecins.Add(medecin1);
context.Rvs.Add(rv1);
context.Rvs.Add(rv2);
// ما زمینه را ذخیره میکنیم – باید یک استثنا دریافت کنیم
// زیرا BD زیربنایی محدودیت یکتایی دارد که مانع میشود
// دو ورودی RDV در یک روز، در همان بازه زمانی
context.SaveChanges();
}
}
catch (Exception e)
{
Console.WriteLine("Erreur : {0}", e);
}
// اگر ذخیره در داخل یک تراکنش انجام شود، نباید هیچ چیزی در پایگاه داده درج شده باشد
// به دلیل استثنای قبلی – ما بررسی میکنیم
using (var context = new RdvMedecinsContext())
{
// مشتریان
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// پزشکان
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// فواصل زمانی
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// قرارها
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
- خطوط ۱۵–۲۷: از یک زمینه پایداری برای خالی کردن پایگاه داده استفاده میشود؛
- خط ۳۰: ایجاد یک شیء [Client]؛
- خط ۳۲: ایجاد یک شیء [Medecin];
- خط ۳۴: ایجاد یک شیء [Creneau];
- خط ۳۶: ایجاد یک شیء [Rv];
- خط ۳۷: ایجاد یک شیء دوم [Rv] یکسان با مورد قبلی؛
- خط ۴۱: باز کردن یک زمینهٔ جدید؛
- خطوط ۴۳–۴۷: اشیاء ایجادشده قبلاً به زمینهٔ جدید متصل میشوند. توجه کنید که با در نظر گرفتن وابستگیها میتوانستیم تعداد عملیات Add را به حداقل برسانیم. با این حال، EF دستورات SQL و INSERT را که باید به پایگاه داده ارسال شوند، بهینهسازی خواهد کرد؛
- خط ۵۱: زمینه با پایگاه داده همگامسازی میشود. همانطور که در نظر (comment) اشاره شده است، درج یکی از دو قرار ملاقات باید به دلیل محدودیت یکتایی در جدول [RVS] ناموفق باشد. اما فراتر از آن، اگر همگامسازی در داخل یک تراکنش انجام شود، همه چیز باید برگشت داده شود. بنابراین، هیچ درجکردنی نباید انجام شود. پایگاه داده باید خالی باقی بماند؛
- خط ۵۳: زمینه بسته میشود؛
- خطوط ۶۱–۹۰: نمایش محتویات پایگاه داده. باید خالی باشد.
خروجی صفحه به شرح زیر است:
- خط ۱: استثنا به دلیل نقض محدودیت یکتایی در جدول [RVS];
- خطوط ۹–۱۲: پایگاه داده در واقع خالی است. بنابراین، زمینه در داخل یک تراکنش با پایگاه داده همگامسازی شد.
بدون شک جنبههای دیگری نیز برای بررسی در EF 5 وجود دارد. با این حال، اکنون دانش کافی برای بازگشت به مطالعه معماری چندلایه خود را داریم. در ابتدای این سند، خواننده ارجاعاتی به مقالات و کتابهایی خواهد یافت که به او امکان میدهد دانش خود را در مورد EF 5 تعمیق بخشد.
3.6. مطالعهٔ معماری چندلایه مبتنی بر EF 5
به مطالعه موردی خود که در پاراگراف ۲ توصیف شده است بازمیگردیم. این یک برنامه وب ASP.NET است که به شرح زیر ساختار یافته است:
![]() |
ما با ساخت لایه دسترسی به داده [DAO] شروع خواهیم کرد. این لایه بر اساس EF5 خواهد بود.
3.6.1. پروژه جدید
ما یک پروژه کنسول جدید، VS 2012 [RdvMedecins-SqlServer-02]، را در داخل راهحل فعلی، [1]، ایجاد میکنیم:
![]() |
ما چهار پوشه به نامهای [2] را اضافه میکنیم که در آنها کد خود را سازماندهی خواهیم کرد. پوشه [Entites] کپیای از پوشه [Entites] پروژه قبلی است. پس از کپی این فایل، خطاهایی ظاهر میشوند زیرا ارجاعات صحیح را نداریم. ما باید یک ارجاع به Entity Framework 5 اضافه کنیم. برای این کار، روش توضیحدادهشده در بخش 3.4، صفحه 21 را دنبال خواهیم کرد. فهرست ارجاعات به شکل زیر درمیآید: [3]:
![]() |
در این مرحله، پروژه دیگر نباید هیچ خطای کامپایلی داشته باشد. ما همچنین فایل [App.config] را که اتصال به پایگاه داده را پیکربندی میکند، از پروژه قبلی کپی میکنیم:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!--برای اطلاعات بیشتر درباره پیکربندی Entity Framework به http://go.microsoft.com/fwlink/?LinkID=237468 مراجعه کنید. -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- رشته اتصال به پایگاه داده -->
<connectionStrings>
<add name="monContexte"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- ارائهدهندهٔ فابریک -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
3.6.2. کلاس Exception
ما از یک کلاس استثنای مخصوص پروژه استفاده خواهیم کرد. این کلاسی است که توسط لایه [DAO] بازگردانده میشود:
![]() |
لایه [DAO] تمام استثناهایی را که تا آن ارسال میشوند، میگیرد و آنها را در یک استثنا از نوع [RdvMedecinsException] بستهبندی میکند. این استثنا به شکل زیر خواهد بود:
using System;
namespace RdvMedecins.Exceptions
{
public class RdvMedecinsException : Exception
{
// ویژگیها
public int Code { get; set; }
// سازندهها
public RdvMedecinsException()
: base()
{
}
public RdvMedecinsException(string message)
: base(message)
{
}
public RdvMedecinsException(int code, string message)
: base(message)
{
Code = code;
}
public RdvMedecinsException(int code, string message, Exception ex)
: base(message, ex)
{
Code = code;
}
// هویت
public override string ToString()
{
if (InnerException == null)
{
return string.Format("RdvMedecinsException[{0},{1}]", Code, base.Message);
}
else
{
return string.Format("RdvMedecinsException[{0},{1},{2}]", Code, base.Message, base.InnerException.Message);
}
}
}
}
- خط ۵: این کلاس از کلاس [Exception] ارث میبرد؛
- خط ۹: یک کد خطا به کلاس پایه خود اضافه میکند؛
- خطوط ۱۲–۳۲: سازندههای مختلف فیلد [Code] را گنجاندهاند.
پروژه به شرح زیر تکامل مییابد:
![]() |
3.6.3. لایه [DAO]
![]() |
لایه [DAO] رابطی را برای لایه [ASP.NET] فراهم میکند. برای شناسایی لایه دوم، باید به صفحات وب برنامه نگاه کرد:
![]() |
- در [1] بالا، لیست کشویی با فهرست پزشکان پر شده است. لایه [DAO] این فهرست را فراهم میکند؛
- در [2]، لایه [DAO] ارائه خواهد کرد؛
- فهرست نوبتهای یک پزشک برای یک روز معین،
- فهرست بازههای زمانی آزاد یک پزشک،
- اطلاعات اضافی دربارهٔ پزشک انتخابشده؛
![]() |
- در [3]، فهرست کشویی مشتریان توسط لایه [DAO] ارائه خواهد شد؛
![]() |
- در [4]، کاربر یک قرار ملاقات را تأیید میکند. لایه [DAO] باید قادر به افزودن این مورد به پایگاه داده باشد. همچنین باید بتواند اطلاعات بیشتری در مورد مشتری انتخابشده ارائه دهد؛
![]() |
- در 5، کاربر یک قرار ملاقات را حذف میکند. لایه [DAO] باید این کار را مجاز بداند.
با این اطلاعات، رابط [IDao] لایه [DAO] میتواند به شکل زیر باشد:
using System;
using System.Collections.Generic;
using RdvMedecins.Entites;
namespace RdvMedecins.Dao
{
public interface IDao
{
// فهرست مشتریان
List<Client> GetAllClients();
// فهرست پزشکان
List<Medecin> GetAllMedecins();
//فهرست شکافهای نوبتدهی پزشک
List<Creneau> GetCreneauxMedecin(int idMedecin);
// فهرست RV برای یک پزشک معین در یک روز معین
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// افزودن یک RV
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// حذف یک RV
void SupprimerRv(int idRv);
//یافتن یک موجودیت T بر اساس کلید اصلی آن
T Find<T>(int id) where T : class;
}
}
متدهای خطوط ۱۰ تا ۲۰ از تحلیل انجامشده استخراج شدهاند. متد در خط ۲۲ برای رسیدگی به این واقعیت است که ما با بارگذاری تنبل (lazy loading) کار میکنیم. اگر در لایه [ASP.NET] به وابستگی به یک انتیت (entity) نیاز داشته باشیم، با استفاده از این متد آن را از پایگاه داده بازیابی خواهیم کرد.
پیادهسازی این رابط [Dao] به شرح زیر خواهد بود:
using System;
using System.Collections.Generic;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using RdvMedecins.Models;
namespace RdvMedecins.Dao
{
public class Dao : IDao
{
//فهرست مشتریان
public List<Client> GetAllClients()
{
// فهرست مشتریان
List<Client> clients = null;
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
// فهرست مشتریان
clients = context.Clients.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(1, "GetAllClients", ex);
}
// بازگرداندن نتیجه
return clients;
}
// فهرست پزشکان
public List<Medecin> GetAllMedecins()
{
// فهرست پزشکان
List<Medecin> medecins = null;
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
// فهرست پزشکان
medecins = context.Medecins.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(2, "GetAllMedecins", ex);
}
// نتیجه را بازمیگرداند
return medecins;
}
// فهرست اسلاتهای قرار ملاقات برای یک پزشک معین
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
...
}
//فهرست RV یک پزشک برای یک روز معین
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
...
}
// افزودن یک RV
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
...
}
// حذف یک RV
public void SupprimerRv(int idRv)
{
...
}
//پیدا کردن یک مشتری
public Client FindClient(int id)
{
...
}
// پیدا کردن یک بازه زمانی ملاقات
public Creneau FindCreneau(int id)
{
...
}
// پیدا کردن پزشک
public Medecin FindMedecin(int id)
{
....
}
// پیدا کردن وقت ملاقات
public Rv FindRv(int id){
...
}
}
}
بیایید متد [GetAllClients] را توضیح دهیم که هدف آن بازگرداندن فهرستی از تمام مشتریان است:
- خطوط ۱۸–۳۱: جستجو برای مشتریان در داخل یک بلوک try/catch انجام میشود. همین امر برای تمام متدهای بعدی نیز صدق میکند؛
- خط ۲۱: یک زمینهٔ جدید باز میشود؛
- خط ۲۴: اشیاء [Client] در زمینه بارگذاری شده و در یک لیست قرار میگیرند.
متد [GetAllMedecins] که هدف آن بازگرداندن فهرستی از همه پزشکان است، مشابه است (خطوط ۳۷–۵۷).
متد [GetCreneauxMedecin] به شرح زیر است:
//فهرست بازههای زمانی ملاقات برای پزشک مشخص
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
//فهرست اسلاتهای قرار ملاقات
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
//بازیابی پزشک و اسلاتهای قرار ملاقات او
Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
//فهرست نوبتهای دکتر
return medecin.Creneaux.ToList<Creneau>();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
}
}
- خط ۹: باز کردن یک زمینه پایداری جدید؛
- خط ۱۱: پزشک مورد نظر که کلید اصلی او مشخص است، بازیابی میشود. درخواستی برای شامل کردن وابستگی [Creneaux] ارسال میشود که مجموعهای از اسلاتهای قرار ملاقات پزشک است. اگر پزشک وجود نداشته باشد، متد Single یک استثنا پرتاب میکند؛
- خط ۱۳: لیست اسلاتهای قرار ملاقات بازگردانده میشود.
متد [GetRvMedecinJour] باید لیست قرارهای ملاقات یک پزشک را برای یک روز مشخص بازگرداند. کد آن میتواند به صورت زیر باشد:
//فهرست RV یک پزشک برای یک روز معین
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
// فهرست قرار ملاقاتها
List<Rv> rvs = null;
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
//بازیابی پزشک
Medecin medecin = context.Medecins.Find(idMedecin);
if (medecin == null)
{
throw new RdvMedecinsException(10, string.Format("Médecin [{0}] inexistant", idMedecin));
}
// فهرست قرار ملاقاتها
rvs = context.Rvs.Where(r => r.Creneau.Medecin.Id == idMedecin && r.Jour == jour).ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(4, "GetRvMedecinJour", ex);
}
// بازگرداندن نتیجه
return rvs;
}
- خط ۱۳: پزشک با کلید اصلی شناختهشده از زمینه بازیابی میشود؛
- خطوط ۱۴–۱۷: اگر پزشک وجود نداشته باشد، یک استثنا پرتاب میشود؛
- خط ۱۹: پرسوجوی LINQ برای بازیابی قرار ملاقاتهای این پزشک اجرا میشود؛
متد [AjouterRv] باید یک قرار ملاقات را به پایگاه داده اضافه کرده و کلید اصلی رکورد درجشده را بازگرداند. کد آن میتواند به صورت زیر باشد:
// افزودن یک RV
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
// شماره قرار ملاقات اضافه شد
int idRv;
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
// بازیابی بازه زمانی
Creneau creneau = context.Creneaux.Find(idCreneau);
if (creneau == null)
{
throw new RdvMedecinsException(5, string.Format("Créneau [{0}] inexistant", idCreneau));
}
// بازیابی مشتری
Client client = context.Clients.Find(idClient);
if (client == null)
{
throw new RdvMedecinsException(6, string.Format("Client [{0}] inexistant", idCreneau));
}
// اسلات ایجاد شد
Rv rv = new Rv { Jour = jour, Client = client, Creneau = creneau };
// افزودهشده به زمینه
context.Rvs.Add(rv);
// ذخیره کردن زمینه
context.SaveChanges();
//بازیابی کلید اصلی قرار ملاقات اضافه شده
idRv = (int)rv.Id;
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(7, "AjouterRv", ex);
}
// نتیجه
return idRv;
}
- خط ۱۲: جستجوی شکاف قرار ملاقات در پایگاه داده؛
- خطوط ۱۳–۱۶: اگر پیدا نشود، یک استثنا پرتاب میشود؛
- خط ۱۸: جستجوی مشتری قرار ملاقات در پایگاه داده؛
- خطوط ۱۹–۲۲: اگر پیدا نشود، یک استثنا پرتاب میشود؛
- خط ۲۴: یک شیء [Rv] با اطلاعات لازم ایجاد میشود؛
- خط ۲۶: آن را به زمینه پایداری اضافه میکنیم؛
- خط ۲۸: ما زمینه پایداری را با پایگاه داده همگامسازی میکنیم. سپس قرار ملاقات در پایگاه داده ذخیره خواهد شد؛
- خط ۳۰: میدانیم که پس از همگامسازی با پایگاه داده، کلیدهای اصلی آیتمهای درجشده در دسترس هستند. ما کلید اصلی قرار ملاقات اضافه شده را بازیابی میکنیم؛
- خط ۳۱: بستن زمینه پایداری.
متد [SupprimerRv] باید یک قرار ملاقات را که کلید اصلی آن به آن پاس شده است، حذف کند.
// حذف یک RV
public void SupprimerRv(int idRv)
{
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
//بازیابی RV
Rv rv = context.Rvs.Find(idRv);
if (rv == null)
{
throw new RdvMedecinsException(5, string.Format("Rv [{0}] inexistant", idRv));
}
// حذف Rv
context.Rvs.Remove(rv);
// ذخیرهٔ زمینه
context.SaveChanges();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(8, "SupprimerRv", ex);
}
}
- خط ۷: زمینه پایداری جدید؛
- خط ۱۰: قرار ملاقاتی که باید حذف شود به کانکست اضافه میشود؛
- خطوط ۱۱–۱۵: اگر وجود نداشته باشد، یک استثنا پرتاب میشود؛
- خط ۱۶: قرار ملاقات از زمینه حذف میشود؛
- خط ۱۸: همگامسازی زمینه با پایگاه داده؛
- خط ۱۹: زمینه بسته میشود.
متد [Find<T>] به شما امکان میدهد تا در پایگاه داده به دنبال یک موجودیت از نوع T با استفاده از کلید اصلی آن بگردید. کد مربوط به این کار ممکن است به شکل زیر باشد:
public T Find<T>(int id) where T : class
{
try
{
// باز کردن زمینه پایداری
using (var context = new RdvMedecinsContext())
{
return context.Set<T>().Find(id);
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(20, "Find<T>", ex);
}
}
- خط ۸: متد Set<T> یک DbSet<T> را بازیابی میکند که میتوان روشهای معمول را روی آن اعمال کرد.
پروژه به شرح زیر توسعه مییابد:
![]() |
3.6.4. آزمایش لایه [DAO]
ما قصد داریم یک برنامهٔ آزمایشی برای لایهٔ [DAO] ایجاد کنیم. معماری آزمایش به شرح زیر خواهد بود:
![]() |
یک برنامه کنسول به [Spring.net] دستور میدهد تا لایه [DAO] را نمونهسازی کند. پس از انجام این کار، قابلیتهای مختلف رابط لایه [DAO] را آزمایش میکند. به جای یک برنامه کنسول، ترجیح داشت که یک برنامه تست از نوع NUnit نوشته شود. یک برنامه تست برای لایه [DAO] ممکن است به این شکل باشد:
using System;
using System.Collections.Generic;
using RdvMedecins.Dao;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using Spring.Context.Support;
namespace RdvMedecins.Tests
{
class Program
{
public static void Main()
{
IDao dao = null;
try
{
// ایجاد لایه [DAO] از طریق Spring
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
//نمایش مشتریان
List<Client> clients = dao.GetAllClients();
DisplayClients("Liste des clients :", clients);
// نمایش پزشکان
List<Medecin> medecins = dao.GetAllMedecins();
DisplayMedecins("Liste des médecins :", medecins);
// فهرست شکافهای زمانی نوبت برای پزشک شماره ۰
List<Creneau> creneaux = dao.GetCreneauxMedecin((int)medecins[0].Id);
DisplayCreneaux(string.Format("Liste des créneaux horaires du médecin {0}", medecins[0]), creneaux);
//فهرست قرارهای ملاقات پزشک برای یک روز معین
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// افزودن یک RV به دکتر شمارهٔ ۱ در شکاف شمارهٔ ۰
Console.WriteLine(string.Format("Ajout d'un RV au médecin {0} avec client {1} le 23/11/2013", medecins[0], clients[0]));
int idRv1 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// افزودن قرار ملاقات به یک اسلات از پیش اشغالشده – باید یک استثنا ایجاد کند
int idRv2;
Console.WriteLine("Ajout d'un RV dans un créneau déjà occupé");
try
{
idRv2 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (RdvMedecinsException ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
// حذف یک قرار ملاقات
Console.WriteLine(string.Format("Suppression du RV n° {0}", idRv1));
dao.SupprimerRv(idRv1);
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
//مکث
Console.ReadLine();
}
// متدهای ابزار – نمایش لیستها
public static void DisplayClients(string Message, List<Client> clients)
{
Console.WriteLine(Message);
foreach (Client c in clients)
{
Console.WriteLine(c.ShortIdentity());
}
}
public static void DisplayMedecins(string Message, List<Medecin> medecins)
{
...
}
public static void DisplayCreneaux(string Message, List<Creneau> creneaux)
{
...
}
public static void DisplayRvs(string Message, List<Rv> rvs)
{
...
}
}
}
- خط ۱۴: مرجع به لایه [DAO]. برای مستقل کردن تست از پیادهسازی واقعی این لایه، این مرجع از نوع رابط [IDao] و نه از نوع کلاس [Dao] است؛
- خط ۱۸: لایه [DAO] توسط Spring نمونه سازی میشود. به پیکربندی مورد نیاز برای این کار باز خواهیم گشت. ما ارجاع شیء بازگشتی از Spring را به ارجاعی از نوع رابط [IDao] تبدیل میکنیم؛
- خطوط ۲۱–۲۲: نمایش مشتریان؛
- خطوط ۲۵–۲۶: نمایش بیماران؛
- خطوط ۲۹–۳۰: نمایش لیست نوبتها برای دکتر شماره ۰؛
- خط ۳۳: نمایش قرارهای ملاقات دکتر شماره ۰ برای ۲۳/۱۱/۲۰۱۳. قرار است هیچ قرار ملاقاتی وجود نداشته باشد؛
- خط ۳۷: یک قرار ملاقات برای دکتر شماره ۰ در تاریخ ۲۳/۱۱/۲۰۱۳ اضافه میکند؛
- خط ۳۹: نمایش قرارهای دکتر شماره ۰ برای ۲۳/۱۱/۲۰۱۳. باید یک قرار وجود داشته باشد؛
- خط ۴۶: همان قرار ملاقات برای بار دوم اضافه میشود. یک خطا باید رخ دهد؛
- خط ۵۷: قرار ملاقات تکی که اضافه شده بود را حذف میکند؛
- خط ۵۸: قرارهای دکتر شماره ۰ در تاریخ ۲۳/۱۱/۲۰۱۳ را نمایش میدهد. نباید هیچ قرار وجود داشته باشد.
3.6.5. پیکربندی Spring.net
در برنامه آزمایشی بالا، ما به طور خلاصه به دستوری اشاره کردیم که لایه [DAO] را نمونهسازی میکند:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
کلاس [ContextRegistry] یک کلاس Spring در فضای نام [Spring.Context.Support] است. برای استفاده از Spring، باید وابستگی DLL آن را به پروژه اضافه کنیم. این کار را به شرح زیر انجام میدهیم:
![]() |
- در [1]، با استفاده از ابزار [NuGet]، به دنبال پکیجها میگردیم؛
![]() |
- برای [2]، بهصورت آنلاین به دنبال بستهها بگردید؛
- در [3]، کلمه کلیدی spring را در کادر جستجو وارد کنید؛
- در [4]، بستههایی که توضیحاتشان حاوی این کلمهٔ کلیدی است نمایش داده میشوند. در اینجا، [Spring.Core] همان بستهای است که به آن نیاز داریم. آن را نصب میکنیم.
اشارههای پروژه به شرح زیر تغییر میکنند:
![]() |
پکیج [Spring.Core] به پکیج [Common.Logging] وابسته بود. این پکیج نیز بارگذاری شده است. در این مرحله، پروژه دیگر نباید هیچ خطایی داشته باشد.
با این حال، این به معنای کار کردن آن نیست. ابتدا باید Spring را در فایل [App.config] پیکربندی کنیم. این پیچیدهترین بخش پروژه است. فایل جدید [App.config] به شرح زیر است:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- برای اطلاعات بیشتر درباره پیکربندی Entity Framework، به http://go.microsoft.com/fwlink/?LinkID=237468 مراجعه کنید -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- اسپرینگ -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- لاگگیری مشترک-->
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<!-- Entity Framework -->
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
<!-- رشتههای اتصال -->
<connectionStrings>
<add name="monContexte" connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>
<!-- پیکربندی Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!--پیکربندی common.logging -->
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</configuration>
بیایید با حذف همه چیزهایی که از قبل مشخص هستند شروع کنیم: Entity Framework، رشتههای اتصال، ProviderFactory. فایل اکنون به این شکل است:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- برای اطلاعات بیشتر در مورد پیکربندی Entity Framework، به http://go.microsoft.com/fwlink/?LinkID=237468 مراجعه کنید. -->
<section name="entityFramework" ... />
<!-- اسپرینگ -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- لاگگیری مشترک-->
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
...
<!-- پیکربندی Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!-- پیکربندی common.logging -->
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
</configuration>
- خطوط ۳–۱۵: بخشهای پیکربندی را تعریف میکنند؛
- خط ۸: کلاسی را تعریف میکند که بخش <spring><context> فایل XML (خطوط ۱۹–۲۱) را مدیریت خواهد کرد؛
- خط ۹: کلاسی را تعریف میکند که بخش <spring><objects> فایل XML (خطوط ۲۲–۲۴) را مدیریت خواهد کرد؛
- خط ۱۳: کلاسی را تعریف میکند که بخش <common><logging> فایل XML (خطوط ۲۷–۳۶) را مدیریت خواهد کرد؛
- خطوط ۷–۱۴: ثابت هستند. نیازی به تغییر آنها در پروژه دیگری نیست؛
- خطوط ۱۸–۲۵: پیکربندی Spring. این بخش پایدار است به جز خطوط ۲۲–۲۴ که اشیایی را که Spring آنها را نمونهسازی میکند، تعریف میکنند؛
- خط ۲۳: تعریف یک شیء. ویژگی id دلخواهی است. این شناسهی شیء است. ویژگی type کلاس مورد نظر را به صورت «نام کامل کلاس، اسمبلی حاوی کلاس» مشخص میکند. کلاس اینجا همان کلاسی است که لایه [DAO] را پیادهسازی میکند: [RdvMedecins.Dao.Dao]. برای یافتن اسمبلی آن، ویژگیهای پروژه را بررسی کنید:
![]() |
در [1]، نام اسمبلی که باید ارائه شود؛
- خطوط ۲۷–۳۶: پیکربندی «لاگگیری مشترک» پایدار است. ممکن است لازم باشد سطح لاگگیری را در خط ۳۲ تغییر دهید. پس از مرحله عیبیابی، میتوانید سطح را روی INFO تنظیم کنید.
در نهایت، اگرچه ممکن است در نگاه اول پیچیده به نظر برسد، فایل پیکربندی Spring ساده است. تنها تغییرات مورد نیاز عبارتند از:
- خطوط ۲۲–۲۴، که اشیایی را که باید نمونهسازی شوند، تعریف میکنند؛
- خط ۳۲: سطح لاگ.
در برنامه آزمایشی، دستوری که لایه [DAO] را نمونهسازی میکند به شرح زیر است:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
[ContextRegistry] یک کلاس Spring است که از پیکربندی Spring تعریفشده در فایلی به نام [Web.config] یا [App.config] استفاده میکند. در این مورد، از بخش زیر از فایل [App.config] استفاده خواهد کرد:
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
- ContextRegistry.GetContext() از زمینهٔ خطوط ۲ تا ۴ استفاده میکند. خط ۳ نشان میدهد که اشیاء Spring در بخش [spring/objects] فایل پیکربندی تعریف شدهاند. این بخش شامل خطوط ۵ تا ۷ است؛
- ContextRegistry.GetContext().GetObject("rdvmedecinsDao") از بخشی که شامل خطوط ۵–۷ است استفاده میکند. این یک مرجع به شیء با ویژگی id="rdvmedecinsDao" را بازمیگرداند. این همان شیء تعریفشده در خط ۶ است. سپس اسپرینگ کلاس تعریفشده توسط ویژگی type را با استفاده از سازنده بدون پارامتر آن نمونهسازی میکند. بنابراین این سازنده باید وجود داشته باشد. پس از انجام این کار، مرجع شیء ایجادشده به کد فراخوانیکننده بازگردانده میشود. اگر شیء برای بار دوم در کد درخواست شود، اسپرینگ به سادگی مرجعی به اولین شیء ایجاد شده را بازمیگرداند. این الگوی طراحی است که به عنوان «سینگلتون» شناخته میشود.
ایجاد شی میتواند پیچیدهتر باشد. شما میتوانید از یک سازنده با پارامتر استفاده کنید یا مقداردهی اولیه برخی از فیلدهای شی را پس از ایجاد آن مشخص کنید. برای اطلاعات بیشتر در این زمینه، لطفاً به مقاله «Spring Tutorial IOC for .NET» مراجعه کنید که در URL [http://tahe.developpez.com/dotnet/springioc/] موجود است.
پس از انجام این کار، میتوانیم برنامه را اجرا کنیم. خروجی صفحه به شرح زیر است:
نتایج همانطور که انتظار میرفت است. اکنون لایه [DAO] خود را معتبر میدانیم. آموزش میتوانست همینجا به پایان برسد. تا اینجا، موارد زیر را پوشش دادهایم:
- مبانی Entity Framework 5؛
- یک لایه [DAO] که از این ORM استفاده میکند.
بیایید مطالعه موردی خود را که در ابتدای این سند توضیح داده شد، به یاد بیاوریم. ما با یک برنامه موجود با معماری زیر شروع میکنیم:
![]() |
که میخواهیم آن را به این شکل تبدیل کنیم:
![]() |
که در آن EF5 جایگزین NHibernate شده است. ما به تازگی لایه [DAO2] را ساختهایم. در واقع، این لایه همان رابط [DAO1] را ندارد، که رابط محدودتری داشت:
public interface IDao
{
// فهرست مشتریان
List<Client> GetAllClients();
// فهرست پزشکان
List<Medecin> GetAllMedecins();
// فهرست شکافهای نوبتدهی یک پزشک
List<Creneau> GetCreneauxMedecin(int idMedecin);
//فهرست RV برای یک پزشک در یک روز معین
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// افزودن یک RV
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// حذف یک RV
void SupprimerRv(int idRv);
}
لایه [DAO2] روش زیر را به این رابط اضافه کرد:
// پیدا کردن یک موجودیت T بر اساس کلید اصلی آن
T Find<T>(int id) where T : class;
این متد اضافه شده است زیرا لایه ORM EF 5 به طور پیشفرض در حالت بارگذاری تنبل (Lazy Loading) عمل میکند. اِنتیتها بدون وابستگیهای خود به لایه [ASP.NET] میرسند. روش فوق به ما این امکان را میدهد که در صورت نیاز آنها را بازیابی کنیم، و در برخی موارد به آنها نیاز داریم. NHibernate نیز به طور پیشفرض در حالت بارگذاری تنبل (Lazy Loading) عمل میکند، اما من از آن در حالت بارگذاری مشتاق (Eager Loading) استفاده میکردم. انتیتها به همراه وابستگیهایشان به لایه [ASP.NET] میرسیدند.
ما در حال تکمیل انتقال اپلیکیشن ASP.NET / NHibernate به اپلیکیشن ASP.NET / EF 5 هستیم. با این حال، از آنجایی که این موضوع دیگر مربوط به EF5 نیست، ما در مورد کد وب نظری نخواهیم داد. ما به سادگی نحوه راهاندازی و آزمایش برنامه وب را توضیح خواهیم داد. این مطلب در وبسایت این آموزش در دسترس است.
3.6.6. تولید DLL از لایه [DAO]
در معماری زیر:
![]() |
لایه [ASP.NET] به لایههای سمت راست خود به شکل DLL دسترسی خواهد داشت. بنابراین ما DLL را از لایه [DAO] میسازیم.
![]() |
- در [1]، ما برنامه آزمایشی را انتخاب میکنیم، در حالی که در [2] آن را در DLL که قرار است تولید شود، شامل نمیکنیم؛
- در [3]، در ویژگیهای پروژه، مشخص کنید که اسمبلیای که باید ایجاد شود DLL است؛
- در [4]، در منوی مربوط به VS، مشخص شده است که یک اسمبلی از نوع [Release] تولید خواهد شد، که اطلاعات کمتری نسبت به یک اسمبلی از نوع [Debug] دارد؛
![]() |
- در 5، اسمبلی پروژه مجدداً تولید میشود. اسمبلی DLL تولید خواهد شد؛
- در [6]، ما تمام فایلهای پروژه را نمایش میدهیم؛
![]() |
- در [7]، DLL برای پروژه روی لایه [DAO] ایجاد میشود. این همان چیزی است که پروژه وب ASP.NET از آن استفاده خواهد کرد؛
- در [8]، نمای پروژه را تازه میکنیم؛
![]() |
- در [9]، فایلهای DLL از پوشه [Release] در یک پوشه خارجی [lib] جمعآوری میشوند که خود این پوشه در داخل [10] قرار دارد. اینجا جایی است که پروژه وب مراجع خود را از آن بازیابی میکند.
3.6.7. لایه [ASP.NET]
در اینجا توضیح میدهیم که چگونه برنامه [ASP.NET / NHibernate] را به برنامه [ASP.NET / EF 5] منتقل کنیم. ما با Visual Studio Express 2012 for the Web کار خواهیم کرد که بهصورت رایگان در URL [http://www.microsoft.com/visualstudio/fra/downloads] در دسترس است.
ما از پروژه وب موجودی که با VS 2010 ایجاد شده است، کار خواهیم کرد.
![]() |
- در [1]، پروژهٔ موجود را باز کنید:
- در [2]، پروژهٔ بارگذاریشده دارای ارجاعات زیر است: [3]:
- [NHibernate] مربوط به DLL از فریمورک NHibernate است،
- [Spring.Core] مربوط به DLL از فریمورک Spring.net است،
- [log4net] معادل DLL در چارچوب لاگگیری log4net است. این چارچوب توسط Spring.net استفاده میشود،
- [MySql.Data] درایور ADO.NET برای SGBD و MySQL است،
- [rdvmedecins]، DLLِ لایه [DAO] است که با NHibernate ساخته شده است؛
- در [4]، نام پروژه را تغییر میدهیم و در 5، ارجاعات قبلی را حذف میکنیم؛
![]() |
- در [6]، ما ارجاعات را به پروژه اضافه میکنیم؛
- در [7]، در ویزارد از گزینه [Parcourir] استفاده میکنیم؛
![]() |
- در [8]، ما تمام فایلهای DLL را از پروژه شماره ۲ که قبلاً در پوشه [lib] قرار داده شده بودند، انتخاب میکنیم؛
- در [9]، خلاصهای که ما تأیید میکنیم؛
- [10]، پروژه وب با مراجع جدیدش.
پس از انجام این کار، پروژه به شکل زیر ساختاردهی میشود:
![]() |
- در [1]، کد مدیریت صفحات وب بین دو فایل [Global.asax] و [Default.aspx] تقسیم شده است. کدهای ابزار در پوشه [Entites] قرار داده شدهاند. در نهایت، برنامه توسط فایل [Web.config] پیکربندی میشود؛
- در [2]، مونتاژ پروژه را تولید میکنیم؛
- در [3]، خطاهایی ظاهر میشوند.
بیایید خطاها را بررسی کنیم، برای مثال خطای زیر:
![]()
و توضیح آن:
![]()
آیا نوع [medecin.Id] 'int' است؟ در حالی که متد [GetCreneauxMedecin] از نوع 'int' است. بنابراین یک cast لازم است. این خطا در سراسر کد تکرار میشود زیرا اشیاء در ASP.NET / NHibernate دارای کلیدهای اصلی از نوع int بود، در حالی که کلیدهای پروژه ASP.NET / EF 5 از نوع int? هستند. ما تمام خطاهای از این نوع را اصلاح کرده و پروژه را مجدداً تولید میکنیم. در این صورت دیگر خطایی وجود نخواهد داشت.
یک جزئیات نهایی باقی مانده است که قبل از اجرای پروژه باید حل شود: نمونهسازی لایه [DAO] توسط فریمورک Spring. این کار در [Global.asax] انجام میشود:
protected void Application_Start(object sender, EventArgs e)
{
// کش کردن برخی دادهها از پایگاه داده
try
{
//نمونهسازی لایه [dao]
Dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
...
}
catch (Exception ex)
{...
}
}
در برنامه تست لایه [DAO]، این لایه لایه [DAO] را به شرح زیر نمونه سازی کرد:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
این دو روش یکسان هستند. شایان ذکر است که این نمونهسازی لایه [DAO] بر اساس پیکربندی انجامشده در [App.config] بود. سپس محتوای فعلی [Web.config] پروژه وب را با محتوای [App.config] از پروژهای در لایه [DAO] جایگزین میکنیم تا پیکربندی یکسانی داشته باشیم.
اکنون برای اولین اجرا آمادهایم. صفحهٔ اصلی به صورت [1] نمایش داده میشود:
![]() |
- در [2]، تاریخ قرار ملاقات را وارد کرده و تأیید کنید؛
![]() |
- در [3]، خطایی رخ میدهد.
با بررسی پیام خطای نمایشدادهشده در صفحه، مشخص میشود که استثنای گزارششده به «بارگذاری تنبل» (Lazy Loading) مربوط است: تلاشی برای بارگذاری یک وابستگی از یک شیء انجام شده در حالی که زمینه پایداری (persistence context) که آن را مدیریت میکرد، بسته شده بود. این شیء اکنون در وضعیت «جداشده» قرار دارد. این خطا به این دلیل رخ میدهد که NHibernate در حالت بارگذاری مشتاق (Eager Loading) استفاده شده است، در حالی که EF بهطور پیشفرض در حالت بارگذاری تنبل (Lazy Loading) عمل میکند. در خطی که در بالا به رنگ قرمز برجسته شده است:
- rdv نشاندهندهی یک شی [Rv] است که بدون وابستگیهایش بارگذاری شده است؛
- برای ارزیابی rdv.Creneau.Id، برنامه تلاش میکند وابستگی rdv.Creneau را بارگذاری کند. با این حال، از آنجایی که دیگر در آن زمینه قرار نداریم، این کار امکانپذیر نیست و در نتیجه این خطای استثنایی (exception) رخ میدهد.
در اینجا، راهحل ساده است. در خط ۱۰۸، ما با استفاده از کلید اصلی بازه زمانی یک قرار ملاقات بهعنوان کلید، یک ورودی در یک دیکشنری ایجاد میکنیم. از قضا، انتیت [Rv] کلید اصلی بازه زمانی مربوطه را در خود جای داده است. بنابراین مینویسیم:
dicoRvPris[(int)rdv.CreneauId] = rdv;
ما کد را دوباره اجرا میکنیم. این بار، خطا به شرح زیر است:
![]() |
خطا مشابه است. در خط ۱۳۲، تلاشی برای بارگذاری وابستگی [Client] از یک شی [Rv] به لایه ASP.NET انجام میشود، که بنابراین خارج از زمینه است. شیء [Client] باید از پایگاه داده بازیابی شود. برای رفع این مشکل، رابط [IDao] با روش زیر گسترش یافته است:
// یافتن یک انتیت T از طریق کلید اصلی آن
T Find<T>(int id) where T : class;
این امر امکان بازیابی وابستگیها را فراهم میکند. در نتیجه، خط نادرست نشاندادهشده در بالا به صورت زیر بازنویسی خواهد شد:
Client client = Global.Dao.Find<Client>(agenda.Creneaux[i].Rdv.ClientId);
بار دیگر، شایان ذکر است که درج کلیدهای خارجی در انتیتها مزایایی دارد. در اینجا، انتیت [Rv] به ما دسترسی به کلید خارجی وابستگی مرتبط، یعنی [Creneau]، را میدهد. با انجام این دو اصلاح، برنامه کار میکند. از خوانندگان دعوت میشود تا اپلیکیشن [RdvMedecins-SqlServer-03] را که در بخش دانلودهای نمونه وبسایت این مقاله در دسترس است، آزمایش کنند.
3.7. Conclusion
ما با موفقیت اپلیکیشن ASP.NET / NHibernate را منتقل کردهایم:
![]() |
به یک برنامه ASP.NET / EF 5:
![]() |
در حالی که این معماری باید به ما امکان میداد لایه [ASP.NET] را دستنخورده نگه داریم، ما مجبور شدیم آن را به دو دلیل تغییر دهیم:
- اشیاء دقیقاً یکسان نبودند. نوع کلید اصلی برای اشیاء NHibernate
intبود، در حالی که برای EF 5</span>**<span style="color: #000000">int?</span>**<span style="color: #000000">بود. این امر ما را مجبور کرد تا cast را در کد وب وارد کنیم؛ - حالت بارگذاری برای دو موجودیت ORM یکسان نبود: بارگذاری مشتاق (Eager Loading) برای NHibernate و بارگذاری تنبل (Lazy Loading) برای EF 5. این امر ما را بر آن داشت تا رابط لایه [DAO] را با یک متد عمومی که امکان بازیابی یک انتیت از طریق کلید اصلی آن را فراهم میکند، بهبود بخشیم.
با این حال، این پورتینگ نسبتاً ساده بود و بار دیگر معماری لایهای و تزریق وابستگی با استفاده از Spring یا هر چارچوب تزریق وابستگی دیگری را – اگر نیازی به اثبات بود – توجیه کرد.
اکنون تأثیر تغییر در SGBD را بر معماری قبلی ارزیابی خواهیم کرد. ما تمام پروژههای قبلی را به چهار نمونه دیگر از SGBD منتقل خواهیم کرد:
- نسخهٔ اکسپرس دیتابیس اوراکل ۱۱g نسخهٔ ۲؛
- MySQL 5.5.28;
- PostgreSQL 9.2.1;
- فایربرد ۲.۱.
کدها دیگر تغییر نخواهند کرد. تنها عناصر زیر تغییر خواهند کرد:
- تعریف در اجزا، فیلدی که برای کنترل دسترسی همزمان به یک جزء استفاده میشود؛
- فایلهای پیکربندی [App.config] یا [Web.config]؛
ما تنها در مورد عناصری که در حال تغییر هستند، توضیح خواهیم داد.


















































































































































