این روزها هیچکدام از فناوریهای دسترسی به داده بدون امکان یکپارچگی آنها با سیستمها و روشهای متفاوت caching ، مطلوب شمرده نمیشوند. ایده اصلی caching هم به زبان ساده به این صورت است : فراهم آوردن روشهایی جهت میسر ساختن دسترسی سریعتر به دادههایی که به صورت متناوب در برنامه مورد استفاده قرار میگیرند، بجای مراجعه مستقیم به بانک اطلاعاتی و خواندن اطلاعات از دیسک سخت.
یکی از تفاوتهای مهم NHibernate با اکثر ORM های موجود داشتن دو سطح متفاوت cache است : first level cache & second level cache .
برای نمونه Entity framework (در زمان نگارش این مطلب) تنها first level caching را پشتیبانی میکند و پروایدر توکار و یکپارچهای را جهت second level caching ارائه نمیدهد.
در این قسمت قصد داریم First Level Cache را بررسی کنیم.
سطح اول caching در NHibernate چیست؟
سطح اول caching در تمام ORM هایی که آنرا پشتیبانی میکنند مانند NHibernate ، در طول عمر یک تراکنش تعریف میگردد. در این حالت در طی یک تراکنش و طول عمر یک سشن، دریافت اطلاعات هر رکورد از بانک اطلاعاتی، تنها یکبار انجام خواهد شد؛ صرفنظر از اینکه کوئری دریافت اطلاعات آن چندبار فراخوانی میگردد. یکی از دلایل این روش هم آن است که هیچ دو شیء متفاوتی که هم اکنون در حافظه قرار دارند نباید بیانگر یک رکورد واحد از بانک اطلاعاتی باشند.
در NHibernate به صورت پیش فرض هر زمانیکه از شیء استاندارد session استفاده میکنید، سطح اول caching نیز فعال است. درست در زمانیکه سشن خاتمه مییابد، این سطح از caching نیز به صورت خودکار تخلیه خواهد گردید.
به first level caching اصطلاحا thought-out cache system یا Cache Through pattern و یا identity map هم گفته میشود.
مثال:
روش متداول و استاندارد کار با NHibernate عموما به صورت زیر است:
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد.
د) سپس مجددا سعی در دریافت رکوردی با ID مساوی یک. اینبار اطلاعات این شیء مستقیما از cache خوانده میشود و رفت و برگشتی به بانک اطلاعاتی نخواهیم داشت. به همین جهت به این روش identity map هم گفته میشود، زیرا NHibernate بر اساس ID منحصربفرد این اشیاء ، identity map خود را تشکیل میدهد.
ه) خاتمهی سشن با فراخوانی متد Close آن
بلافاصله
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد (زیرا در یک سشن جدید قرار داریم و همچنین سشن قبلی بسته شده و کش آن تخلیه گشته است).
د) خاتمهی سشن با فراخوانی متد Close آن
سؤال: آیا استفاده از یک سشن سراسری در برنامه صحیح است؟
پاسخ: خیر!
توضیحات: زمانیکه از یک سشن سراسری استفاده میکنید، کش NHibernate را در اختیار تمام کاربران همزمان سیستم قرار دادهاید. در طی یک سشن، همانطور که عنوان شد، بر اساس IDهای اشیاء، یک identity map تشکیل میشود و در این حالت به ازای هر رکورد بانک اطلاعاتی فقط و فقط یک شیء در حافظه وجود خواهد داشت که این روش در محیطهای چندکاربره مانند برنامههای وب به زودی تبدیل به نشت اطلاعات و یا تخریب اطلاعات میگردد. به همین جهت در این نوع برنامهها روش session-per-request بهترین حالت کاری است.
سؤال: حین به روز رسانی اشیاء جدید، به خطا بر میخورم. مشکل در کجاست؟
فرض کنید شیء مفروض Customer را توسط متد session.Get از بانک اطلاعاتی دریافت و تعدادی از خواص آنرا جهت ساخت شیء جدیدی از کلاس Customer استفاده کردهایم. اکنون اگر بخواهیم این شیء جدید را در بانک اطلاعاتی ذخیره یا به روز رسانی کنیم، NHibernate این اجازه را نمیدهد! چرا؟
پاسخ:
خطای متداول این حالت عموما به صورت زیر است:
a different object with the same identifier value was already associated with the session
همانطور که عنوان شد، در طول یک سشن، نمیتوان دو شیء با یک ID را به عنوان یک رکورد بانک اطلاعاتی مورد استفاده قرار داد. اولین فراخوانی Get ، سبب کش شدن آن شیء در identity map سطح اول caching میگردد.
راه حل:
الف) از چندین و چند شیء استفاده نکنید. هر رکورد باید تنها با یک وهله از شیءایی متناظر باشد.
ب) میتوان پیش از update، کش سطح اول را به صورت دستی خالی کرد. برای این منظور از متد Clear شیء سشن استفاده کنید.
ج) بجای استفاده از متد saveOrUpdate شیء سشن، از متد Merge آن استفاده کنید. به این صورت شیء جدید ایجاد شده با شیء موجود در کش یکی خواهد شد.
د) میتوان بجای تخلیه کل کش (حالت ب)، کش مرتبط با شیء Customer را به صورت دستی خالی کرد. برای این منظور از متد Evict شیء سشن استفاده نمائید.
و لازم به ذکر است که متد Flush سبب تخلیه کش نمیگردد. کار این متد اعمال کلیه تغییرات اعمالی موجود در کش به بانک اطلاعاتی است و بیشتر جهت هماهنگ سازی این دو مورد استفاده قرار میگیرد.
سؤال: آیا میتوان سطح اول caching را غیرفعال کرد؟
پاسخ:بله.
توضیحات:
عموما کلیه ORMs جهت Batching یا Bulk data operations (برای مثال ثبت تعداد زیادی رکورد یا به روز رسانی تعداد بالایی از آنها، یا نمایش فقط خواندنی تعداد زیادی رکورد و گزارشگیری از آنها) کارآیی مطلوبی ندارند. نمونهای از آنرا در مبحث جاری ملاحظه کردهاید. هر شیءایی که به نحوی به سشن جاری وارد میشود تحت نظر قرار میگیرد و این مورد در تعداد بالای ثبت یا به روز رسانی رکوردها، یعنی کاهش سرعت و کارآیی، به علاوه مصرف بالای حافظه. به همین جهت باید به خاطر داشت که ORMs جهت سناریوهای OLTP مناسب هستند و کسانی که سرعت و کارآیی ORMs را با Batch processing اندازه گیری میکنند، کلا درکی از فلسفهی وجودی ORMs و ساختار درونی آنها ندارند!
خوشبختانه NHibernate با معرفی Stateless Sessions بر این مشکل فائق آمده است. در اینجا بجای ISession تنها کافی است از IStatelessSession استفاده گردد:
using (IStatelessSession statelessSession = sessionFactory.OpenStatelessSession())
using (ITransaction transaction = statelessSession.BeginTransaction())
{
//now insert 1,000,000 records!
}
تنها باید به خاطر داشت که در این حالت lazy loading پشتیبانی نمیشود و همچنین رخدادهای درونی NHibernate نیز لغو خواهند شد.
در این قسمت یک مثال ساده از insert ، load و delete را بر اساس اطلاعات قسمتهای قبل با هم مرور خواهیم کرد. برای سادگی کار از یک برنامه Console استفاده خواهد شد (هر چند مرسوم شده است که برای نوشتن آزمایشات از آزمونهای واحد بجای این نوع پروژهها استفاده شود). همچنین فرض هم بر این است که database schema برنامه را مطابق قسمت قبل در اس کیوال سرور ایجاد کرده اید (نکته آخر بحث قسمت سوم).
یک پروژه جدید از نوع کنسول را به solution برنامه (همان NHSample1 که در قسمتهای قبل ایجاد شد)، اضافه نمائید.
سپس ارجاعاتی را به اسمبلیهای زیر به آن اضافه کنید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHSample1.dll : در قسمتهای قبل تعاریف موجودیتها و نگاشت آنها را در این پروژه class library ایجاد کرده بودیم و اکنون قصد استفاده از آن را داریم.
اگر دیتابیس قسمت قبل را هنوز ایجاد نکردهاید، کلاس CDb را به برنامه افزوده و سپس متد CreateDb آنرا به برنامه اضافه نمائید.
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHSample1.Mappings;
namespace ConsoleTestApplication
{
class CDb
{
public static void CreateDb(IPersistenceConfigurer dbType)
{
var cfg = Fluently.Configure().Database(dbType);
PersistenceModel pm = new PersistenceModel();
pm.AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
var sessionSource = new SessionSource(
cfg.BuildConfiguration().Properties,
pm);
var session = sessionSource.CreateSession();
sessionSource.BuildSchema(session, true);
}
}
}
CDb.CreateDb(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql());
در ادامه یک کلاس جدید به نام Config را به برنامه کنسول ایجاد شده اضافه کنید:
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample1.Mappings;
namespace ConsoleTestApplication
{
class Config
{
public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
{
return
Fluently.Configure().Database(dbType
).Mappings(m => m.FluentMappings.AddFromAssembly(typeof(CustomerMapping).Assembly))
.BuildSessionFactory();
}
}
}
اکنون سورس کامل مثال برنامه را در نظر بگیرید:
کلاس CDbOperations جهت اعمال ثبت و حذف اطلاعات:
using System;
using NHibernate;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class CDbOperations
{
ISessionFactory _factory;
public CDbOperations(ISessionFactory factory)
{
_factory = factory;
}
public int AddNewCustomer()
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Customer vahid = new Customer()
{
FirstName = "Vahid",
LastName = "Nasiri",
AddressLine1 = "Addr1",
AddressLine2 = "Addr2",
PostalCode = "1234",
City = "Tehran",
CountryCode = "IR"
};
Console.WriteLine("Saving a customer...");
session.Save(vahid);
session.Flush();//چندین عملیات با هم و بعد
transaction.Commit();
return vahid.Id;
}
}
}
public void DeleteCustomer(int id)
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Customer customer = session.Load<Customer>(id);
Console.WriteLine("Id:{0}, Name: {1}", customer.Id, customer.FirstName);
Console.WriteLine("Deleting a customer...");
session.Delete(customer);
session.Flush();//چندین عملیات با هم و بعد
transaction.Commit();
}
}
}
}
}
using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class Program
{
static void Main(string[] args)
{
//CDb.CreateDb(SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql());
//return;
//todo: Read ConnectionString from app.config or web.config
using (ISessionFactory session = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
CDbOperations db = new CDbOperations(session);
int id = db.AddNewCustomer();
Console.WriteLine("Loading a customer and delete it...");
db.DeleteCustomer(id);
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
نیاز است تا ISessionFactory را برای ساخت سشنهای دسترسی به دیتابیس ذکر شده در تنظمیات آن جهت استفاده در تمام تردهای برنامه، ایجاد نمائیم. لازم به ذکر است که تا قبل از فراخوانی BuildSessionFactory این تنظیمات باید معرفی شده باشند و پس از آن دیگر اثری نخواهند داشت.
ایجاد شیء ISessionFactory هزینه بر است و گاهی بر اساس تعداد کلاسهایی که باید مپ شوند، ممکن است تا چند ثانیه به طول انجامد. به همین جهت نیاز است تا یکبار ایجاد شده و بارها مورد استفاده قرار گیرد. در برنامه به کرات از using استفاده شده تا اشیاء IDisposable را به صورت خودکار و حتمی، معدوم نماید.
بررسی متد AddNewCustomer :
در ابتدا یک سشن را از ISessionFactory موجود درخواست میکنیم. سپس یکی از بهترین تمرینهای کاری جهت کار با دیتابیسها ایجاد یک تراکنش جدید است تا اگر در حین اجرای کوئریها مشکلی در سیستم، سخت افزار و غیره پدید آمد، دیتابیسی ناهماهنگ حاصل نشود. زمانیکه از تراکنش استفاده شود، تا هنگامیکه دستور transaction.Commit آن با موفقیت به پایان نرسیده باشد، اطلاعاتی در دیتابیس تغییر نخواهد کرد و از این لحاظ استفاده از تراکنشها جزو الزامات یک برنامه اصولی است.
در ادامه یک وهله از شیء Customer را ایجاد کرده و آنرا مقدار دهی میکنیم (این شیء در قسمتهای قبل ایجاد گردید). سپس با استفاده از session.Save دستور ثبت را صادر کرده، اما تا زمانیکه transaction.Commit فراخوانی و به پایان نرسیده باشد، اطلاعاتی در دیتابیس ثبت نخواهد شد.
نیازی به ذکر سطر فلاش در این مثال نبود و NHibernate اینکار را به صورت خودکار انجام میدهد و فقط از این جهت عنوان گردید که اگر چندین عملیات را با هم معرفی کردید، استفاده از session.Flush سبب خواهد شد که رفت و برگشتها به دیتابیس حداقل شود و فقط یکبار صورت گیرد.
در پایان این متد، Id ثبت شده در دیتابیس بازگشت داده میشود.
چون در متد CreateSessionFactory ، متد ShowSql را نیز ذکر کرده بودیم، هنگام اجرای برنامه، عبارات SQL ایی که در پشت صحنه توسط NHibernate تولید میشوند را نیز میتوان مشاهده نمود:
بررسی متد DeleteCustomer :
ایجاد سشن و آغاز تراکنش آن همانند متد AddNewCustomer است. سپس در این سشن، یک شیء از نوع Customer با Id ایی مشخص load خواهد گردید. برای نمونه، نام این مشتری نیز در کنسول نمایش داده میشود. سپس این شیء مشخص و بارگذاری شده را به متد session.Delete ارسال کرده و پس از فراخوانی transaction.Commit ، این مشتری از دیتابیس حذف میشود.
برای نمونه خروجی SQL پشت صحنه این عملیات که توسط NHibernate مدیریت میشود، به صورت زیر است:
Saving a customer...
NHibernate: select next_hi from hibernate_unique_key with (updlock, rowlock)
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 17, @p1 = 16
NHibernate: INSERT INTO [Customer] (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 16016
Loading a customer and delete it...
NHibernate: SELECT customer0_.Id as Id2_0_, customer0_.FirstName as FirstName2_0_, customer0_.LastName as LastName2_0_, customer0_.AddressLine1 as AddressL4_2_0_, customer0_.AddressLine2 as AddressL5_2_0_, customer0_.PostalCode as PostalCode2_0_, customer0_.City as City2_0_, customer0_.CountryCode as CountryC8_2_0_ FROM [Customer] customer0_ WHERE customer0_.Id=@p0;@p0 = 16016
Id:16016, Name: Vahid
Deleting a customer...
NHibernate: DELETE FROM [Customer] WHERE Id = @p0;@p0 = 16016
Press a key...
فرض کنید از هفته آینده قرار شده است که نسخه سبک و تک کاربرهای از برنامه ما تهیه شود. بدیهی است SQL server برای این منظور انتخاب مناسبی نیست (هزینه بالا برای یک مشتری، مشکلات نصب، مشکلات نگهداری و امثال آن برای یک کاربر نهایی و نه یک سازمان بزرگ که حتما ادمینی برای این مسایل در نظر گرفته میشود).
اکنون چه باید کرد؟ باید برنامه را از صفر بازنویسی کرد یا قسمت دسترسی به دادههای آنرا کلا مورد باز بینی قرار داد؟ اگر برنامه اسپاگتی ما اصلا لایه دسترسی به دادهها را نداشت چه؟! همه جای برنامه پر است از SqlCommand و Open و Close ! و عملا استفاده از یک دیتابیس دیگر یعنی باز نویسی کل برنامه.
همانطور که ملاحظه میکنید، زمانیکه با NHibernate کار شود، مدیریت لایه دسترسی به دادهها به این فریم ورک محول میشود و اکنون برای استفاده از دیتابیس SQLite تنها باید تغییرات زیر صورت گیرد:
ابتدا ارجاعی را به اسمبلی System.Data.SQLite.dll اضافه نمائید (تمام این اسمبلیهای ذکر شده به همراه مجموعه FluentNHibernate ارائه میشوند). سپس:
الف) ایجاد یک دیتابیس خام بر اساس کلاسهای domain و mapping تعریف شده در قسمتهای قبل به صورت خودکار
CDb.CreateDb(SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql());
//todo: Read ConnectionString from app.config or web.config
using (ISessionFactory session = Config.CreateSessionFactory(
SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql()
))
{
...
دریافت سورس برنامه تا این قسمت
نکته:
در سه قسمت قبل، تمام خواص پابلیک کلاسهای پوشه domain را به صورت معمولی و متداول معرفی کردیم. اگر نیاز به lazy loading در برنامه وجود داشت، باید تمامی کلاسها را ویرایش کرده و واژه کلیدی virtual را به کلیه خواص پابلیک آنها اضافه کرد. علت هم این است که برای عملیات lazy loading ، فریم ورک NHibernate باید یک سری پروکسی را به صورت خودکار جهت کلاسهای برنامه ایجاد نماید و برای این امر نیاز است تا بتواند این خواص را تحریف (override) کند. به همین جهت باید آنها را به صورت virtual تعریف کرد. همچنین تمام سطرهای Not.LazyLoad نیز باید حذف شوند.
ادامه دارد ...
موقع ویرایش کاربر خطا داره
بهبودهای کارآیی LINQ در داتنت 9
ساخت برنامه های تحت وب با Blazor
Today we released our first public preview of Blazor , a new experimental .NET web framework using C#/Razor and HTML that runs in the browser with WebAssembly . Blazor enables full stack web development with the stability, consistency, and productivity of .NET.
.NET Core + Angular Dashboard
Topics Covered:
- Building a dashboard application in Angular
- Building a Web API in .NET Core 2.0
- Using Chart.js to build stunning charts of different types
- Making HTTP requests using Angular to query a Web API
- Using Postman to send requests
- Working with Observables
- Using Input and Output decorators in Angular
- Using PostgreSQL and pgAdmin
- Automatically seeding a database with large amounts of sample data
- Styling an application using custom CSS and Bootstrap 4
- Using Map, Filter, and Reduce in Javascript
- Creating Routes in Angular
- Get, Put, Post, Patch Web API Controller Action request types
- Configuring your API for CORS
مروری بر .net core توسط اسکات هانتر
There has never been a better time to be a .NET developer, you can now build Android, iOS, Linux, Mac, and Windows applications with .NET all in Open Source. In this session we will run through some of the new innovations including the .NET Framework updates, .NET Standard, Universal Windows Platform updates, .NET Core, managed languages and more. We will also review the updates to Visual Studio and Visual Studio Code to make you a better developer, come see some of the latest productivity features in these tools including managing code style, searching your source and more.