نظرات مطالب
نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
دو بحث وجود داره:
- برجسته سازی قسمتی از عبارت جستجو شده در لیست نمایش داده شده توسط افزونه auto-complete. این مورد خودکار است و توسط افزونه انجام می‌شود.
- برجسته سازی قسمتی از عبارت جستجو شده در نتایج یک جستجوی کامل بعدی که قرار است highlight آن توسط ما با کدنویسی خاصی انجام شود. مراجعه کنید به این مطلب برای توضیحات بیشتر.
مطالب
آشنایی با NHibernate - قسمت نهم

استفاده از Log4Net جهت ثبت خروجی‌های SQL حاصل از NHibernate

هنگام استفاده از NHibernate، پس از افزودن ارجاعات لازم به اسمبلی‌های مورد نیاز آن به برنامه، یکی از اسمبلی‌هایی که به پوشه build برنامه به صورت خودکار کپی می‌شود، فایل log4net.dll است (حتی اگر ارجاعی را به آن اضافه نکرده باشیم) که جهت ثبت وقایع مرتبط با NHibernate مورد استفاده قرار می‌گیرد. خوب اگر مجبوریم که این وابستگی کتابخانه NHibernate را نیز در پروژه‌های خود داشته باشیم، چرا از آن استفاده نکنیم؟!
شرح مفصل استفاده از این کتابخانه سورس باز را در سایت اصلی آن می‌توان مشاهده کرد:


برای اینکه از این کتابخانه در برنامه خود جهت ثبت عبارات SQL تولیدی توسط NHibernate استفاده کنیم، باید مراحل زیر طی شوند:
الف) ارجاعی را به اسمبلی log4net.dll اضافه نمائید (کنار اسمبلی NHibernate در پوشه build برنامه باید موجود باشد)
ب) فایل app.config برنامه را (برنامه ویندوزی) به صورت زیر ویرایش کرده و چند سطر مربوطه را اضافه نمائید (در مورد برنامه‌های وب هم به همین شکل است. configSections فایل web.config تنظیم شده و سپس تنظیمات log4net را قبل از بسته شدن تگ configuration اضافه نمائید ) :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>

<log4net>
<appender name="rollingFile"
type="log4net.Appender.RollingFileAppender,log4net" >
<param name="File" value="NHibernate_Log.txt" />
<param name="AppendToFile" value="true" />
<param name="DatePattern" value="yyyy.MM.dd" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="500KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%d %p %m%n" />
</layout>
</appender>
<logger name="NHibernate.SQL">
<level value="ALL" />
<appender-ref ref="rollingFile" />
</logger>
</log4net>

</configuration>
ج) سپس باید فراخوانی زیر نیز در ابتدای کار برنامه صورت گیرد:

log4net.Config.XmlConfigurator.Configure();
در یک برنامه ASP.Net این فراخوانی باید در Application_Start فایل Global.asax.cs صورت گیرد.
یا در یک برنامه از نوع WinForms تنها کافی است سطر زیر را به فایل AssemblyInfo.cs برنامه اضافه کرد:

// Configure log4net using the .config file
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
یا این سطر را به فایل Global.asax.cs یک برنامه ASP.Net نیز می‌توان اضافه کرد. Watch=true آن، با کمک FileSystemWatcher تغییرات فایل کانفیگ را تحت نظر داشته و هر بار که تغییر کند بلافاصله، تغییرات جدید را اعمال خواهد کرد.

د) هنگام استفاده از کتابخانه Fluent NHibernate حتما باید متد ShowSql در جایی که دیتابیس برنامه را تنظیم می‌کنیم (Fluently.Configure().Database) ذکر گردد (که نمونه آن‌را در مثال‌های قسمت‌های قبل ملاحظه‌ کرده‌اید).

