نظرات مطالب
EF Code First #7
به ازای هر FK یک Collection مجزا باید داشته باشید و همچنین از Fluent API هم باید استفاده کنید:
public class User { public int UserId { get; set;} public string Name { get; set; } public virtual ICollection<Comment> HomeCommentes { get; set; } public virtual ICollection<Comment> AwayCommentes { get; set; } } public class Comment { public int CommentId { get; set; } public int HomeUserId { get; set; } public int GuestUserId { get; set; } public virtual User HomeUser { get; set; } public virtual User GuestUser { get; set; } } public class Context : DbContext { ... protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Comment>() .HasRequired(m => m.HomeUser) .WithMany(t => t.HomeCommentes) .HasForeignKey(m => m.HomeUserId) .WillCascadeOnDelete(false); modelBuilder.Entity<Comment>() .HasRequired(m => m.GuestUser) .WithMany(t => t.AwayCommentes) .HasForeignKey(m => m.GuestUserId) .WillCascadeOnDelete(false); } }
نظرات مطالب
بازنویسی سطح دوم کش برای Entity framework 6
public virtual void Load(IUnitOfWork unitOfWork) { //3 get setting for this type name var settings = unitOfWork.Settings.Where(w => w.Type == _name).Cacheable().ToList();
سلام. ممنون.
میشه بفرمائید برای مقادیر مالی به ریال و تومان بهترین نوع داده ای چیست؟
من اینچنین استفاده میکنم:
و در کانفیگ:
همین نوع و همین اندازه تنظیم کافیه؟ آیا تنظیم بیشتری نیاز دارد؟
میشه بفرمائید برای مقادیر مالی به ریال و تومان بهترین نوع داده ای چیست؟
من اینچنین استفاده میکنم:
public virtual decimal CostPrice { set; get; }
this.Property(x => x.CostPrice) .HasColumnType("money") .IsRequired();
نظرات مطالب
EF Code First #7
- در قسمت HasRequired که Username نباید تعریف شود. در اینجا یک سر دیگر رابطه باید معرفی گردد. همان روابط و کلاسهایی که به صورت virtual در کدها آمده. HasRequired با IsRequired متفاوت است.
+ حذف آبشاری به صورت پیش فرض فعال است (برای مواردی که کلید خارجی نال پذیر نیست). نیازی به فعال سازی دستی آن نیست.
+ حذف آبشاری به صورت پیش فرض فعال است (برای مواردی که کلید خارجی نال پذیر نیست). نیازی به فعال سازی دستی آن نیست.
دو سوال
1 - در ورژنی که بنده از NuGet نصب کردم ( 3.5.1 ) ، T4MVCExtensions هم نصب گردید ، کار T4MVCExtensions چیست ؟
2 - بعد از اجرا ، تمام کلاسهای Controllerهای پروژه من partial شدند و تمام Actionهای من virtual ، چرا ؟ آیا مشکلی در آینده برای Controllerها و Actionها پیش نخواهد آمد ؟
نظرات مطالب
EF Code First #7
سلام وحید جان ممنون از این همه لطف
من یک پروژه را با CodeFirst شروع کردم اما یه جایی اشتباه کردم فکر کنم اشتباهم توی یکی از Mappingها باشه. اگه لطف کنید ببینید مشکل چیه.بدون استفاده از Mapping مشکلی نیست و دیتا بیس با روابطی که میخوام ایجاد میشه اما وقتی از Mapping استفاده میکنم با این خطا مواجه میشم:
فکر کنم مشکل از این کلاس زیر باشه:
من یک پروژه را با CodeFirst شروع کردم اما یه جایی اشتباه کردم فکر کنم اشتباهم توی یکی از Mappingها باشه. اگه لطف کنید ببینید مشکل چیه.بدون استفاده از Mapping مشکلی نیست و دیتا بیس با روابطی که میخوام ایجاد میشه اما وقتی از Mapping استفاده میکنم با این خطا مواجه میشم:
{"Sequence contains more than one matching element"}
چندتا کلاسها به شکل زیر هست:public class Driver { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string NationalCode { get; set; } public string CellPhone { get; set; } public string LicenseNumber { get; set; } public bool IsDriverAssistance { get; set; } [InverseProperty("Driver")] public virtual ICollection<Transference> Transferences { get; set; } [InverseProperty("DriverAssistance")] public virtual ICollection<Transference> TransferencesForAssistance { get; set; } [InverseProperty("Driver")] public virtual ICollection<Tanker> Tankers { get; set; } [InverseProperty("DriverAssistance")] public virtual ICollection<Tanker> TankersForAssistance { get; set; } }
public class Transference { public string Id { get; set; } public DateTime Date { get; set; } public Int16 Lytrazh { get; set; } public bool IsEMS { get; set; } public DateTime LoadingDate { get; set; } public DateTime DeliveryDate { get; set; } [InverseProperty("Transferences")] public virtual Driver Driver { get; set; } [InverseProperty("TransferencesForAssistance")] public virtual Driver DriverAssistance { get; set; } public virtual TypeOfTanker TypesOfTanker { get; set; } public virtual Tanker Tanker { get; set; } public virtual Consumer Consumer { get; set; } }
public class TransferenceConfig : EntityTypeConfiguration<Transference> { public TransferenceConfig() { // one-to-many this.HasRequired(x => x.Consumer) .WithMany(x => x.Transferences); // one-to-many this.HasRequired(x => x.TypesOfTanker) .WithMany(x => x.Transferences); // one-to-many this.HasRequired(x => x.Tanker) .WithMany(x => x.Transferences); // one-to-many this.HasRequired(x => x.Driver) .WithMany(x => x.Transferences); // one-to-many this.HasRequired(x => x.DriverAssistance) .WithMany(x => x.Transferences); } }
یکی از امکانات Angular 7، ویژگی Virtual Scrolling میباشد. در صورتیکه شما قصد داشته باشید یک لیست بزرگ از المنتها را بارگذاری کنید، اینکار میتواند بر روی کارآیی برنامهی شما تاثیر بگذارد . تگ زیر
بعد از نصب با استفاده از ng version نسخهی Angular CLI را بررسی میکنیم که باید بزرگتر از 7 باشد:
بعد از نصب، دستور ng serve را اجرا میکنیم تا بررسی کنیم که برنامه به درستی اجرا میشود یا نه. سپس فایل app.module.ts را باز میکنیم و ScrollingModule را در بخش imports اضافه میکنیم. اکنون نیاز است تا یک آرایه را برای نمایش آیتمهای لیست، تولید کنیم. قطعه کد زیر در فایل app.component.ts قرار دارد که یک آرایه عددی را ایجاد میکند و تعدادی آیتم را به آن اضافه میکند:
<cdk-virtual-scroll-viewport></cdk-virtual-scroll-viewport>
می تواند برای بارگذاری تنها بخشهای قابل مشاهدهی از یک لیست، بر روی صفحه نمایش استفاده شود و همچنین تنها آیتمهایی Render خواهند شد که میتواند آنها را در صفحه نمایش جا دهد. اگر لیست بارگذاری شده را اسکرول کنیم، در این حالت المنتها در DOM به صورت پویا load و unload میشوند.
قبل از پیاده سازی ، لازم است Angular CLI را به آخرین نسخه بروز رسانی کنیم. برای بروز رسانی Angular CLI دستور زیر را اجرا میکنیم:
npm install -g @angular/cli
حالا نوبت به ایجاد یک پروژهی جدید میباشد. با استفاده از دستور زیر یک پروژه جدید ایجاد میشود:
ng new angular7-virtualScrolling
بعد از تایید دستور بالا، دو سؤال از شما پرسیده میشود؟
1- آیا قصد دارید Angular routing اضافه شود یا نه؟ ( در نسخههای قبلی با استفاده از routing-- این کار را انجام میدادیم)
2-انتخاب فرمت stylesheet که قصد استفادهی از آنرا دارید ( با کلیدهای جهتی بالا و پایین روی صحفه کلید میتوانید یکی از گزینهها را انتخاب کنید )
برای استفاده از Virtual Scrolling نیاز است پکیج زیر را نصب کنیم :
npm install @angular/cdk@latest
title = 'Angular 7 – Virtual Scrolling feature'; scrollItems: number[] = []; constructor() { for (let index = 0; index < 10000; index++) { this.scrollItems.push(index); } }
در فایل app.component.html قطعه کد زیر را قرار میدهیم:
<div> <h4> {{this.title}} </h4> <cdk-virtual-scroll-viewport itemSize="100"> <div *cdkVirtualFor="let n of scrollItems">Item {{n}}</div> </cdk-virtual-scroll-viewport> </div>
داخل تگ cdk-virtual-scroll-viewport، یک div را ایجاد و سپس یک دایرکتیو را به نام cdkVirtualFor* به آن اضافه میکنیم. این دایرکتیو، ngFor* را درون cdk-virtual-scroll-viewport، جایگزین میکند که شما با استفاده از آن میتوانید یک حلقه بر روی آرایه scrollItems جهت پیمایش ایجاد کنید.
تمام ! اکنون پروژه را اجرا کنید.
در اولین بار اجرا :
تمام ! اکنون پروژه را اجرا کنید.
در اولین بار اجرا :
بعد از اسکرول کردن لیست :
همانطور که مشاهده میکنیم المنتهای قبلی unload شدند و المنتهای جدید load شدند
بازخوردهای پروژهها
عدم authorization بر اساس Permissions
با سلام،
نقشهای کاربر لاگین شده (چطور میتوان Permissonهای کاربر را مشاهده کرد؟):
به دلیل اینکه فیلد Permissions به صورت XML ذخیره میشود باید تنظیمات خاصی را در web.config ست کرد؟
من برای پیاده سازی asp.net identity از کد شما استفاده کردم، اما با یک مشکل مواجه شدم که در پروژه شما این مشکل وجود ندارد.
کنترل دسترسی بر اساس Roles درست کار میکند هم با فیلتر Mvc5Authorize و هم با تابع IsInRole :
[Mvc5Authorize(StandardRoles.Administrators)] public virtual ActionResult Index(string type) { return new ElmahResult(type); } public virtual ActionResult Index() { if (User.IsInRole(StandardRoles.Administrators)) { this.NotyAlert("سلام مدیر2"); } return View(); }
اما بر اساس Permissions دسترسی وجود ندارد. در حالی که نقش Administrators ("مدیران") همه Permissionsها را دارد.
// HTTP Error 403.0 - Forbidden نمایش پیغام [Mvc5Authorize(AssignableToRolePermissions.CanAccessToSystemMaintenance)] public virtual ActionResult Index(string type) { return new ElmahResult(type); } public virtual ActionResult Index() { if (User.IsInRole(AssignableToRolePermissions.CanAccessToSystemMaintenance)) { // عدم اجرا و نمایش ییام this.NotyAlert("سلام مدیر1"); } return View(); }
نقشهای کاربر لاگین شده (چطور میتوان Permissonهای کاربر را مشاهده کرد؟):
به دلیل اینکه فیلد Permissions به صورت XML ذخیره میشود باید تنظیمات خاصی را در web.config ست کرد؟
لطفا راهنمایی بفرمایید مشکل از کجا میتواند باشد.
با تشکر،
معرفی الگوی Repository
روش متداول کار با فناوریهای مختلف دسترسی به دادهها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء
اگر در برنامههای یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را میتوانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به دادهها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوهی رفع مشکل، قسمت عمدهای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکندهاند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه میباشد.
و ...
همین برنامه نویس پس از مدتی کار به این نتیجه میرسد که باید برای اینکارهای متداول، یک لایه و کلاس دسترسی به دادهها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه میکند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل میگردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.
این روزها تشکیل این لایه دسترسی به دادهها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر میباشیم تا یک سری اعمال متداول با آنهارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.
الگوی Repository یکی از الگوهای برنامه نویسی با مقیاس سازمانی است. با کمک این الگو لایهای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه میکند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به دادهها (که در اینجا همان NHibernate میباشد) جلوگیری میشود. الگوی Repository (مخزن)، کار ثبت، حذف، جستجو و به روز رسانی دادهها را با ترجمه آنها به روشهای بومی مورد استفاده توسط ORM مورد نظر، کپسوله میکند. به این شکل شما میتوانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایهای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.
طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژههای متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاسهای دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئریهایی از نوع strongly typed را ترویج کند (مثل کوئریهایی از نوع LINQ).
بررسی مدل برنامه
مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمتهای قبل)، از نوع many-to-many میباشد. در اینجا یک واحد درسی توسط چندین دانشجو میتواند اخذ شود یا یک دانشجو میتواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاسهای متشکل آن به شکل زیر خواهند بود:
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }
public Course()
{
Students = new List<Student>();
}
}
}
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }
public Student()
{
Courses = new List<Course>();
}
}
}
کلاس کانفیگ برنامه جهت ایجاد نگاشتها و سپس ساخت دیتابیس متناظر
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}
public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آنرا به شکل زیر تنظیم میکنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>
ب) در NHibernate سنتی (!) کار ساخت نگاشتها توسط یک سری فایل xml صورت میگیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیتهای Auto Mapping آن، اینکار با سهولت و دقت هر چه تمامتر قابل انجام است که توضیحات نحوهی انجام آنرا در قسمتهای قبل مطالعه فرمودید. اگر نیاز بود تا این فایلهای XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشتها، فایلهای XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشهای که محل اجرای برنامه است خواهید یافت).
با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشتها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آنرا در شکل ذیل مشاهده مینمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آنها در جدولی سوم نگهداری میشود).
پیاده سازی الگوی مخزن
اینترفیس عمومی الگوی مخزن به شکل زیر میتواند باشد:
using System;
using System.Linq;
using System.Linq.Expressions;
namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);
T Save(T entity);
T Update(T entity);
void Delete(T entity);
IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}
سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنشها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی میکند :
using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;
namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;
public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}
~Repository()
{
Dispose(false);
}
public T Get(object key)
{
if (!isSessionSafe) return default(T);
return _session.Get<T>(key);
}
public T Save(T entity)
{
if (!isSessionSafe) return default(T);
_session.Save(entity);
return entity;
}
public T Update(T entity)
{
if (!isSessionSafe) return default(T);
_session.Update(entity);
return entity;
}
public void Delete(T entity)
{
if (!isSessionSafe) return;
_session.Delete(entity);
}
public IQueryable<T> Find()
{
if (!isSessionSafe) return null;
return _session.Linq<T>();
}
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;
return Find().Where(predicate);
}
void Commit()
{
if (!isSessionSafe) return;
if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}
void Rollback()
{
if (!isSessionSafe) return;
if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}
private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}
void BeginTransaction()
{
if (!isSessionSafe) return;
_session.BeginTransaction();
}
public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;
try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}
_disposed = true;
}
}
}
using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;
namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();
//ابتدا یک دانشجو را اضافه میکنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}
//سپس یک واحد را اضافه میکنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}
//اکنون یک واحد را به دانشجو انتساب میدهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}
//سپس شماره دروس استادی خاص را نمایش میدهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");
foreach (var course in query)
Console.WriteLine(course.Id);
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
همانطور که ملاحظه میکنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوهی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنشها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت میکند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن میتوان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.
دریافت سورس برنامه قسمت هشتم
ادامه دارد ...