- فعال کردن خاصیت Contained Databases
قبل از استفاده از Contained Databases می بایست این امکان را فعال کرد. برای این کار میتوانید از SQL Server Management Studio یا T-SQL commands استفاده نمایید. بر روی نام Instance راست کلیت کنید و گزینه Properties را انتخاب نمایید. از گزینه Advanced که در شکل زیر مشاهده مینمایید خاصیت Enable Contained Databases را بر روی True قرار دهید.
یا میتوایند از sp_configure این کار را انجام دهید.دستورات زیر این موضوع را نشان میدهد.
sp_configure 'show advanced options',1 GO RECONFIGURE WITH OVERRIDE GO sp_configure 'contained database authentication',1 GO RECONFIGURE WITH OVERRIDE GO
- ایجاد یا تغییر یک پایگاه داده از نوع Contained Databases
برای ایجاد یک پایگاه داده با این خاصیت یا تغییر پایگاه داده موجود کافیست مقدار گزینه Containment type را بر روی Partial قرار دهید. برای پایگاه داده موجود از پنجره Properties پایگاه داده صفحه Options را انتخاب کنید.
- ایجاد یک کاربر برای پایگاه داده Contained Databases
برای تعریف یک کاربر در سطح پایگاه داده پوشه Security پایگاه داده خود را باز کنید بر روی پوشه Users راست کیلک و گزینه New User را انتخاب نمایید از گزینه User type که در شکل زیر نشان داده شده است SQL user with password را انتخاب نمایید و نام کاربر و رمز عبور و تکرار آن را وارد نمایید. کاربر ایجاد شده در سطح پایگاه داده میباشد و با انتقال به سرور دیگر نیر قابل دسترسی میباشد.
- اتصال به پایگاه داده Contained Databases
برای اتصال به پایگاه داده کافیست در حالت SQL Server Authentication نام کاربری و رمز عبور جدید را وارد و گزینه Options را انتخاب و از برگه Additional Connection Parameters نام پایگاه داده مورد نظر را مانند شکل زیر وارد نمایید پس از ورود تنها پایگاه داده خود را مشاهده مینمایید. یکی از کاربرهای این قابلیت برای مدیران سرور پایگاه داده میباشد که بدون استفاده از مجوز sysadmin به کاربران اجازه دسترسی را میدهد.
<span class='button goodDetail' id='123' >جزییات کالا</span>
$('.goodDetail').click(function(){ //add to hash data that you need to make the AJAX request later $(window).location.hash = $(this).attr('id'); $.ajax({ type: "POST", url: 'some url', dataType: "html", data:'GoodId='+$(this).attr('id'), success: function(html) { //do something } }) })
$(document).ready(function(){ if ($(window).location.hash.length)) { //we will use $(window).location.hash.replace('#','') in the "data" section of the AJAX request $.ajax({ type: "POST", url: current_url, dataType: "html", data:'GoodId='+$(window).location.hash.replace('#',''), success: function(html) { //do something } }) } });
این سه نوع استثناء شامل موارد DbEntityValidationException، DbUpdateConcurrencyException و DbUpdateException هستند که به صورت خلاصه به شکل زیر باید تعریف شوند:
try { context.SaveChanges(); } catch (DbEntityValidationException validationException) { //... } catch (DbUpdateConcurrencyException concurrencyException) { //... } catch (DbUpdateException updateException) { //... }
توضیحات تکمیلی
در حالت DbEntityValidationException به جزئیات خطاهای حاصل از اعتبار سنجی اطلاعات خواهیم رسید. برای مثال اگر قرار است طول فیلدی 30 حرف باشد و کاربر 40 حرف را وارد کرده است، نام خاصیت و همچنین پیغام خطای درنظر گرفته شده را دقیقا در اینجا میتوان دریافت کرد و به نحو مقتضی به کاربر نمایش داد:
catch (DbEntityValidationException validationException) { foreach (var error in validationException.EntityValidationErrors) { var entry = error.Entry; foreach (var err in error.ValidationErrors) { Debug.WriteLine(err.PropertyName + " " + err.ErrorMessage); } } }
نوع استثنای DbUpdateConcurrencyException به مسایل همزمانی و به روز رسانی یک رکورد توسط دو یا چند کاربر در شبکه مرتبط میشود که در قسمت سوم سری EF code first با معرفی ویژگیهای ConcurrencyCheck و Timestamp در مورد آن بحث شد. در اینجا به کلیه موجودیتهای تداخل دار توسط خاصیت concurrencyException.Entries خواهیم رسید و همچنین به کمک متد GetDatabaseValues میتوان موارد جدید ثبت شده مرتبط با این موجودیت تداخل دار را از بانک اطلاعاتی نیز دریافت کرد:
catch (DbUpdateConcurrencyException concurrencyException) { //بررسی مورد اول var dbEntityEntry = concurrencyException.Entries.First(); var dbPropertyValues = dbEntityEntry.GetDatabaseValues(); }
و یا کلا ممکن است حین به روز رسانی بانک اطلاعاتی مشکلی رخ داده باشد که در اینجا عموما پیغام حاصل را باید در InnerException تولیدی یافت و همچنین در اینجا لیست موجودیتهای مشکل دار نیز قابل دریافت و بررسی هستند:
catch (DbUpdateException updateException) { if (updateException.InnerException != null) Debug.WriteLine(updateException.InnerException.Message); foreach (var entry in updateException.Entries) { Debug.WriteLine(entry.Entity); } }
بنابراین بررسی catch exception کلی در EF Code first مناسب نبوده و نیاز است بیشتر به جزئیات ذکر شده، وارد و دقیق شد.
یک نکته:
بهتر است یک کلاس پایه عمومی مشتق شده از DbContext را ایجاد و متد SaveChanges آن را تحریف کرد. سپس سه حالت فوق را به آن اعمال نمود. اکنون میتوان از این کلاس پایه بارها استفاده کرد بدون اینکه نیازی به تکرار کدهای آن در هرجایی که قرار است از متد SaveChanges استفاده شود، باشد. شبیه به این کار را در قسمت 14 سری EF code first مشاهده نمودهاید.
پیشنیاز : «تکرار خودکار سرستونهای یک جدول در صفحات مختلف، توسط iTextSharp»
همانطور که در مطلب پیشنیاز عنوان شده ذکر گردید، iTextSharp امکان درج خودکار header و footer به علاوه محاسبه خودکار تعداد ردیفهای یک جدول در یک صفحه را بر اساس طول و اندازه محتوای هر ردیف، دارد. برای مثال یک صفحه ممکن است 2 ردیف شود و یک صفحه 20 ردیف. تمام اینها را به صورت خودکار محاسبه میکند و بسیار عالی است. (این امکان مهمی است که خیلی از ابزارهای گزارشگیری موجود هنوز با آن مشکل دارند)
اما اگر فرض را بر این بگذاریم که اندازه سلولها و در نتیجه طول هر ردیف ثابت است و مثلا تمام صفحات نهایتا از یک تعداد ردیف مشخص تشکیل خواهند شد، خاصیتی را به نام number of rows یا rows count و امثال آنرا ندارد که مثلا به آن گفت، من در هر صفحه فقط 5 ردیف را میخواهم نمایش دهم و نه 20 ردیف را.
روش حل این مساله را در ادامه ملاحظه خواهید کرد و یک نکتهی خیلی ساده و مستند نشده دارد!
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace RowsCountSample
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var table1 = new PdfPTable(3);
table1.HeaderRows = 2;
table1.FooterRows = 1;
//header row
var headerCell = new PdfPCell(new Phrase("header"));
headerCell.Colspan = 3;
headerCell.HorizontalAlignment = Element.ALIGN_CENTER;
table1.AddCell(headerCell);
//footer row
var footerCell = new PdfPCell(new Phrase("footer"));
footerCell.Colspan = 3;
footerCell.HorizontalAlignment = Element.ALIGN_CENTER;
table1.AddCell(footerCell);
//adding some rows
for (int i = 0; i < 70; i++)
{
//adds a new row
table1.AddCell(new Phrase("Cell[0], Row[" + i + "]"));
table1.AddCell(new Phrase("Cell[1], Row[" + i + "]"));
table1.AddCell(new Phrase("Cell[2], Row[" + i + "]"));
//sets the number of rows per page
if (i > 0 && table1.Rows.Count % 7 == 0)
{
pdfDoc.Add(table1);
table1.DeleteBodyRows();
pdfDoc.NewPage();
}
}
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
نکته جدید این مثال، قسمت زیر است:
if (i > 0 && table1.Rows.Count % 7 == 0)
{
pdfDoc.Add(table1);
table1.DeleteBodyRows();
pdfDoc.NewPage();
}
هر زمان که table1 به صفحه اضافه شود، header و footer هم اضافه خواهند شد، اما اگر BodyRows آن حذف نشود، دفعهی دومی که این table به صفحه اضافه میشود، شامل ردیفهای مثلا یک تا 10 خواهد بود بجای 6 تا 10 .
Dialects در NHibernate کلاسهایی هستند جهت معرفی تعاریف ویژگیهای خاص بانکهای اطلاعاتی مختلف؛ مثلا SQL Server 2008 چه ویژگیهای جدیدی دارد یا SQL Server CE 4.0 که جدیدا ارائه شده، امکان تعریف offset را در کوئریهای خود میسر کرده (چیزی که قرار است در نگارش بعدی SQL Server اصلی(!) در دسترس باشد) ، اکنون چگونه میتوان این ویژگی را فعال کرد (باید Dialect آن به روز شود و ... همین). یک سری Dialect از پیش تعریف شده هم برای اکثر بانکهای اطلاعاتی در NHibernate وجود دارد. ممکن است این Dialects پیش فرض الزاما خواسته شما را برآورده نکنند یا مو به مو مستندات بانک اطلاعاتی مرتبط را پیاده سازی نکرده باشند و سؤال این است که اکنون چه باید کرد؟ آیا باید حتما سورسها را دستکاری و بعد کامپایل کرد؟ به این صورت هر بار با ارائه یک نگارش جدید NHibernate به مشکل برخواهیم خورد چون باید کل عملیات تکرار شود.
خبر خوب اینکه میتوان از همین Dialects موجود ارث بری کرد، سپس مواردی را که نیاز داریم override کرده یا به کلاس مشتق شده افزود. اکنون میتوان از این Dialect سفارشی به جای Dialect اصلی استفاده کرد. در ادامه با یک نمونه آشنا خواهیم شد.
فرض کنید Dialect انتخابی مرتبط است با SQL Server CE استاندارد. کوئری ساده زیر را مینویسیم، به ظاهر باید کار کند:
var list = session.Query<SomeClass>().Where(x=>x.Date.Year==2011).ToList();
extract(year, ?1)
datepart(year, ?1)
using NHibernate;
using NHibernate.Dialect;
using NHibernate.Dialect.Function;
namespace Test1
{
public class CustomSqlCeDialect : MsSqlCeDialect
{
public CustomSqlCeDialect()
{
RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(year, ?1)"));
}
}
}
var dbType = MsSqlCeConfiguration.Standard
...
.Dialect<CustomSqlCeDialect>();
پس از آن کوئری LINQ ابتدای بحث بدون مشکل اجرا خواهد شد چون اکنون میداند که بجای extract year ، باید از تابع datepart استفاده کند.
مرحله بعد هم میتواند تهیه یک patch و ارسال به گروه اصلی برای به روز رسانی پروژه NH باشد.
سطح اول کش در NHibernate در یک تراکنش معنا پیدا میکند (+)؛ اما نتایج حاصل از اعمال سطح دوم (+) آن، در اختیار تمام تراکنشهای جاری برنامه خواهند بود. در ادامه قصد داریم نحوه فعال سازی سطح دوم کش NHibernate را توسط Fluent NHibernate بررسی کنیم.
الف) دریافت کش پروایدر
برای این منظور به صفحه اصلی آن در سایت سورس فورج مراجعه نمائید(+). اگر به علت تحریمها امکان دریافت فایلهای مرتبط را نداشتید از این برنامه استفاده کنید(+). پس از دریافت، میخواهیم نحوه فعال سازی NHibernate.Caches.SysCache.dll را بررسی کنیم (این اسمبلی، در برنامههای وب و دسکتاپ بدون مشکل کار میکند).
ب) اعمال به قسمت تعاریف اولیه
پس از دریافت اسمبلی NHibernate.Caches.SysCache.dll و افزودن ارجاعی به آن، اکنون نوبت به معرفی آن به تنظیمات Fluent NHibernate میباشد. اینکار هم بسیار ساده است:
...
.ConnectionString(x => x.FromConnectionStringWithKey(...))
.Cache(x => x.UseQueryCache()
.UseMinimalPuts()
.ProviderClass<NHibernate.Caches.SysCache.SysCacheProvider>())
...
ج) تعریف نوع کش در هنگام ایجاد نگاشتها
اگر از ClassMapها برای تعریف نگاشتها استفاده میکنید، در انتهای تعاریف یک سطر Cache.ReadWrite را اضافه کنید.
اگر از AutoMapping استفاده میکنید، نیاز است تا با استفاده از IAutoMappingOverride (+) سطر یاد شده اضافه گردد؛ برای مثال:
using FluentNHibernate.Automapping.Alterations;
namespace NH3Test.MappingDefinitions.Domain
{
public class AccountOverrides : IAutoMappingOverride<Account>
{
public void Override(FluentNHibernate.Automapping.AutoMapping<Account> mapping)
{
mapping.Cache.ReadWrite();
}
}
}
د) اعمال متد Cacheable به کوئریها
سه مرحله قبل نحوه برپایی مقدماتی سطح دوم کش را بیان میکنند و تنها یکبار نیاز است انجام شوند. در ادامه هر جایی که نیاز داشتیم نتایج کوئری مورد نظر کش شوند (و باید دقت داشت که این کش شدن سطح دوم به معنی در دسترس بودن نتایج آن جهت تمام کاربران برنامه در تمام تراکنشهای جاری برنامه هستند؛ برای مثال نتایج آمار سایت که دسترسی عمومی دارد) تنها کافی است متد Cacheable را به کوئری مورد نظر اضافه کرد؛ برای مثال:
var data = session.QueryOver<Account>()
.Where(s => s.Name == "name")
.Cacheable()
.List();
ه) چگونه صحت اعمال سطح دوم کش را بررسی کنیم؟
برای بررسی این امر باید به خروجی SQL نهایی مراجعه کرد (+). سه تراکنش مجزا را تعریف کنید. در تراکنش اول یک insert ساده، در تراکنش دوم کوئری از اطلاعات قبل (به همراه اعمال متد Cacheable) و در تراکنش سوم مجددا همان کوئری تراکنش دوم را (به همراه اعمال متد Cacheable) تکرار کنید. حاصل کار تنها باید دو عبارت SQL باشند. یک مورد جهت insert و یک مورد هم select . در تراکنش سوم، از نتایج کش شده تراکنش دوم استفاده خواهد شد؛ به همین جهت دیگری کوئری سومی به بانک اطلاعاتی ارسال نخواهد شد.
اگر اعمال مورد (ج) فوق را فراموش کنید، سه کوئری را مشاهده خواهید کرد، اما کوئری سوم با کوئری دوم اندکی متفاوت خواهد بود و بهینهتر؛ چون به صورت هوشمند بر اساس جستجوی بر روی primary key تغییر کرده است (صرفنظر از اینکه قسمت where کوئری شما چیست).
Thickbox یکی از پلاگینهای jQuery است که جهت نمایش صفحات modal بکار میرود.
روش استفاده بسیار سادهای هم دارد:
الف) jquery.js باید به صفحه معرفی شود.
ب) سپس thickbox.js باید به صفحه الحاق شود.
ج) و فایل css آن یعنی thickbox.css نیز باید به صفحه افزوده شود.
برای استفاده از آن کافی است یک لینک به صفحه اضافه کنید که ویژگی class آن مساوی thickbox باشد. مثلا:
<a href="#TB_inline?height=155&width=300&inlineId=hiddenModalContent&modal=true" class="thickbox">Show hidden modal content.</a>
<a href="iframeModal.html?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=200&width=300&modal=true" title="add a caption to title attribute / or leave blank" class="thickbox">Open iFrame Modal</a>
و مثالهای دیگری از این دست که در صفحه رسمی آن موجود است و نیازی به تکرار آنها نیست.
اما مشکلی که در بلاگر وجود دارد این است که شما نمیتوانید این ویژگیها را در بسیاری از موارد به صورت دستی تنظیم کنید. یک ویجت ظاهر میشود که فقط به شما امکان ثبت یک عنوان و لینک مربوطه را میدهد و همین.
برای انتساب ویژگیهای thickBox به این نوع لینکها که کنترلی روی آن نیست میتوان از خود jQuery کمک گرفت:
$(document).ready(function(){
$("a[href='http://www.mysite.com/page.htm?TB_iframe=true&height=340&width=530']").each(function(){
var obj = $(this);
obj.attr("title","Contact me");
obj.attr("class","thickbox");
}).bind("contextmenu",function(e){
return false;
});
});
همچنین در اینجا کلیک راست نیز بر روی این لینک بسته شده تا کاربر فقط کلیک معمولی را انجام دهد (فقط روی همین یک لینک در صفحه اعمال میشود و نه کل صفحه).
نکتهای در مورد مدیریت طول عمر اشیاء در حالت HybridHttpOrThreadLocalScoped در برنامههای دسکتاپ
cfg.For<IUnitOfWork>().Use(() => new MyWpfFrameworkContext());
ThreadLocal - A single instance will be created for each requesting thread. Caches the instances with ThreadLocalStorage. Hybrid - Uses HttpContext storage if it exists, otherwise uses ThreadLocal storage
خوب، مشکل کجا است؟ در یک برنامه دسکتاپ اگر ترد جدیدی را ایجاد نکنیم، کل برنامه در طول مدتی که در حال اجرا است در یک ترد اصلی اجرا میشود. در نتیجه استفاده از حالت HybridHttpOrThreadLocalScoped در برنامههای دسکتاپ، بسیار شبیه به حالت Singleton است. به این ترتیب با بسته شدن یک پنجره یا هدایت به صفحهای دیگر، Context برنامه و EF رها نخواهند شد (چون تضمین شده است که یک وهله از این شیء در طول عمر ترد جاری وجود داشته باشد). نتیجه آن روبرو شدن با خطاهایی است که ردیابی و دیباگ بسیار مشکلی دارند. برای مثال چندی قبل در سایت مطرح شده بود که چرا در یک برنامه WinForms خطای زیر را دریافت میکنم:
An object with the same key already exists in the ObjectStateManager
در برنامههای وب چطور؟
در برنامههای وب، به ازای هر درخواست رسیده، یک ترد جدید ایجاد میشود. به همین جهت استفاده از حالت HybridHttpOrThreadLocalScoped برای داشتن تنها یک Context در طول یک درخواست ضروری است.
نتیجه گیری
در برنامههای دسکتاپ خود اگر از ترد استفاده نمیکنید، از حالت HybridHttpOrThreadLocalScoped برای مدیریت طول عمر UOW استفاده نکنید.
نوشتن Middleware سفارشی در ASP.NET Core
یک نکتهی تکمیلی: کار با اینترفیس IMiddleware جهت تعریف میانافزارهای سفارشی
اگر امروز قصد تعریف میانافزارهای سفارشی را دارید، بهتر است از روش باز public async Task Invoke(HttpContext context) که در این مطلب معرفی شد، دیگر استفاده نکنید؛ چون مهمترین محدودیتهای آن، داشتن طول عمر Singleton غیرقابل تغییر و همچنین عدم امکان پیاده سازی اینترفیس IDisposable در آن جهت پاکسازی خودکار منابع است. امروز روش توصیه شده، استفاده از اینترفیس IMiddleware است. در این حالت متد Task Invoke فوق، به متد مشخص و ثابت Task InvokeAsync(HttpContext context, RequestDelegate next) تغییر میکند. چون در این حالت دیگر نمیتوان پارامترهای این متد مشخص را مانند قبل که اینترفیسی را پیاده سازی نمیکرد، به صورت پویا کم و زیاد کرد، میتوان سرویسهای مدنظر را به سازندهی کلاس، تزریق کرد. به همین جهت نیاز است، آنرا به نحو زیر به سیستم تزریق وابستگیها معرفی کرد:
builder.Services.AddTransient<MyNewMiddleware>();
این الزام به تعریف آن به صورت یک سرویس رسمی، مزیتهای زیر را به همراه دارد:
الف) میتوان طول عمری، غیر از Singleton را هم در صورت نیاز، تعریف کرد (و مشکل کار با سرویسهایی با طول عمرهای غیر از Singleton کمتر میشود).
ب) چون طول عمر این میانافزار اکنون توسط سیستم تزریق وابستگیها مدیریت میشود، اگر این میانافزار اینترفیس IDisposable را پیاده سازی کند، کار پاکسازی منابع آن خودکار خواهد شد.
نکته 1: روش معرفی آن به سیستم تزریق وابستگیها، به صورت Concrete type است؛ یعنی اصل کلاس باید معرفی شود (مانند سطر فوق) و نه اینکه به صورت متداول زیر به همراه ذکر اینترفیس IMiddleware باشد:
builder.Services.AddTransient<IMiddleware, MyNewMiddleware>();
مابقی کار با آن، با میانافزارهای متداول، تفاوتی ندارد. یعنی قسمت UseMiddleware آن یکی است:
app.UseMiddleware<MyNewMiddleware>();
بنابراین این روش نسبت به روش متداول قبلی، دو تفاوت پیاده سازی اینترفیس مشخص IMiddleware و ثبت کلاس آن به صورت یک سرویس رسمی را دارد؛ مابقی نکات آن، مانند قبل است.
نکته 2: اگر از Scrutor برای ثبت خودکار سرویسهای برنامه استفاده میکنید، روش ثبت خودکار اینگونه سرویسها به صورت زیر و با استفاده از متد ()AsSelf است:
services.Scan(scan => scan.FromAssembliesOf(typeof(IDataSeedersRunner)) .AddClasses(classes => classes.Where(type => { var allInterfaces = type.GetInterfaces(); return allInterfaces.Contains(typeof(IMiddleware)) && allInterfaces.Contains(typeof(ISingletonService)); })) .AsSelf() .WithSingletonLifetime());
در این مثال، تمام IMiddleware هایی که با نشانگر ISingletonService هم مزین شدهاند، یافت شده و به صورت Concrete type هایی، با طول عمر Singleton، به سیستم تزریق وابستگیها اضافه میشوند.
<ErrorBoundary> <ChildContent> @Body </ChildContent> <ErrorContent> <p>Whoa, sorry about that! While we fix this problem, buy some shirts!</p> </ErrorContent> </ErrorBoundary>
<ErrorContent Context="exception"> @exception </ErrorContent>
<ErrorBoundary @ref="errorBoundary"> @Body </ErrorBoundary> @code { ErrorBoundary errorBoundary; protected override void OnParametersSet() { // On each page navigation, reset any error state errorBoundary?.Recover(); } }