توضیحاتی در مورد تنظیمات فوق:
configSections حتما باید در ابتدای فایل app.config‌ ذکر شود و گرنه برنامه کار نخواهد کرد.
سپس کانکشن استرینگ مورد استفاده در قسمت کانفیگ برنامه ذکر شده است.
در ادامه تنظیمات استاندارد مربوط به log4net را مشاهده می‌کنید.
در تنظیمات این کتابخانه، appender مشخص کننده محل ثبت وقایع است. زمانیکه که از RollingFileAppender استفاده کنیم، اطلاعات را در یک سری فایل ذخیره خواهد کرد (امکان ثبت وقایع در EventLog ویندوز، ارسال از طریق ایمیل و غیره نیز میسر است که جهت توضیحات بیشتر می‌توان به مستندات آن رجوع نمود).
سپس نام فایلی که اطلاعات وقایع در آن ثبت خواهند شد ذکر شده است (برای مثال NHibernate_Log.txt)، در ادامه مشخص گردیده که اطلاعات باید هر بار به این فایل Append و اضافه شوند. سطرهای بعدی مشخص می‌کنند که هر زمانیکه این لاگ فایل به 10 مگابایت رسید، یک فایل جدید تولید کن و هر بار 10 فایل آخر را نگه دار و مابقی فایل‌های قدیمی را حذف کن.
در قسمت PatternLayout مشخصات می‌کنیم که خروجی ثبت شده با چه فرمتی باشد. برای مثال یک سطر خروجی مطابق با تنظیمات فوق به شکل زیر خواهد بود:

2009-10-18 20:03:54,187 DEBUG INSERT INTO [Student] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'Vahid'
در قسمت Logger یک نام دلخواه ذکر شده و میزان اطلاعاتی که باید درج شود، از طریق مقدار level مورد نظر، قابل تنظیم است که می‌تواند یکی از مقادیر ALL ،DEBUG ،INFO ،WARN ،ERROR ،FATAL و یا OFF باشد. اینجا level در نظر گرفته شده ALL است که تمامی اطلاعات مرتبط با اعمال پشت صحنه NHibernate را لاگ خواهد کرد.
توسط appender-ref آن appender ایی را که در ابتدای کار تعریف و تنظیم کردیم، مشخص خواهیم کرد.

اگر هم با برنامه نویسی بخواهیم اطلاعاتی را به این لاگ فایل اضافه کنیم تنها کافی است بنویسیم:

log4net.LogManager.GetLogger("NHibernate.SQL").Info("test1");

اطلاعات بیشتر

ادامه دارد ...

مطالب
مکان اصلی یافتن آخرین نگارش‌های Fluent NHibernate

اگر علاقمند باشید که به آخرین نگارش‌های Fluent NHibernate دسترسی داشته باشید، مکان اصلی نگهداری و Build آن‌ها در سایت teamcity.codebetter.com می‌باشد. ثبت نام در آن رایگان است و سپس در آدرس ذیل می‌توانید آخرین Build ها را مشاهده و دریافت کنید:





برای نمونه:

مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود»، نحوه‌ی مهندسی معکوس ساختار جداول و ارتباطات یک بانک اطلاعاتی از پیش موجود را به روش Code First بررسی کردیم. با توجه به رسمی بودن این ابزار، می‌توان از آن برای یافتن معادل‌های سمت بانک اطلاعاتی، در EF Core نیز استفاده کرد. برای مثال بررسی کرد، درک EF Core از بانک اطلاعاتی طراحی شده چیست و هر چند در آن مطلب عنوان شد که می‌توان با پارامتر data-annotations-- ، خروجی نهایی را بر اساس روش data-annotations، بجای Fluent API به دست آورد، اما در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» مشاهده کردیم که بسیاری از تنظیمات پیشرفته‌ی EF Core، اساسا معادل data-annotation ایی ندارند. بنابراین بهتر است این پارامتر را فعال سازی نکنید.


تنظیمات روابط یک به چند در EF Core

همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطه‌ی تعریف شده‌ی در آن از نوع one-to-many است: یک بلاگ که می‌تواند چندین مطلب را داشته باشد.


اگر EF Core را وادار به تولید نگاشت‌های Code First معادل آن کنیم، به این خروجی‌ها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
با خروجی:
using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }
        public string Url { get; set; }

        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(entity =>
            {
                entity.Property(e => e.Url).IsRequired();
            });

            modelBuilder.Entity<Post>(entity =>
            {
                entity.HasOne(d => d.Blog)
                    .WithMany(p => p.Post)
                    .HasForeignKey(d => d.BlogId);
            });
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}

نحوه‌ی تشخیص خودکار روابط

EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاس‌ها تشخیص می‌دهد. در اینجا به خاصیت Blog نام navigation property را می‌دهند:
 public virtual Blog Blog { get; set; }
و به خاصیت Post نیز Collection navigation property می‌گویند:
 public virtual ICollection<Post> Post { get; set; }
در اینجا اگر تنها دو navigation property، در کلاس‌های به هم مرتبط شده، یافت شوند، به صورت خودکار به عنوان دو سر رابطه تنظیم می‌شوند. اگر بیشتر از یک navigation property در کلاسی وجود داشت، هیچ رابطه‌ای به صورت خودکار تشکیل نشده و باید ابتدا و انتهای روابط را به صورت دستی مشخص نمود.


