مطالب
NHibernate و سطح اول cache آن

این روزها هیچکدام از فناوری‌های دسترسی به داده بدون امکان یکپارچگی آن‌ها با سیستم‌ها و روش‌های متفاوت 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
اگر شخصی با مکانیزم سطح اول caching در NHibernate آشنایی نداشته باشد، شاید ساعاتی را در انجمن‌های مرتبط، جهت یافتن روش حل خطای فوق سپری کند.
همانطور که عنوان شد، در طول یک سشن، نمی‌توان دو شیء با یک 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!
}
در این حالت سیستم دو مزیت عمده را تجربه خواهد کرد: سرعت بالای ثبت اطلاعات با تعداد زیاد رکورد و همچنین مصرف پایین حافظه از آنجائیکه یک IStatelessSession ارجاعی را به اشیایی که بارگذاری می‌کند، در خود نگهداری نخواهد کرد.
تنها باید به خاطر داشت که در این حالت lazy loading پشتیبانی نمی‌شود و همچنین رخدادهای درونی NHibernate نیز لغو خواهند شد.

مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت هشتم - بررسی عملگرهای Merge Join و Sort در یک Query Plan
در یک merge join، اطلاعات از دو ورودی مرتب شده، دریافت و join می‌شوند. اگر این ورودی‌ها از پیش مرتب شده نباشند (دارای ایندکس مناسبی نباشند)، یک عملگر Sort در این میان تزریق خواهد شد. عملگر Sort نیز اندکی متفاوت است از سایر عملگرها. این عملگر یک iterator نیست (یعنی ردیف به ردیف عمل نمی‌کند) و اگر اطلاعاتی وارد آن شد، ابتدا باید کل آن مرتب شود و سپس به قسمت‌های بعدی ارسال گردد؛ که مصرف حافظه و I/O زیادی را به همراه دارد. به همین جهت جزو مواردی است که باید در یک کوئری پلن، بیشتر به آن دقت داشت.


بررسی عملگر merge join

 ابتدا در management studio از منوی Query، گزینه‌ی Include actual execution plan را انتخاب می‌کنیم. سپس کوئری‌های زیر را اجرا می‌کنیم:
USE [WideWorldImporters];
GO

SET STATISTICS IO ON;
GO

SELECT
    [p].[PurchaseOrderID],
    [pl].[PurchaseOrderLineID]
FROM [Purchasing].[PurchaseOrders] [p]
    JOIN [Purchasing].[PurchaseOrderLines] [pl]
    ON [p].[PurchaseOrderID] = [pl].[PurchaseOrderID];
GO
در اینجا اطلاعات دو جدول PurchaseOrders و PurchaseOrderLines بر روی ستون PurchaseOrderID با هم Join شده‌اند و اجرای آن یک چنین کوئری پلنی را تولید می‌کند:


در اینجا یک merge join انجام شده، چون اطلاعات رسیده‌ی به آن، از پیش مرتب شده‌است. از این جهت که جدول PurchaseOrders دارای یک clustered index تعریف شده‌ی بر روی PurchaseOrderID است:
ALTER TABLE [Purchasing].[PurchaseOrders] ADD  CONSTRAINT [PK_Purchasing_PurchaseOrders] PRIMARY KEY CLUSTERED
(
   [PurchaseOrderID] ASC
)
و همچنین جدول PurchaseOrderLines نیز دارای یک non-clustered index تعریف شده‌ی بر روی PurchaseOrderID است:
CREATE NONCLUSTERED INDEX [FK_Purchasing_PurchaseOrderLines_PurchaseOrderID] ON [Purchasing].[PurchaseOrderLines]
(
    [PurchaseOrderID] ASC
)
چون این دو ایندکس پیش‌فرض، اطلاعات از پیش مرتب شده‌ای را بر اساس PurchaseOrderID دارند، قابلیت تغذیه‌ی merge join را خواهند داشت.

اما بهینه سازی کوئری‌های SQL Server، همیشه در یک چنین شرایطی، از merge join استفاده نمی‌کند. برای مثال کوئری زیر نیز دقیقا از لحاظ تعریف ایندکس بر روی OrderID، وضعیت مشابهی با کوئری قبلی دارد:
SELECT
    [o].[OrderID],
    [ol].[OrderLineID]
FROM [Sales].[Orders] [o]
    JOIN [Sales].[OrderLines] [ol]
    ON [o].[OrderID] = [ol].[OrderID];
GO
اما کوئری پلن آن به صورت زیر است:


اگر به میزان ضخامت پیکان‌های این پلن، با پلن قبلی دقت کنید، مشاهده می‌کنید که ضخامت آن‌ها در اینجا افزایش یافته‌است. این افزایش ضخامت پیکان‌ها، بیانگر افزایش میزان اطلاعات ارسالی به قسمت‌های مختلف است (حدود 231 هزار ردیف) به همراه اسکن بالایی بر روی ایندکس [FK_Sales_Orders_SalespersonPersonID] است (بر روی PersonID بجای OrderID) و دومی بر روی [NCCX_Sales_OrderLines]. چون ایندکس OrderID سنگین است و تعداد ردیف زیادی را شامل می‌شود، بهینه ساز ترجیح داده‌است تا از ایندکس دیگری استفاده کند که I/O کمتری را به همراه دارد. در این‌حالت دیگر merger join میسر نبوده و از hash match استفاده کرده‌است.

اگر OrderID انتخاب شده را از جدول OrderLines تهیه کنیم، چه اتفاقی رخ می‌دهد؟ (در کوئری قبلی، OrderID از جدول Orders انتخاب شده بود)
SELECT
    [ol].[OrderID],
    [ol].[OrderLineID]
FROM [Sales].[Orders] [o]
    JOIN [Sales].[OrderLines] [ol]
    ON [o].[OrderID] = [ol].[OrderID];
در این حالت به کوئری پلن زیر خواهیم رسید:


یک بازنویسی ساده و دریافت دو ستون از یک جدول سبب شده‌است تا بهینه سازی کوئری، join تشکیل شده را غیرضروری دانسته و مستقیم عمل کند.


اهمیت مرتب شده بودن اطلاعات در تشکیل Joinهای بهینه

کوئری زیر را در نظر بگیرید که در آن یک select * را داریم (که یک ضد الگو است):
SELECT *
FROM [Sales].[Orders] [o]
    JOIN [Sales].[OrderLines] [ol]
    ON [o].[OrderID] = [ol].[OrderID];
GO
اجرای آن چنین کوئری پلنی را تولید می‌کند:


جدول OrderLines دارای یک non-clustered index، فقط بر روی ستون OrderID است؛ اما با select * نوشته شده، تمام ستون‌های آن‌را درخواست کرده‌ایم (و نه فقط OrderID را)؛ به همین جهت اطلاعات آن پیش از ارسال به merge join باید توسط عملگر sort مرتب شود و همانطور که مشاهده می‌کنید، هزینه‌ی این عملگر در این پلن، 82 درصد کل است.


تاثیر order by بر روی کوئری پلن تشکیل شده

دو کوئری زیر را در نظر بگیرید که تفاوت دومی با اولی، در داشتن یک ORDER BY است:
SELECT TOP 1000
    *
FROM [Sales].[OrderLines];
GO

SELECT TOP 1000
    *
FROM [Sales].[OrderLines]
ORDER BY [Description];
GO
پس از اجرای این دو کوئری با هم، به کوئری پلن زیر خواهیم رسید:


اولی، تمام clustered index را اسکن نمی‌کند و جائیکه 1000 ردیف را از آن بازگشت می‌دهد، متوقف می‌شود.
اما در دومی چون نیاز به مرتب سازی اطلاعات بر اساس یک ستون بوده‌است، عملگر sort مشاهده می‌شود. اسکن آن نیز بر روی کل اطلاعات است (پیکان مرتبط با آن، نسبت به پلن قبلی ضخیم‌تر است) و سپس آن‌ها را مرتب می‌کند.

برای بهبود این وضعیت، تعداد ستون‌های بازگشت داده شده را محدود کرده و سپس بر اساس آن‌ها، ایندکس صحیحی را طراحی می‌کنیم:
بنابراین اینبار بجای select *، تعداد مشخصی از ستون‌ها را بازگشت می‌دهیم:
SELECT
    [CustomerID],
    [OrderDate],
    [ExpectedDeliveryDate]
FROM [Sales].[Orders]
ORDER BY [CustomerID];
GO
همچنین یک non-clustered index را بر روی CustomerID که دو ستون OrderDate و ExpectedDeliveryDate را include می‌کند، تعریف می‌کنیم:
CREATE NONCLUSTERED INDEX [IX_Sales_Orders_CustomerID_Dates]
ON [Sales].[Orders](
[CustomerID] ASC
)
INCLUDE (
[OrderDate], [ExpectedDeliveryDate]
)
ON [USERDATA];
GO
اکنون اگر کوئری جدید محدود شده را اجرا کنیم، به کوئری پلن زیر خواهیم رسید که در آن خبری از عملگر sort نیست؛ چون ایندکس جدید تعریف و استفاده شده، کار مرتب سازی را نیز انجام داده‌است:

نظرات اشتراک‌ها
تفاوت اعتبارسنجی ورودی ها با اعتبارسنجی مرتبط با قواعد تجاری
یک مطلب مرتبط
A next-level validation is domain validation, or as I've often seen referred, "business rule validation". This is more of a system state validation, "can I affect the change to my system based on the current state of my system". I might be checking the state of a single entity, a group of entities, an entire collection of entities, or the entire system. The key here is I'm not checking the request against itself, but against the system state.  

اشتراک‌ها
بررسی تغییرات ASP.NET Core در NET 8 Preview 7.

Here’s a summary of what’s new in this preview release:

Servers & middleware
  Antiforgery middleware
API authoring
  Antiforgery integration for minimal APIs
Native AOT
  Request Delegate Generator supports interceptors feature
  Full TrimMode is used for web projects compiled with trimming enabled
  WebApplication.CreateEmptyBuilder
Blazor
  Antiforgery integration
  Server-side form handling improvements
  Auto render mode
  Register root-level cascading values
  Improved integration of interactive components with server-side rendering
  New EmptyContent parameter for Virtualize
Identity
  New bearer token authentication handler
  New API endpoints
Single page apps (SPA)
  New Visual Studio templates
 

بررسی تغییرات ASP.NET Core در NET 8 Preview 7.
اشتراک‌ها
دوره‌ی کامل میکروسرویس‌ها در دات نت

.NET Microservices – Full Course, Les Jackson
In this step-by-step tutorial I take you through an introduction on building microservices using .NET. As the name suggests we build everything completely from start to finish –with the full scope of the course outlined in the time-stamp section below. However, at a high-level we’ll cover:

• Building two .NET Microservices using the REST API pattern
• Working with dedicated persistence layers for both services
• Deploying our services to Kubernetes cluster
• Employing the API Gateway pattern to route to our services
• Building Synchronous messaging between services (HTTP & gRPC)
• Building Asynchronous messaging between services using an Event Bus (RabbitMQ)

 

دوره‌ی کامل میکروسرویس‌ها در دات نت
اشتراک‌ها
dnSpy : ابزاری برای Debug و تغییر در DLL های دات نت

dnSpy is a debugger and .NET assembly editor. You can use it to edit and debug assemblies even if you don't have any source code available.

Some Features 

  • Debug .NET Framework, .NET Core and Unity game assemblies, no source code required
  • Set breakpoints and step into any assembly
  • Locals, watch, autos windows 
  • All metadata can be edited
  • Edit methods and classes in C# or Visual Basic with IntelliSense, no source code required
  • Add new methods, classes or members in C# or Visual Basic
  • IL editor for low level IL method body editing 

dnSpy : ابزاری برای Debug و تغییر در DLL های دات نت
اشتراک‌ها
C# در مرورگر با Blazor

 Blazor is the new Microsoft experimental framework that brings C# into any browser without a plug-in. It holds the promise of modern single-page applications, combined with the ability to use C# and its vast base-class library. Blazor takes C# development to a new level. It’s the final piece necessary to make the language a full-stack development tool. It will have all the power of the popular JavaScript frameworks, but based on the familiar languages, APIs and tooling of the Microsoft .NET Framework. 

C# در مرورگر با Blazor
اشتراک‌ها
نگاهی به آینده WebAssembly

The future of WebAssembly - A look at upcoming features and proposals

WebAssembly is a performance optimised virtual machine that was shipped in all four major browsers earlier this year. It is a nascent technology and the current version is very much an MVP (minimum viable product). This blog post takes a look at the WebAssembly roadmap and the features it might be gain in the near future.

I’ll try to keep this blog post relatively high-level, so I’ll skip over some of the more technical proposals, instead focusing on what they might mean for languages that target WebAssembly. 

نگاهی به آینده WebAssembly
اشتراک‌ها
زبان برنامه‌نویسی J یک زبان سطح بالا و پرتابل و دارای قابلیت Memory Map File

J (J language) is a high-level, general-purpose, high-performance programming language. J is portable and runs on 32/64-bit Windows/Linux/Mac as well as iOS, Android, and other platforms. J source (required only if Jsoftware binaries don't meet your requirements) is available under both commercial and GPL 3 license. J systems can be installed and distributed for free.

J systems have:

  • an integrated development environment
  • standard libraries, utilities, and packages
  • console, browser, and Qt front ends
  • interfaces with other programming languages and applications
  • integrated graphics
  • memory mapped files for high performance data applications
  • Jd 
زبان برنامه‌نویسی J  یک زبان سطح بالا و پرتابل و دارای قابلیت Memory Map File