نحوه‌ی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطه‌ی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
<primary key property name>
<navigation property name><primary key property name>
<principal entity name><primary key property name>
آنگاه این خاصیت به صورت خودکار به عنوان کلید خارجی تنظیم می‌شود. در رابطه‌ی فوق Blog از نوع principal است (پدر رابطه) و Post از نوع dependent (فرزند رابطه).
برای مثال در رابطه‌ی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطه‌است:
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
بنابراین به صورت خودکار به عنوان کلید خارجی درنظر گرفته می‌شود.

تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آن‌ها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.


استفاده از Fluent API برای تنظیم رابطه‌ی One-to-Many

در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده می‌کنید:
modelBuilder.Entity<Post>(entity =>
{
    entity.HasOne(d => d.Blog)
             .WithMany(p => p.Post)
             .HasForeignKey(d => d.BlogId);
});
از هر طرفی که شروع می‌کنید، متدهای HasOne و یا HasMany، مشخص کننده‌ی navigation property هستند که در سمت موجودیت معرفی شده قرار دارند. در اینجا چون کار با موجودیت Post شروع شده‌است، متد HasOne به خاصیت راهبری در همان سمت و به خاصیت Blog آن اشاره می‌کند.
مرحله‌ی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. این‌کار توسط یکی از متدهای WithOne و یا WithMany انجام می‌شود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره می‌کنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفته‌است.


ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose -a
با خروجی:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }

        [Required]
        public string Url { get; set; }

        [InverseProperty("Blog")]
        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        [ForeignKey("BlogId")]
        [InverseProperty("Post")]
        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}
همانطور که در توضیحات روش Fluent API عنوان شد، این مدل خاص، چون دقیقا بر اساس پیش فرض‌های EF Core طراحی شده‌است، نیازی به هیچگونه تنظیم اضافه‌تری ندارد. اما اگر کلید خارجی، مطابق سه الگویی که عنوان شد، قابل تشخیص نباشد، باید آن‌را در روش data annotations توسط ویژگی ForeignKey، به نحو صریحی مشخص کرد:
  [ForeignKey("BlogId")]
  [InverseProperty("Post")]
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
همچنین اگر بیش از یک خاصیت راهبری (navigation property) وجود داشت، ذکر InverseProperty نیز ضروری است تا مشخص شود سر دیگر این رابطه دقیقا کدام است.
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
   [InverseProperty("Blog")]
  public virtual ICollection<Post> Post { get; set; }

مطالب تکمیلی

علت virtual بودن خواص راهبری تولید شده

اگر دقت کنید، EF Core کدی را که تولید کرده‌است، به همراه خاصیت‌هایی virtual است:
public virtual Blog Blog { get; set; }
در اینجا تمام خاصیت‌های راهبری virtual تعریف شده‌اند. علت آن، به پیاده سازی مباحث AOP بر می‌گردد. زمانیکه خاصیتی به صورت virtual تعریف می‌شود، EF core می‌تواند آن‌را توسط یک شیء پروکسی شفاف احاطه کند. این پروکسی‌ها دو هدف را دنبال می‌کند:
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آن‌ها)
ب) پیاده سازی change tracking

مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمی‌شود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شده‌اند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شده‌اند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعه‌ها نیز ICollection ذکر شده‌است. این مورد نیز یکی دیگر از پیش فرض‌های توکار EF Core است؛ در جهت تشکیل پروکسی‌ها بر روی خواص راهبری مجموعه‌ای (علاوه بر virtual تعریف کردن آن‌ها). عنوان شده‌است که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection  مشتق شده‌است، این پروکسی‌ها تشکیل نخواهند شد.


واکشی اعضای به هم مرتبط

همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمی‌کند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامه‌های وب! چون استفاده‌ی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشت‌های بی‌شماری به بانک اطلاعاتی می‌شود و عموم برنامه نویس‌های وب باید مدام توسط برنامه‌های Profiler بررسی کنند که آیا این مساله رخ داده‌است یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
 var firstPost = context.Post.First();
Console.WriteLine(firstPost.Blog.Url);
چنین خطایی را صادر می‌کند:
 System.NullReferenceException
Object reference not set to an instance of an object.
علت اینجا است که چون Lazy loading غیرفعال است (هنوز در EF Core 1.0 پیاده سازی نشده‌است)، اولین دسترسی به شیء Blog، سبب وهله سازی خودکار آن نشده و این شیء نال است. به همین جهت استثنای فوق را مشاهده می‌کنیم.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading می‌گویند:
 var firstPost = context.Post.Include(x => x.Blog).First();
Console.WriteLine(firstPost.Blog.Url);

نکته‌ای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core

متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، می‌توان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Include(blog => blog.Owner)
                              .ToList();
اما فرض کنید خاصیت Post، دارای یک خاصیت راهبری دیگری به نام Author نیز باشد و می‌خواهیم این خاصیت هم بارگذاری شود:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                      .ThenInclude(post => post.Author)
                              .ToList();
روش انجام چنین کاری در EF Core، توسط متد الحاقی جدید ThenInclude است. ابتدا لیست Blogها عنوان شده‌است. سپس در این لیست علاقمند به واکشی تمام مطالب این بلاگ‌ها هم بوده‌ایم. به علاوه در این مطالب، نیاز است خاصیت Author آن‌ها نیز از پیش مقدار دهی شده و قابل دسترسی باشد. به همین جهت برای دسترسی به چندین سطح مختلف از متد ThenInclude کمک گرفته شده‌است.
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                 .ThenInclude(post => post.Author)
                                        .ThenInclude(author => author.Photo)
                              .ToList();
در این مثال یک سطح دیگر جلو رفته و شیء Photo مربوط به شیء Author را هم واکشی کرده‌ایم.
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                  .ThenInclude(post => post.Author)
                                      .ThenInclude(author => author.Photo)
                              .Include(blog => blog.Owner)
                                    .ThenInclude(owner => owner.Photo)
                              .ToList();

یک نکته: متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کرده‌ایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر می‌شود؛ مانند کوئری ذیل:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Select(blog => new
                               {
                                  Id = blog.BlogId,
                                  Url = blog.Url
                               })
                               .ToList();


تنظیمات حذف آبشاری در رابطه‌ی one-to-many

زمانیکه در رابطه‌ی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف می‌شود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیف‌های فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیت‌هایی اعمال می‌شود که توسط Context بارگذاری شده و در آن وجود دارند. اگر می‌خواهید سایر موجودیت‌های مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
 CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE
و اگر با EF Core بانک اطلاعاتی خود را ایجاد می‌کنید (مباحث مهاجرت‌ها)، این تنظیم به صورت خودکار اعمال خواهد شد؛ اگر DeleteBehavior را به نحو ذیل مشخص کرده باشید:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .OnDelete(DeleteBehavior.Cascade);
ب) SetNull: در این حالت فرزندان وابسته حذف نمی‌شوند و تنها کلید خارجی آن‌ها به نال تنظیم می‌شود.
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمی‌دهد.

یک نکته: به صورت پیش فرض اگر رابطه‌ی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالت‌های Optional، حالت SetNull تنظیم می‌گردد:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .IsRequired();
در اینجا ذکر صریح متد IsRequired به این معنا است که مقدار دهی کلید خارجی سر دیگر رابطه، اجباری است.
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستون‌ها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته می‌شود. به همین جهت است که در کدهای تولید شده‌ی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمی‌کنید.
بنابراین اگر می‌خواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.
مطالب
NHibernate و مدیریت خودکار تغییرات ساختار بانک اطلاعاتی

یکی از دردهای عظمایی که حین کار با بانک‌های اطلاعاتی رابطه‌ای وجود دارد، هماهنگ نبودن دیتابیس توسعه، با دیتابیس کاری است. البته ابزار‌های متعددی برای تهیه Diff بین این دو وجود دارند. ولی زمانیکه قرار باشد این کار را در چندجا هم انجام دهیم، باز هم مشکل خواهد بود.
با NHibernate می‌شود کل این مساله را فراموش کرد! می‌شود راحت خاصیتی را به کلاسی اضافه کرد و در اولین بار اجرای برنامه، خود NHibernate هماهنگ سازی‌ها را انجام دهد. فیلد اضافه کند. جدول اضافه کند. روابط مرتبط را اضافه کند. یعنی تا این حد که ما فقط فایل اجرایی برنامه را به روز کنیم، کافی باشد. البته در لابلای مطالبی که تا به حال در مورد NHibernate در این سایت منتشر شده به این موضوع هم پرداخته شده و مطلب جاری، خلاصه‌ی بزرگنمایی شده آن‌ها است.

اولین قدم: آیا ساختار دیتابیس جاری، با مدل برنامه تطابق دارد؟
قبل از اینکه از NHibernate بخواهیم ساختار بانک اطلاعاتی ما را تغییر دهید، باید بدانیم که آیا واقعا نیازی به اینکار هست یا خیر؟
توضیحات بیشتر در مورد روش تشخیص در اینجا: (^)

قدم دوم: اگر ساختار دیتابیس جاری با مدل برنامه تطابق ندارد، چگونه باید آن‌را به صورت خودکار به روز کرد؟
متد زیر بر اساس Configuration ابتدایی بانک اطلاعاتی و نگاشت‌های شما، کار به روز رسانی خودکار ساختار بانک اطلاعاتی را انجام خواهد داد:

public void UpdateDatabaseSchema(NHibernate.Cfg.Configuration config)
{
var schemaUpdate = new NHibernate.Tool.hbm2ddl.SchemaUpdate(config);
schemaUpdate.Execute(script: false, doUpdate: true);
}

یک نکته را هم باید درنظر داشت. در این روش هیچ فیلد و جدولی حذف نمی‌شود و به این ترتیب، جهت امنیت بیشتر طراحی شده. اگر واقعا نیاز داشتید فیلد یا جدولی را حذف کنید باید دستی، همانند سابق اقدام کنید.

قدم سوم: چگونه و در کجا، دو قدم قبل را با برنامه یکپارچه کنیم؟
بلافاصله پس از ایجاد SessionFactory در برنامه، متد زیر را فراخوانی کنید:

public void TryValidateAndUpdateDatabaseSchema(NHibernate.Cfg.Configuration config)
{
try
{
ValidateDatabaseSchemaAgainstMappings();
}
catch
{
UpdateDatabaseSchema(config);
}
}

متد ValidateDatabaseSchemaAgainstMappings در صورت عدم تطابق مدلی با بانک اطلاعاتی، یک exception را صادر می‌کند. بنابراین در اینجا کافی است متد UpdateDatabaseSchema را در قسمت catch فراخوانی کرد.
و از این پس دیگر می‌توانید به روز رسانی ساختار بانک اطلاعاتی برنامه را فراموش کنید! فیلد اضافه کنید، کلاس اضافه کنید، تمام این‌ها در اولین بار اجرای برنامه به روز شده، به صورت خودکار به بانک اطلاعاتی اعمال خواهند شد.


نظرات مطالب
نحوه‌ی مشاهده‌ی خروجی SQL تولید شده توسط WCF RIA Services
- اصلا راجع به تشخیص بحث نشد. نه SP و نه EF و نه L2SQL و نه NHibernate هیچکدام روش تشخیصی برای درک اینکه آیا ورودی فعلی خطرناک است یا خیر ندارند. کوئری پارامتری است که این‌ها را حفظ می‌کند: +
- در مورد توزیع از چه نوع؟ برنامه ویندوزی یا برنامه وب؟ برنامه‌های ویندوزی از نظر من اصلا برای یک سازمان مناسب نیستند : +
- مثال Round Trip اصلا واضح نبود ...
- مطلبی که لینک دادید بیشتر مربوط به نگارش اول EF بود و الان خیلی تغییر کرده و حالا اگر با EF مشکل دارید از NHibernate استفاده کنید یا موارد دیگر.
- مورد آخر بیشتر یک نظر شخصی است (و محترم) ولی دلایل آن قانع کننده نبودند.
مطالب
SFDown

چند روز قبل جهت دریافت فایل‌های تنظیم سطح دوم کش NHibernate به سایت سورس فورج مراجعه کردم و ... آه از نهادم برخاست! نه از این جهت که این سایت مدت مدیدی است ما رو تحریم کرده، به این دلیل که سورس فورج حتی با IP غیر ایرانی تونسته بود موقعیت من رو شناسایی کنه. شبیه به همین مورد مدتی است توسط گوگل نیز بکارگرفته میشه. به نظر میرسه این وسط جایی نشتی وجود داره. برای مثال در فایرفاکس امکان گزارش Geo Location به صورت پیش فرض فعال است. هر چند در مستندات آن صراحتا عنوان شده که ... خیر ... ما این اطلاعات را بدون تائید شما بروز نمی‌دهیم؛ ولی سؤال اینجا است که چطور تونستند از روی IP غیرایرانی، موقعیت من رو تشخیص دهند؟!
غیر فعال کردن این مورد هم ساده است. مطابق تصویر زیر عمل کنید:



به عبارتی در نوار آدرس فایرفاکس عبارت about:config را تایپ کرده، سپس عبارت geo.enabled را یافته و غیرفعال کنید. اکنون نیاز است یکبار مرورگر را بسته و باز کنید.
و ... IP ایی که سوخت ... تا یکی دو هفته عمل نخواهند کرد و بعد از لیست سیاه پاک خواهد شد.

برای رفع این نقیصه و ساده‌تر کردن دریافت فایل‌ها از سورج فورج، برنامه‌ی کوچکی را تهیه کرده‌ام که با گرفتن آدرس یک پروژه سورس فورج، لینک مستقیم قابل دریافت فایل‌های آن‌را در اختیار شما قرار می‌دهد. این برنامه بر اساس Mirror های سورس فورج عمل می‌کند و به صورت خودکار تمام آن‌ها را بررسی کرده و مورد قابل استفاده را گزارش خواهد داد. یا می‌توانید توسط خود برنامه فایل نهایی را دریافت کنید یا امکان کپی کردن یا ذخیره کردن لینک‌های مستقیم نهایی یافت شده، در برنامه پیش بینی شده است (جهت دریافت توسط برنامه download manager مورد علاقه شما).




مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت دوم - اجرای قواعد اعتبارسنجی تعریف شده
در قسمت قبل، روش تعریف قواعد اعتبارسنجی را با استفاده از کتابخانه‌ی Fluent Validation بررسی کردیم. در این قسمت می‌خواهیم این قواعد را به صورت خودکار به یک برنامه‌ی ASP.NET Core معرفی کرده و سپس از آن‌ها استفاده کنیم.


روش اول: استفاده‌ی دستی از اعتبارسنج کتابخانه‌ی Fluent Validation

روش‌های زیادی برای استفاده‌ی از قواعد تعریف شده‌ی توسط کتابخانه‌ی Fluent Validation وجود دارند. اولین روش، فراخوانی دستی اعتبارسنج، در مکان‌های مورد نیاز است. برای اینکار در ابتدا نیاز است با اجرای دستور «dotnet add package FluentValidation.AspNetCore»، این کتابخانه را در پروژه‌ی وب خود نیز نصب کنیم تا بتوانیم از کلاس‌ها و متدهای آن استفاده نمائیم. پس از آن، روش دستی کار با کلاس RegisterModelValidator که در قسمت قبل آن‌را تعریف کردیم، به صورت زیر است:
using FluentValidationSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult RegisterValidateManually(RegisterModel model)
        {
            var validator = new RegisterModelValidator();
            var validationResult = validator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
ساده‌ترین روش کار با RegisterModelValidator تعریف شده، ایجاد یک وهله‌ی جدید از آن و سپس فراخوانی متد Validate آن شیء است. در این حالت می‌توان کنترل کاملی را بر روی قالب پیام نهایی بازگشت داده شده داشت. برای مثال در اینجا اولین خطای بازگشت داده شده، به اطلاع کاربر رسیده‌است. حتی می‌توان کل شیء Errors را نیز بازگشت داد.

یک نکته: متد الحاقی AddToModelState که در فضای نام FluentValidation.AspNetCore قرار دارد، امکان تبدیل نتیجه‌ی اعتبارسنجی حاصل را به ModelState استاندارد ASP.NET Core نیز میسر می‌کند:
public IActionResult RegisterValidateManually(RegisterModel model)
{
    var validator = new RegisterModelValidator();
    var validationResult = validator.Validate(model);
    if (!validationResult.IsValid)
    {
       validationResult.AddToModelState(ModelState, null);
       return BadRequest(ModelState);
    }

    // TODO: Save the model

    return Ok();
}


روش دوم: تزریق اعتبارسنج تعریف شده در سازنده‌ی کنترلر

بجای وهله سازی دستی RegisterModelValidator و ایجاد وابستگی مستقیمی به آن، می‌توان از روش تزریق وابستگی‌های آن نیز استفاده کرد. در این حالت اعتبارسنج RegisterModelValidator با طول عمر Transient به سیستم تزریق وابستگی‌ها معرفی شده:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews();
        }
 و پس از آن با تزریق <IValidator<RegisterModel به سازنده‌ی کنترلر مدنظر، می‌توان به امکانات آن همانند روش اول، دسترسی یافت:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IValidator<RegisterModel> _registerModelValidator;

        public HomeController(IValidator<RegisterModel> registerModelValidator)
        {
            _registerModelValidator = registerModelValidator;
        }

        [HttpPost]
        public IActionResult RegisterValidatorInjection(RegisterModel model)
        {
            var validationResult = _registerModelValidator.Validate(model);
            if (!validationResult.IsValid)
            {
                return BadRequest(validationResult.Errors[0].ErrorMessage);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
به این ترتیب new RegisterModelValidator را با وهله‌ای از <IValidator<RegisterModel، تعویض کردیم. کار با این روش بسیار انعطاف پذیر بوده و همچنین قابلیت آزمون پذیری بالایی را نیز دارد.


روش سوم: خودکار سازی اجرای یک تک اعتبارسنج تعریف شده

اگر متد الحاقی AddFluentValidation را به صورت زیر به سیستم معرفی کنیم:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>();
            services.AddControllersWithViews().AddFluentValidation();
        }
سبب اجرای خودکار تمام IValidatorهای اضافه شده‌ی به سیستم، پیش از اجرای اکشن متد مرتبط با آن‌ها می‌شود. برای مثال اگر اکشن متدی دارای پارامتری از نوع RegisterModel بود، چون IValidator مخصوص به آن به سیستم تزریق وابستگی‌ها معرفی شده‌است، متد الحاقی AddFluentValidation، کار وهله سازی خودکار این IValidator و سپس فراخوانی متد Validate آن‌را به صورت خودکار انجام می‌دهد. به این ترتیب، قطعه کدهایی را که تاکنون نوشتیم، به صورت زیر خلاصه خواهند شد که در آن‌ها اثری از بکارگیری کتابخانه‌ی FluentValidation مشاهده نمی‌شود:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            if (!ModelState.IsValid)
            {
                // re-render the view when validation failed.
                return View(model);
            }

            // TODO: Save the model

            return Ok();
        }
    }
}
زمانیکه model به سمت اکشن متد فوق ارسال می‌شود، زیرساخت model-binding موجود در ASP.NET Core، اینبار کار اعتبارسنجی آن‌را توسط RegisterModelValidator به صورت خودکار انجام داده و نتیجه‌ی آن‌را به ModelState اضافه می‌کند که برای مثال در اینجا سبب رندر مجدد فرم شده که تمام مباحث tag-helper‌های استانداردی مانند asp-validation-summary و asp-validation-for پس از آن به صورت متداولی و همانند قبل، قابل استفاده خواهند بود.

نکته 1: تنظیمات فوق برایASP.NET Web Pages و PageModels نیز یکی است. فقط با این تفاوت که اعتبارسنج‌ها را فقط می‌توان به مدل‌هایی که به صورت خواص یک page model تعریف شده‌اند، اعمال کرد و نه به کل page model.

نکته 2: اگر کنترلر شما به ویژگی [ApiController] مزین شده باشد:
namespace FluentValidationSample.Web.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class HomeController : Controller
    {
        [HttpPost]
        public IActionResult RegisterValidatorAutomatically(RegisterModel model)
        {
            // TODO: Save the model

            return Ok();
        }
    }
}
در این حالت دیگر نیازی به ذکر if (!ModelState.IsValid) نیست و خطای حاصل از شکست اعتبارسنجی، به صورت خودکار توسط FluentValidation تشکیل شده و بازگشت داده می‌شود (پیش از رسیدن به بدنه‌ی اکشن متد فوق) و برای نمونه یک چنین شکل و خروجی خودکاری را پیدا می‌کند:
{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|84df05e2-41e0d4841bb61293.",
    "errors": {
        "FirstName": [
            "'First Name' must not be empty."
        ]
    }
}
اگر علاقمند به سفارشی سازی این خروجی خودکار هستید، باید به این صورت با تنظیم ApiBehaviorOptions و مقدار دهی نحوه‌ی تشکیل ModelState نهایی، عمل کرد:
        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            // override modelstate
            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = context =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => p.ErrorMessage)).ToList();
                    return new BadRequestObjectResult(new
                    {
                        Code = "00009",
                        Message = "Validation errors",
                        Errors = errors
                    });
                };
            });
        }


روش چهارم: خودکار سازی ثبت و اجرای تمام اعتبارسنج‌های تعریف شده

و در آخر بجای معرفی دستی تک تک اعتبارسنج‌های تعریف شده به سیستم تزریق وابستگی‌ها، می‌توان تمام آن‌ها را با فراخوانی متد RegisterValidatorsFromAssemblyContaining، به صورت خودکار از یک اسمبلی خاص استخراج نمود و با طول عمر Transient، به سیستم معرفی کرد. در این حالت متد ConfigureServices به صورت زیر خلاصه می‌شود:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews().AddFluentValidation(
                fv => fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>()
            );
        }
در اینجا امکان استفاده‌ی از متد fv.RegisterValidatorsFromAssembly نیز برای معرفی اسمبلی خاصی مانند ()Assembly.GetExecutingAssembly نیز وجود دارد.


سازگاری اجرای خودکار FluentValidation با اعتبارسنج‌های استاندارد ASP.NET Core

به صورت پیش‌فرض، زمانیکه FluentValidation اجرا می‌شود، اگر اعتبارسنج دیگری نیز در سیستم تعریف شده باشد، اجرا خواهد شد. به این معنا که برای مثال می‌توان FluentValidation و DataAnnotations attributes و IValidatableObject‌ها را با هم ترکیب کرد.
اگر می‌خواهید این قابلیت را غیرفعال کنید و فقط سبب اجرای خودکار FluentValidationها شوید، نیاز است تنظیم زیر را انجام دهید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews().AddFluentValidation(
       fv =>
       {
           fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
           fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
       }
    );
}
نظرات مطالب
EF Code First #7
- در قسمت HasRequired که Username نباید تعریف شود. در اینجا یک سر دیگر رابطه باید معرفی گردد. همان روابط و کلاس‌هایی که به صورت virtual در کدها آمده.
- در متن ذکر کردم «همین میزان تنظیم کفایت می‌کند و نیازی به استفاده از Fluent API برای معرفی روابط نیست.»
برای بسیاری از تنظیمات EF Code first، اگر پیش فرض‌های آن‌را رعایت کنید، نیازی به هیچگونه تنظیم اضافه‌تری ندارید. مثلا برای رابطه one-to-many فقط کافی است در دو سر رابطه (نه فقط یک سر آن)، تنظیمات زیر را داشته باشید:
// یک سایت که چندین بلاگ دارد
public class Site
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Blog> Blogs { set; get; }
}

public class Blog
{
    public int Id { get; set; }
    public string Name { set; get; }
 
    [ForeignKey("SiteId")]
    public virtual Site Site { get; set; }
    public int SiteId { set; get; }
}
همین مقدار کافی است و پیش فرض‌ها را پوشش می‌دهد. تنظیمات Fluent برای زمانی است که می‌خواهید پیش‌فرض‌ها را بازنویسی کنید. مثلا نام جدول خودکار تشکیل شده توسط آن مدنظر شما نیست. یا حالت بسیار خاصی از روابط مانند مدل‌های خودارجاع دهنده باید تشکیل شود و در این حالت فقط حالت Fluent است که پاسخگوی یک چنین سناریوهایی است.
مطالب
ذخیره سازی SQL تولیدی در NH3

همانطور که در مطلب "NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net" عنوان شد، از اینترفیس جدید IInternalLogger آن می‌توان جهت ثبت وقایع داخلی NHibernate استفاده کرد. اگر در این بین صرفا بخواهیم SQL های تولیدی را لاگ کنیم، خلاصه‌ی آن به صورت زیر خواهد بود:
public class LoggerFactory : ILoggerFactory
{
public IInternalLogger LoggerFor(System.Type type)
{
if (type == typeof(NHibernate.Tool.hbm2ddl.SchemaExport))
//log it
}

public IInternalLogger LoggerFor(string keyName)
{
if (keyName == "NHibernate.SQL")
//log it
}
}
یا کلید NHibernate.SQL باید پردازش شود (جهت ثبت SQL های کوئری‌ها) یا نوع NHibernate.Tool.hbm2ddl.SchemaExport جهت ثبت SQL ساخت ساختار جداول بانک اطلاعاتی باید بررسی گردد.
سورس کامل این کتابخانه‌ی کوچک را از اینجا می‌توانید دریافت کنید. جهت استفاده از آن تنها کافی است چند سطر زیر به فایل app.config یا web.config برنامه‌ی شما اضافه شوند:

<appSettings>
<add key="nhibernate-logger" value="NH3SQLLogger.LoggerFactory, NH3SQLLogger" />
</appSettings>

کلید nhibernate-logger ، به صورت مستقیم توسط NHibernate بررسی می‌شود و صرف نظر از اینکه از کدامیک از مشتقات NHibernate استفاده می‌کنید، با تمام آن‌ها کار خواهد کرد.
لازم به ذکر است که اگر برنامه‌ی شما از نوع ASP.NET است، این کتابخانه اطلاعات را در پوشه‌ی استاندارد App_Data ثبت خواهد کرد؛ در غیراینصورت فایل‌ها در کنار فایل اجرایی برنامه تشکیل خواهند شد.