مطالب
آشنایی با CLR: قسمت دوازدهم
متادیتاها شامل بلوکی از داده‌های باینری هستند که شامل چندین جدول شده و جدول‌ها نیز به سه دسته تقسیم می‌شوند:
  1. جداول تعاریف Definition Table
  2. جداول ارجاع References Table
  3. جداول manifest

جداول تعریف

جدول زیر تعدادی از جداول تعریف‌ها را توضیح می‌دهد:
 ModuleDef  شامل آدرس یا مدخلی است که ماژول در آن تعریف شده است. این آدرس شامل نام ماژول به همراه پسوند آن است؛ بدون ذکر مسیر. در صورتی که کامپایل به صورت GUID انجام گرفته باشد، Version ID ماژول هم همراه آن‌ها خواهد بود. در صورتیکه نام فایل تغییر کند، این جدول باز نام اصلی ماژول را به همراه خواهد داشت. هر چند تغییر نام فایل به شدت رد شده و ممکن است باعث شود CLR نتواند در زمان اجرا آن را پیدا کند.
 TypeDef  شامل یک مدخل ورودی برای هر نوعی است که تعریف شده است. هر آدرس ورودی شامل نام نوع ، پرچمها (همان مجوز‌های public و private و ...) می‌باشد. همچنین شامل اندیس هایی به متدها است که شامل جدول MethodDef می‌باشند یا فیلدهایی که شامل جدول FieldDef می‌باشند و الی آخر...
 MethodDef  شامل آدرسی برای هر متد تعریف شده در ماژول است که شامل  نام متد و پرچم هاست. همچنین شامل امضای متد و نقطه‌ی آغاز کد IL آن در ماژول هم می‌شود و آن آدرس هم میتواند ارجاعی به جدول ParamDef جهت شناسایی پارامترها باشد.
 FieldDef  شامل اطلاعاتی در مورد فیلدهاست که این اطلاعات ، پرچم، نام و نوع فیلد را مشخص می‌کنند.
 ParamDef  حاوی اطلاعات پارامتر متدهاست که این اطلاعات شامل پرچم‌ها (in , out ,retval) ، نوع و نام است.
 PropertyDef   برای هر پراپرتی یا خصوصیت، شامل یک آدرس است که شامل نام، نوع و پرچم می‌شود.
 EventDef  برای هر رویداد شامل یک آدرس است که این آدرس شامل نام و نوع است.

جداول ارجاعی
موقعی که کد شما کامپایل می‌شود، اگر شما به اسمبلی دیگری ارجاع داشته باشید، از جداول ارجاع کمک گرفته می‌شود که در جدول زیر تعدادی از این جداول فهرست شده‌اند:
 AssemblyRef  شامل آدرس اسمبلی است که ماژولی به آن ارجاع داده است و این آدرس شامل اطلاعات ضروری جهت اتصال به اسمبلی می‌شود و این اطلاعات شامل نام اسمبلی (بدون ذکر پسوند و مسیر)، شماره نسخه اسمبلی، سیستم فرهنگی و منطقه‌ای تعیین شده اسمبلی culture و یک کلید عمومی که عموما توسط ناشر ایجاد می‌گردد که هویت ناشر آن اسمبلی را مشخص می‌کند. هر آدرس شامل یک پرچم و یک کد هش هست که بری ارزیابی از صحت و بی خطا بودن بیت‌های اسمبلی ارجاع شده Checksum استفاده می‌شود.
 ModuleRef  شامل یک آدرس ورودی به هدر PE ماژول است به نوع‌های پیاده سازی شده آن ماژول در آن اسمبلی. هر آدرس شامل نام فایل و پسوند آن بدون ذکر مسیر است. این جدول برای اتصال به نوع‌هایی استفاده می‌شود که در یک ماژول متفاوت از ماژول اسمبلی صدا زده شده پیاده سازی شده است.
 TypeRef  شامل یک آدرس یا ورودی برای هر نوعی است که توسط ماژول ارجاع داده شده است. هر آدرس شامل نام نوع و آدرسی است که نوع در آن جا قرار دارد. اگر این نوع داخل نوع دیگری پیاده سازی شود، ارجاعات به سمت یک جدول TypeDef خواهد بود. اگر نوع داخل همان ماژول تعریف شده باشد، ارجاع به سمت جدول ModuleDef خواهد بود و اگر نوع در ماژول دیگری از آن اسمبلی پیاده سازی شده باشد، ارجاع به سمت یک جدول ModuleRef خواهد بود و اگر نوع در یک اسمبلی جداگانه تعریف شده باشد، ارجاع به جدول AssemblyRef خواهد بود.
 MemberRef  شامل یک آدرس ورودی برای هر عضو (فیلد و متدها و حتی پراپرتی و رویدادها) است که توسط آن آن ماژول ارجاع شده باشد. هر آدرس شامل نام عضو، امضاء و یک اشاره‌گر به جدول TypeRef است، برای نوع‌هایی که به تعریف عضو پرداخته‌اند. 
البته جداولی که در بالا هستند فقط تعدادی از آن جداول هستند، ولی قصد ما تنها یک آشنایی کلی با جداول هر قسمت بود. در مورد جداول manifest بعد‌تر صحبت می‌کنیم.
ابزارهای متنوع و زیادی هستند که برای بررسی و آزمایش متادیتاها استفاده می‌شوند. یکی از این ابزارها ILDasm.exe می‌باشد. برای دیدن متادیتاهای یک فایل اجرایی فرمان زیر را صادر کنید:
ILDasm Program.exe
صدور فرمان بالا باعث اجرای ILDasm و بارگزاری اسمبلی‌های program.exe می‌شود. برای مشاهده‌ی اطلاعات جداول متا به صورت شکیل و قابل خواندن برای انسان، در منوی برنامه، مسیر زیر را طی کنید:
View/MetaInfo/Show
با طی کردن گزینه‌های بالا، اطلاعات به صورت زیر نمایش داده می‌شوند:
===========================================================
ScopeName : Program.exe
MVID : {CA73FFE8­0D42­4610­A8D3­9276195C35AA}
===========================================================
Global functions
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Global fields
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Global MemberRefs
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
TypeDef #1 (02000002)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
TypDefName: Program (02000002)
Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass]
[BeforeFieldInit] (00100101)
Extends : 01000001 [TypeRef] System.Object
Method #1 (06000001) [ENTRYPOINT]
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
MethodName: Main (06000001)
Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
No arguments.
Method #2 (06000002)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
MethodName: .ctor (06000002)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName]
[RTSpecialName] [.ctor] (00001886)
RVA : 0x0000205c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
TypeRef #1 (01000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x01000001
ResolutionScope: 0x23000001
TypeRefName: System.Object
MemberRef #1 (0a000004)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Member: (0a000004) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
TypeRef #2 (01000002)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x01000002
ResolutionScope: 0x23000001
TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute
MemberRef #1 (0a000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Member: (0a000001) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
1 Arguments
Argument #1: I4
TypeRef #3 (01000003)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x01000003
ResolutionScope: 0x23000001
TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
MemberRef #1 (0a000002)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Member: (0a000002) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
TypeRef #4 (01000004)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x01000004
ResolutionScope: 0x23000001
TypeRefName: System.Console
MemberRef #1 (0a000003)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Member: (0a000003) WriteLine:
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: String
Assembly
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x20000001
Name : Program
Public Key :
Hash Algorithm : 0x00008004
Version: 0.0.0.0
Major Version: 0x00000000
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
Flags : [none] (00000000)
CustomAttribute #1 (0c000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
CustomAttribute Type: 0a000001
CustomAttributeName:
System.Runtime.CompilerServices.CompilationRelaxationsAttribute ::
instance void .ctor(int32)
Length: 8
Value : 01 00 08 00 00 00 00 00 > <
ctor args: (8)
CustomAttribute #2 (0c000002)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
CustomAttribute Type: 0a000002
CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute ::
instance void .ctor()
Length: 30
Value : 01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78 > T WrapNonEx<
: 63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01 >ceptionThrows <
ctor args: ()
AssemblyRef #1 (23000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x23000001
Public Key or Token: b7 7a 5c 56 19 34 e0 89
Name: mscorlib
Version: 4.0.0.0
Major Version: 0x00000004
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
User Strings
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
70000001 : ( 2) L"Hi"
Coff symbol name overhead: 0
لازم نیست که تمامی اطلاعات بالا را به طور کامل بفهمید. همین که متوجه شوید برنامه شامل  TypeDef است که نام آن Program است و این نوع به صورت یک کلاس عمومی sealed است که از نوع system.object ارث بری کرده است (یک نوع ارجاع از اسمبلی دیگر) و برنامه شامل دو متد main و یک سازنده ctor. است، کافی هست.
متد Main یک متد عمومی و ایستا static است که شامل کد IL است و هیچ خروجی ندارد و هیچ آرگومانی را نمی‌پزیرد. متد سازنده عمومی است و شامل کد IL است، سازنده هیچ نوع خروجی ندارد و هیچ آرگومانی هم نمی‌پذیرد و یک اشاره‌گر که به یک object در حافظه که موقع صدا زدن ساخته خواهد شد.
ابزار ILDasm امکاناتی بیشتری از آنچه که دیدید ارائه می‌کند. به عنوان نمونه اگر مسیر زیر را در منوها طی کنید:
View/statistics
اطلاعات آماری زیر نمایش داده می‌شود:
File size : 3584
PE header size : 512 (496 used) (14.29%)
PE additional info : 1411 (39.37%)
Num.of PE sections : 3
CLR header size : 72 ( 2.01%)
CLR meta­data size : 612 (17.08%)
CLR additional info : 0 ( 0.00%)
CLR method headers : 2 ( 0.06%)
Managed code : 20 ( 0.56%)
Data : 2048 (57.14%)
Unaccounted : ­1093 (­30.50%)
Num.of PE sections : 3
.text ­ 1024
.rsrc ­ 1536
.reloc ­ 512
CLR meta­data size : 612
Module ­ 1 (10 bytes)
TypeDef ­ 2 (28 bytes) 0 interfaces, 0 explicit layout
TypeRef ­ 4 (24 bytes)
MethodDef ­ 2 (28 bytes) 0 abstract, 0 native, 2 bodies
MemberRef ­ 4 (24 bytes)
CustomAttribute­ 2 (12 bytes)
Assembly ­ 1 (22 bytes)
AssemblyRef ­ 1 (20 bytes)
Strings ­ 184 bytes
Blobs ­ 68 bytes
UserStrings ­ 8 bytes
Guids ­ 16 bytes
Uncategorized ­ 168 bytes
CLR method headers : 2
Num.of method bodies ­ 2
Num.of fat headers ­ 0
Num.of tiny headers ­ 2
Managed code : 20
Ave method size ­ 10
اطلاعات بالا شامل نمایش حجم فایل به بایت و سایر قسمت‌های تشکیل دهنده فایل است...
توجه: ILDasm یک باگ دارد که بر نمایش اندازه‌ی فایل تاثیر می‌گذارد و باعث می‌شود شما نتوانید به اطلاعات ثبت شده اعتماد داشته باشید.
اشتراک‌ها
استفاده از MVVM در ASP.NET Core

Open source MVVM framework for ASP.NET Core and OWIN 

View

@viewModel DotvvmDemo.CalculatorViewModel, Dotvvm
    
<p>
    Enter the first number: 
    <dot:TextBox Text="{value: Number1}" />
</p>
<p>
    Enter the second number: 
    <dot:TextBox Text="{value: Number2}" />
</p>
<p>
    <dot:Button Text="Calculate" Click="{command: Calculate()}" />
</p>
<p>
    The result is: {{value: Result}}
</p>

ViewModel

using System;
    
namespace DotvvmDemo 
{
    public class CalculatorViewModel 
    {
            
        public int Number1 { get; set; }
            
        public int Number2 { get; set; }
            
        public int Result { get; set; }
            
        public void Calculate() 
        {
            Result = Number1 + Number2;
        }
    }
}


استفاده از MVVM در ASP.NET Core
نظرات مطالب
بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
روش ارتقاء به EF Core 2.2
    public class BloggingContext : DbContext
    {
        public BloggingContext()
        { }

        public BloggingContext(DbContextOptions options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.EnableSensitiveDataLogging();
                optionsBuilder.UseSqlServer(@"...");
                optionsBuilder.ConfigureWarnings(warnings =>
                {
                    warnings.Log(CoreEventId.IncludeIgnoredWarning);
                    warnings.Log(RelationalEventId.QueryClientEvaluationWarning);
                });
                optionsBuilder.UseLoggerFactory(GetLoggerFactory());
            }
        }

        private ILoggerFactory GetLoggerFactory()
        {
            IServiceCollection serviceCollection = new ServiceCollection();
            serviceCollection.AddLogging(builder =>
                   builder.AddConsole()
                          //.AddFilter(category: DbLoggerCategory.Database.Command.Name, level: LogLevel.Information));
                          .AddFilter(level => true)); // log everything
            return serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
        }
    }
در اینجا برای لاگ کردن خروجی EF می‌توان یک ILoggerFactory را که توسط متد AddFilter، سطوح لاگ کردن آن مشخص می‌شود، به UseLoggerFactory ارسال کرد.
اشتراک‌ها
پیاده سازی صحیح الگوی PRG در ASP.NET MVC

خلاصه کار استفاده از دو اکشن فیلتر زیر برروی اکشن‌های GET و POST میباشد:

public abstract class ModelStateTransfer : ActionFilterAttribute  
{
    protected static readonly string Key = typeof(ModelStateTransfer).FullName;
}

public class ExportModelStateAttribute : ModelStateTransfer  
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        //Only export when ModelState is not valid
        if (!filterContext.Controller.ViewData.ModelState.IsValid)
        {
            //Export if we are redirecting
            if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
            {
                filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

public class ImportModelStateAttribute : ModelStateTransfer  
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        if (modelState != null)
        {
            //Only Import if we are viewing
            if (filterContext.Result is ViewResult)
            {
                filterContext.Controller.ViewData.ModelState.Merge(modelState);
            }
            else
            {
                //Otherwise remove it.
                filterContext.Controller.TempData.Remove(Key);
            }
        }

        base.OnActionExecuted(filterContext);
    }
}
پیاده سازی صحیح الگوی PRG در ASP.NET MVC
مطالب
تهیه‌ی کارت با فرمت PDF با استفاده از کتابخانه iTextSharp
فرض کنید که می‌خواهید برای کاربری پس از ثبت اطلاعاتش در سایت، کارتی به فرمت PDF صادر کنید تا آن را دریافت و سپس چاپ کند. حتما از این دست موارد زیاد مشاهده کرده اید؛ مانند دریافت کارت ورود به جلسات امتحانی، کارت ورود به همایش‌ها و کنسرت‌های موسیقی و ...

برای تهیه فایل PDF، به غیر از کتابخانه‌های گزارش گیری تجاری، می‌توان از کتابخانه‌ی iTextSharp که گزینه‌ای سورس باز، با کیفیت و محبوب است، استفاده کرد. متاسفانه این کتابخانه دارای محیط گرافیکی طراحی گزارش نیست و کار با آن فقط از طریق کدنویسی میسر است که صد البته انعطاف پذیری و پویایی قابل توجهی را برای تهیه‌ی گزارش نسبت به ابزار‌های طراحی گرافیکی، در اختیار برنامه نویس قرار می‌دهد. البته می‌توان از برنامه‌ی Open Office برای طراحی قالب گزارش نیز استفاده کرد، اما من پس از استفاده، به کیفیت و انعطاف پذیری و امکانات مورد نظرم نتوانستم دست یابم و تصمیم گرفتم برای تهیه‌ی کارت، مستقیما با iTextSharp کد نویسی انجام دهم.

در این مقاله به نحوه‌ی تهیه یک کارت به فرمت PDF با استفاده از کتابخانه iTextSharp خواهیم پرداخت که این کتابخانه به فناوری خاصی گره نخورده است و در تمامی برنامه‌های ASP.NET ، WPF، Windows Form و در کل هر کجا که دات نت فریمورک در دسترس است، قابل استفاده می‌باشد.

فرض کنید کارتی به شکل زیر می‌خواهیم بسازیم (تمامی تصاویر از سطح اینترنت جمع آوری شده‌اند):


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

 فرقی نمی‌کند که تکنولوژی مورد استفاده شما چیست، برای سادگی کار این مثال را با یک Console Application آغاز کنید. برای نصب iTextSharp نیز فرمان زیر را در کنسول NuGet وارد کنید:
Install-Package iTextSharp


شروع کار با iTextSharp

معمولا برای کار با iText یک سری روال تکراری از قبیل انتخاب نام فایل نهایی، تعریف فونت، سایز کاغذ، حاشیه بندی و ... را باید طی کنید که کدهای آن را در ذیل مشاهده می‌کنید:

            var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);

            var docFont = GetFont();

            var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد

            var doc = new Document(pageSize);

            doc.SetMargins(18f, 18f, 15f, 2f);

            var pdfWriter = PdfWriter.GetInstance(doc, fileStream);

            doc.Open();
-  در اینجا سایز کارت، بر روی کاغذ A6 در حالت افقی قرار داده شده است. بدیهی است که مطابق نیاز خودتان می‌توانید این سایز را تغییر دهید.
-  تابع GetFont یک تابع کمکی است که در سورس نهایی ارائه شده است و نکته تعریف فونت در iTextSharp  در آن رعایت شده است.
 - بقیه موارد نیز جزء الزامات کار با این کتابخانه است.


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

            //  درج لوگوی مسابقات به صورت شفاف در پس زمینه
            var canvas = pdfWriter.DirectContentUnder;
            var logoImg = Image.GetInstance(competitionImagePath);
            logoImg.SetAbsolutePosition(0, 0);
            logoImg.ScaleAbsolute(pageSize);
            var graphicsState = new PdfGState { FillOpacity = 0.2F };
            canvas.SetGState(graphicsState);
            canvas.AddImage(logoImg);
 

چیدمان و طرح بندی بندی عناصر در iTextSharp

برای طراحی کارت یا کلا کار طراحی، باید با نحوه‌ی قرار دادن و طرح بندی عناصر مثل تصاویر و نوشته‌ها و ابزارهای مورد نیاز برای این کار، آشنا شوید. خوشبختانه در iText برای این کار ابزارهای خوبی وجود دارد.

حتما با تگ Table در HTML آشنایی دارید. در سال‌های دور، حتی کل صفحه‌ی وب را به وسیله‌ی Table ساختار دهی می‌کردند. در iTextSharp نیز کلاسی به نام PdfPTable در دسترس است که می‌توان از آن به عنوان قالبی برای قرار دادن عناصر، در صفحه استفاده کرد. این Table همانند هر جدولی دارای یک سری سطر و ستون است که می‌توانیم عناصر مورد نظرمان مثل تصویر و نوشته و... را در آن قرار دهیم. 

همانطور که از تگ Table در HTML می‌توان برای رسم جدول و قرار دادن عناصر در سطر و ستون‌های آن استفاده کرد، در iText نیز می‌توان از کلاس PdfPTable برای ترسیم جدول و از متد AddCell آن برای افزودن سلول به آن استفاده کرد. کار با کلاس PdfPTable نیز ساده است. کافی هست به هنگام ساخت نمونه‌ای از آن، در سازنده‌اش تعداد ستون‌های جدول را ذکر کنید. سپس با استفاده از متد AddCell آن، پارامتری از جنس PdfPCell برای آن ارسال کنید تا به جدول، سلول جدیدی اضافه شود و در صورتیکه تعداد سلول‌های جدید، از تعداد ستون‌های تعریف شده بیشتر شود، iText به صورت خودکار سلول‌های اضافی را به ردیف جدیدی منتقل می‌کند. البته برای افزودن سطر و ستون، روش‌های دیگری نیز هست؛ ولی گویا  روش مرجح همین روش است.  کلاس PdfPCell که نقش سلول‌های جدول را بازی می‌کند، نیز می‌تواند شامل متن، تصویر و یا حتی یک جدول تودرتو باشد.
  
در ادامه کدهای جدول بالایی کارت شامل لوگوی دانشگاه، عنوان مسابقات و عکس فرد را مشاهده می‌کنید:
            // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
            var topTable = new PdfPTable(3)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
            };

            var universityLogoImage = Image.GetInstance(universityLogoPath);

            universityLogoImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(universityLogoImage)
            {
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_CENTER,
                Border = 0,
            });

            var userImage = Image.GetInstance(userModel.ImagePath);
            userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
            userImage.BorderWidth = 1f;
            userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
            userImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(userImage)
            {
                HorizontalAlignment = 2,
                Border = 0
            });

            int[] topTableColumnsWidth = { 10, 25, 10 };

            topTable.SetWidths(topTableColumnsWidth);

            doc.Add(topTable);
-  در ابتدا یک جدول سه ستونه تعریف شده است. تعداد ستون‌ها در هنگام نمونه سازی از کلاس PdfPTable، در سازنده‌ی آن ذکر شده است.
-  در iText برای کار با تصاویر، باید از کلاس Image و متد GetInstance فراهم شده توسط خود کتابخانه استفاده کرد. سپس این تصویر را باید به عنوان پارامتر به سلول جدول ارسال کرد.
-  به کمک متد AddCell، می‌توان به جدول، سلول اضافه کرد و به صورت خودکار سلول‌های جدیدی که از تعداد ستون‌ها بیشتر می‌شوند، به سطر جدید منتقل می‌شوند.
-  اگر می‌خواهید در سلولی متنی نمایش دهید، از کلاس Phrase و تعیین صریح فونت آن استفاده کنید؛ چرا که در غیر این صورت ممکن است متون فارسی نمایش داده نشود.
-  در انتها هم جدول مورد نظر را باید به شی doc از جنس کلاس Document تعریف شده اضافه کرد.
 
بدیهی هست که اطلاعات شخص مثل نام، نام خانوادگی و ... را نیز باید در یک جدول چهار ستونه قرار داد و نکته‌ی خاص اضافه‌تری ندارد. 

حال اگر بیاییم این تکه کدها را کنار هم قرار بدهیم به کدی قابل اجرا خواهیم رسید.
ابتدا کلاسی را که در برگیرنده‌ی اطلاعات فرد است، تعریف می‌کنیم:
    public class UserModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string StudentNumber { get; set; }
        public string NationalCode { get; set; }
        public string UniversityName { get; set; }
        public string ImagePath { get; set; }
    }
 
- سپس کلاس CardReport را که اصل و اساس بحث ما بود، تعریف می‌کنیم.
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using Font = iTextSharp.text.Font;
using Image = iTextSharp.text.Image;
using Rectangle = iTextSharp.text.Rectangle;

namespace ITextSharpCardSample
{
    public class CardReport
    {
        public static void Generate(UserModel userModel, string competitionImagePath, string universityLogoPath)
        {
            var fileStream = new FileStream("card.pdf", FileMode.Create, FileAccess.Write, FileShare.None);

            var docFont = GetFont();

            var pageSize = PageSize.A6.Rotate(); // سایز کارت را اینجا باید مشخص کرد

            var doc = new Document(pageSize);

            doc.SetMargins(18f, 18f, 15f, 2f);

            var pdfWriter = PdfWriter.GetInstance(doc, fileStream);

            doc.Open();

            //  درج لوگوی مسابقات به صورت شفاف در پس زمینه
            var canvas = pdfWriter.DirectContentUnder;
            var logoImg = Image.GetInstance(competitionImagePath);
            logoImg.SetAbsolutePosition(0, 0);
            logoImg.ScaleAbsolute(pageSize);
            var graphicsState = new PdfGState { FillOpacity = 0.2F };
            canvas.SetGState(graphicsState);
            canvas.AddImage(logoImg);


            // جدولی که برای چیدمان عناصر ارم دانشگاه و عنوان و عکس شخص استفاده می‌شود
            var topTable = new PdfPTable(3)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
            };

            var universityLogoImage = Image.GetInstance(universityLogoPath);

            universityLogoImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(universityLogoImage)
            {
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            topTable.AddCell(new PdfPCell(new Phrase("کارت مسابقات دانشگاه آزاد اسلامی", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_CENTER,
                Border = 0,
            });

            var userImage = Image.GetInstance(userModel.ImagePath);
            userImage.Border = Rectangle.TOP_BORDER | Rectangle.RIGHT_BORDER | Rectangle.BOTTOM_BORDER | Rectangle.LEFT_BORDER;
            userImage.BorderWidth = 1f;
            userImage.BorderColor = new BaseColor(204, 204, 204); // gray color
            userImage.ScaleAbsolute(70, 100);

            topTable.AddCell(new PdfPCell(userImage)
            {
                HorizontalAlignment = 2,
                Border = 0
            });

            int[] topTableColumnsWidth = { 10, 25, 10 };

            topTable.SetWidths(topTableColumnsWidth);

            doc.Add(topTable);


            // جدول مشخصات شرکت کننده مثل نام و نام خانوادگی
            var infoTable = new PdfPTable(4)
            {
                WidthPercentage = 100,
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                ExtendLastRow = false,
                SpacingBefore = 15,
            };

            infoTable.AddCell(new PdfPCell(new Phrase("نام:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0,
                PaddingBottom = 15
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.FirstName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("نام خانوادگی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.LastName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("شماره\nدانشجویی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0,
                PaddingBottom = 15
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.StudentNumber, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("کد ملی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.NationalCode, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("واحد دانشگاهی:", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase(userModel.UniversityName, docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });


            // دو سلول بعدی صرفا جهت تکمیل شدن یک ردیف است تا عملکرد صحیح خود را داشته باشد
            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });

            infoTable.AddCell(new PdfPCell(new Phrase("", docFont))
            {
                RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                HorizontalAlignment = Element.ALIGN_LEFT,
                Border = 0
            });


            int[] infoTableColumnsWidth = { 20, 15, 20, 15 };

            infoTable.SetWidths(infoTableColumnsWidth);

            doc.Add(infoTable);

            doc.Close();
        }

        private static Font GetFont()
        {
            const string fontName = "Iranian Sans";

            if (FontFactory.IsRegistered(fontName))
                return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

            var fontPath = "Fonts/irsans.ttf"; // مسیر فونت

            FontFactory.Register(fontPath);

            return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        }

    }

}
نکته: حتما به تعریف فونت در پوشه‌ی Fonts و عکس‌ها در پوشه Images توجه فرمایید.

و در انتها نحوه‌ی استفاده از کلاس CardReport در یک برنامه‌ی Console: 
    class Program
    {
        static void Main(string[] args)
        {
            var userModel = new UserModel
            {
                FirstName = "علی",
                LastName = "احمدی",
                NationalCode = "1234567890",
                StudentNumber = "23242342",
                UniversityName = "آزاد",
                ImagePath = "Images/avatar.jpg"
            };

            CardReport.Generate(userModel, "Images/competition_logo.jpg", "Images/university_logo.png");

            System.Diagnostics.Process.Start("card.pdf");

        }
    }
     
مطالب
React 16x - قسمت 12 - طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
تا اینجا کامپوننت صفحه بندی را به همراه اعمال آن به لیست نمایش داده شده، پیاده سازی کردیم. در ادامه می‌خواهیم لیست ژانرهای سینمایی را که در فایل fakeGenreService.js تعریف شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
  { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" }
];

export function getGenres() {
  return genres.filter(g => g);
}
توسط list-group‌های بوت استرپی، در کنار صفحه نمایش داده و سپس به ازای هر گروه انتخابی توسط کاربر، فیلم‌های مرتبط با آن گروه را فیلتر کرده و نمایش دهیم.


بررسی ساختار کامپوننت ListGroup

شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import ListGroup from "./common/listGroup";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و با اضافه کردن یک row بوت استرپی دو ستونی، قصد داریم کامپوننت لیست فیلم‌ها را در ستون اول این ردیف نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم.
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین می‌کنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار می‌گیرد و در دومی، مابقی عناصری که تاکنون اضافه کرده‌ایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتم‌ها:
    return (
      <div className="row">
        <div className="col-2">
          <ListGroup />
        </div>
        <div className="col">
          ...
        </div>
      </div>
    );
این listGroup، حداقل نیاز به لیست آیتم‌هایی را دارد که باید نمایش دهد. این لیست نیز از fakeGenreService و متد getGenres آن تامین می‌شود که به صورت یک خاصیت جدید در state به نحو زیر درج خواهد شد:
import { getGenres } from "../services/fakeGenreService";
// ...

class Movies extends Component {
  state = {
    // ...
    genres: getGenres()
  };
همانطور که در قسمت 9 این سری نیز بررسی کردیم، اگر getGenres قرار است از سمت سرور و توسط یک درخواست Ajax ای تامین شود، محل صحیح قرارگیری آن در متد lifecycle hook ویژه‌ای به نام componentDidMount است. اما در اینجا چون genres یک لیست درون حافظه‌ای است، مقدار دهی فوق، مشکلی را ایجاد نمی‌کند. هرچند می‌توان هم اکنون نیز تعریف فوق را کمی اصولی‌تر نوشت. برای اینکار متد componentDidMount را اضافه کرده و به نحو زیر تنظیم می‌کنیم:
class Movies extends Component {
  state = {
    movies: [],
    pageSize: 4,
    currentPage: 1,
    genres: []
  };

  componentDidMount() {
    this.setState({ movies: getMovies(), genres: getGenres() });
  }
ابتدا آرایه‌های مورد نیاز movies و genres را در state تعریف کرده و آن‌ها را با یک آرایه‌ی خالی، مقدار دهی اولیه می‌کنیم. از این جهت که تا رسیدن به مرحله‌ی componentDidMount که اندکی طول می‌کشد، خطاهای زمان اجرای عدم دسترسی به این آرایه‌ها در برنامه رخ ندهد. سپس زمانیکه وهله‌ای از این کامپوننت در DOM رندر شد، متد componentDidMount فراخوانی شده و دو خاصیت state را با مقادیر دریافتی، به روز رسانی می‌کند.

پس از آن می‌توان ویژگی جدید items این کامپوننت را به آرایه‌ی genres دریافتی از state، تنظیم کرد:
<ListGroup items={this.state.genres} />
در این مرحله، ورودی دیگری به نظر نمی‌رسد که مورد نیاز باشد. اکنون این سؤال مطرح می‌شود که چه رخ‌دادهایی را قرار است از این کامپوننت دریافت کنیم یا به عبارتی خروجی آن چیست؟
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلم‌ها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه می‌کنیم:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
و سپس متد handleGenreSelect متصل به آن‌‌را به نحو زیر تعریف خواهیم کرد:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
  };
تا اینجا اینترفیس کامپوننت ListGroup را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


پیاده سازی نمایش آیتم‌ها در کامپوننت ListGroup

پیاده سازی ابتدایی کامپوننت ListGroup را در اینجا مشاهده می‌کنید:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item._id} className="list-group-item">
            {item.name}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;
کار با درج یک ul که با کلاس list-group مزین شده‌است، شروع می‌شود. سپس باید liهای آن‌را که نمایانگر آیتم‌های این لیست است، به صورت پویا با کلاس‌های list-group-item رندر کرد. برای اینکار از آرایه‌ی دریافتی this.props.items و فراخوانی متد map بر روی آن کمک می‌گیریم. در اینجا key هر ردیف با استفاده از خاصیت id هر آیتم و برچسب هر کدام از طریق خاصیت name هر شیء دریافتی، تامین می‌شود.

تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر می‌رسیم:


البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آن‌را به col-3 تبدیل می‌کنیم.


پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup

در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابسته‌است و اگر شیء دیگری را که دارای خواصی معادل این نام‌ها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم می‌کنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
<ListGroup
  items={this.state.genres}
  textProperty="name"
  valueProperty="_id"
  onItemSelect={this.handleGenreSelect}
/>
پس از این تغییر و افزودن textProperty و valueProperty، برای پویا سازی نام‌های خواص دریافتی در کامپوننت ListGroup، از روش کار با []، جهت دسترسی پویای به خواص یک شیء، استفاده می‌کنیم تا دیگر این کامپوننت به شیء خاص genre، وابستگی نداشته باشد و قابلیت استفاده‌ی مجدد از آن افزایش یابد:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item[this.props.valueProperty]} className="list-group-item">
            {item[this.props.textProperty]}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;


تعیین مقادیر پیش‌فرضی برای خواص props

با زیاد شدن تعداد خواص props، اینترفیس کامپوننت‌ها پیچیده‌تر می‌شوند. در یک چنین حالتی می‌توان در کامپوننت‌ها defaultProps را تعریف کرد و توسط آن مقادیر پیش‌فرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیش‌فرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف می‌کنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که می‌خواهیم مقادیر پیش‌فرضی را برای آن‌ها تعیین کنیم، ذکر خواهیم کرد:
ListGroup.defaultProps = {
  textProperty: "name",
  valueProperty: "_id"
};

export default ListGroup;
برای نمونه در اینجا دو خاصیت جدید textProperty و valueProperty را به همان مقادیر name و id_ مورد استفاده‌ی در این مثال تنظیم کرده‌ایم. پس از این تعریف، می‌توان به کامپوننت movies که از این ویژگی‌ها استفاده می‌کند مراجعه کرده و آن‌هایی را که با defaultProps تطابق دارند، از لیست ویژگی‌های ذکر شده حذف کرد؛ یعنی تعریف المان ListGroup به صورت زیر ساده می‌شود:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
بدیهی است اگر در آینده با اشیاء دیگری سر و کار داشتیم، می‌توان مجددا این خواص پیش‌فرض را بر اساس ساختار این اشیاء، مقدار دهی و تعیین کرد.


مدیریت انتخاب گروه‌های فیلم‌ها

در ادامه می‌خواهیم رخ‌داد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخ‌دادی به نام onItemSelect شویم که در ابتدای بحث، آن‌را به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن می‌شود، به المان li کامپوننت ListGroup اضافه می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className="list-group-item"
  onClick={() => this.props.onItemSelect(item)}
  style={{ cursor: "pointer" }}
>
  {item[this.props.textProperty]}
</li>
پس از این تغییرات و ذخیره‌ی برنامه، اگر به خروجی برنامه در مرورگر مراجعه کرده و بر روی هر کدام از آیتم‌های لیست گروه‌های فیلم‌ها کلیک کنیم، شیء مرتبط با آن آیتم در کنسول توسعه دهنده‌های مرورگر، لاگ می‌شود که نشان از برقراری صحیح ارتباطات این قسمت را دارد.

پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون می‌خواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحه‌ی انتخاب شده‌ی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگی‌های اینترفیس تعریف این المان اضافه می‌کنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی می‌کنیم:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({selectedGenre: genre});
  };
در یک چنین حالتی الزامی به تعریف selectedGenre در خاصیت state ابتدای کامپوننت نیست. چون با فراخوانی متد setState اگر یکی از خواص منتسب به شیء state به روز شده باشد، آن خاصیت نیز به روز می‌شود و یا اگر این خاصیت جدید باشد، با state موجود یکی خواهد شد؛ هرچند آن‌را به صورت زیر نیز می‌توان تعریف کرد که با یک شیء خالی مقدار دهی شده‌است:
class Movies extends Component {
  state = {
    // ...
    selectedGenre: {}
  };
سپس ویژگی selectedItem کامپوننت را به این مقدار تغییر یافته‌ی this.state.selectedGenre تنظیم می‌کنیم تا با هر بار فراخوانی setState که سبب رندر مجدد کامپوننت Movies در DOM مجازی React می‌شود، کامپوننت از selectedItem تغییر یافته مطلع شده و با افزودن کلاس active به آن آیتم، واکنش نشان دهد:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
  selectedItem={this.state.selectedGenre}
/>
اکنون به کامپوننت ListGroup مراجعه کرده و بر اساس ویژگی جدید selectedItem، تغییرات زیر را به className اعمال می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className={
    item === this.props.selectedItem
      ? "list-group-item active"
      : "list-group-item"
  }
  style={{ cursor: "pointer" }}
  onClick={() => this.props.onItemSelect(item)}
>
  {item[this.props.textProperty]}
</li>
در اینجا اگر item در حال رندر با this.props.selectedItem دریافتی یکی باشد، کلاس active به کلاس list-group-item اضافه می‌شود و برعکس.



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

در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شده‌ی در هر کدام باید بر اساس لیست فیلم‌های فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده می‌کنیم:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies
    } = this.state;

    const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

    const first = (currentPage - 1) * pageSize;
    const last = first + pageSize;
    const pagedMovies = filteredMovies.slice(first, last);

    return { totalCount: filteredMovies.length, data: pagedMovies };
  }
- در اینجا بجای اینکه مدام this.stat‌ها را جهت دریافت خواص آن تکرار کنیم، با استفاده از ویژگی Object Destructuring، خواصی را که نیاز داریم یکبار انتخاب کرده و سپس به دفعات از آن‌ها استفاده می‌کنیم. به همین جهت در این قطعه کد، فقط یکبار this.state را مشاهده می‌کنید که بسیار تمیزتر است و همچنین کارآیی آن نیز به علت عدم انتخاب مداوم مقدار خاصیتی از یک شیء، بالاتر از حالت قبل است.
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر داده‌ایم تا واضح‌تر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده می‌شود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار می‌گیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده می‌کنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شده‌است.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و داده‌های فیلتر شده‌ی صفحه بندی شده‌است، بازگشت می‌دهد.

ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده می‌شود:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const { totalCount, data: movies } = this.getPagedData();
- از آرایه‌ی movies، در قسمت قبل برای رندر لیست فیلم‌ها استفاده شد. به همین جهت در اینجا تغییر نام data به movies را مشاهده می‌کنید.
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتم‌های فیلتر شده را نمایش دهد و نه totalCount تمام فیلم‌های موجود را:
<Pagination
    itemsCount={totalCount}
در اینجا برچسب نمایش تعداد آیتم‌های موجود نیز باید تغییر کند:
<p>Showing {totalCount} movies in the database.</p>
ج) ممکن است در اولین بار مشاهده‌ی صفحه، کاربر صفحه‌ی شماره‌ی 3 را انتخاب کند که سبب تغییر currentPage موجود در state، به عدد 3 می‌شود. اکنون اگر کاربر نمایش فیلتر شده‌ی فیلم‌های یک گروه خاص را انتخاب کند، باید این شماره، به عدد 1 مجددا تنظیم شود:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, currentPage: 1 });
  };



افزودن گزینه‌ی نمایش تمام اطلاعات به لیست گروه‌های فیلم‌ها

در ادامه قصد داریم به بالای لیست گروه‌های موجود، گزینه‌ی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلم‌های موجود را مشاهده کرد.


برای این منظور در جائیکه لیست getGenres را دریافت و نمایش می‌دهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایه‌ی جدید را ایجاد می‌کنیم؛ بطوریکه اولین عنصر آن، گزینه‌ی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایه‌ی گروه‌ها را به این آرایه‌ی جدید اضافه می‌کنیم:
  componentDidMount() {
    const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
    this.setState({ movies: getMovies(), genres });
  }
همین اندازه تغییر برای فعالسازی این گزینه کفایت می‌کند؛ از این جهت که در متد getPagedData، ابتدا بررسی می‌شود که اگر آیتمی انتخاب شده بود و همچنین دارای id نیز بود، آنگاه کار فیلتر کردن صورت گیرد، درغیراینصورت، تمام رکوردها را بازگشت دهد:
const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-12.zip
مطالب
استفاده از Full text search توسط Entity Framework
پیشنیاز مطلب:
پشتیبانی از Full Text Search در SQL Server

Full Text Search یا به اختصار FTS یکی از قابلیت‌های SQL Server جهت جستجوی پیشرفته در متون میباشد. این قابلیت تا کنون در EF 6.1.1 ایجاد نشده است.
در ادامه پیاده سازی از FTS در EF را مشاهده مینمایید.
جهت ایجاد قابلیت FTS از متد Contains در Linq استفاده شده است.
ابتدا متد‌های الحاقی جهت اعمال دستورات FREETEXT و CONTAINS اضافه میشود.سپس دستورات تولیدی EF را قبل از اجرا بر روی بانک اطلاعاتی توسط امکان Command Interception به دستورات FTS تغییر میدهیم.
همانطور که میدانید دستور Contains در Linq توسط EF به دستور LIKE تبدیل میشود. به جهت اینکه ممکن است بخواهیم از دستور LIKE نیز استفاده کنیم یک پیشوند به مقادیری که میخواهیم به دستورات FTS تبدیل شوند اضافه مینماییم.
جهت استفاده از Fts در EF کلاس‌های زیر را ایجاد نمایید.
 
کلاس FullTextPrefixes :
/// <summary>
    /// 
    /// </summary>
    public static class FullTextPrefixes
    {
        /// <summary>
        /// 
        /// </summary>
        public const string ContainsPrefix = "-CONTAINS-";

        /// <summary>
        /// 
        /// </summary>
        public const string FreetextPrefix = "-FREETEXT-";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static string Contains(string searchTerm)
        {
            return string.Format("({0}{1})", ContainsPrefix, searchTerm);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static string Freetext(string searchTerm)
        {
            return string.Format("({0}{1})", FreetextPrefix, searchTerm);
        }

    }

کلاس جاری جهت علامت گذاری دستورات FTS میباشد و توسط متد‌های الحاقی و کلاس FtsInterceptor استفاده میگردد.

کلاس FullTextSearchExtensions :
public static class FullTextSearchExtensions
    {

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
        {
            return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
        {
            return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm)
        {
            if (String.IsNullOrEmpty(searchTerm))
            {
                return source;
            }

            // The below represents the following lamda:
            // source.Where(x => x.[property].Contains(searchTerm))

            //Create expression to represent x.[property].Contains(searchTerm)
            //var searchTermExpression = Expression.Constant(searchTerm);
            var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value");
            var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression);

            //Join not null and contains expressions

            var methodCallExpression = Expression.Call(typeof(Queryable),
                                                       "Where",
                                                       new[] { source.ElementType },
                                                       source.Expression,
                                                       Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters));

            return source.Provider.CreateQuery<TEntity>(methodCallExpression);
        }

در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
متد FreeTextSearch جهت استفاده از دستور FREETEXT  استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو. 
متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
 
کلاس FtsInterceptor :
 public class FtsInterceptor : IDbCommandInterceptor
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            RewriteFullTextQuery(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            RewriteFullTextQuery(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cmd"></param>
        public static void RewriteFullTextQuery(DbCommand cmd)
        {
            var text = cmd.CommandText;
            for (var i = 0; i < cmd.Parameters.Count; i++)
            {
                var parameter = cmd.Parameters[i];
                if (
                    !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength,
                        DbType.AnsiStringFixedLength)) continue;
                if (parameter.Value == DBNull.Value)
                    continue;
                var value = (string)parameter.Value;
                if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0)
                {
                    parameter.Size = 4096;
                    parameter.DbType = DbType.AnsiStringFixedLength;
                    value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query
                    value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                    parameter.Value = value;
                    cmd.CommandText = Regex.Replace(text,
                        string.Format(
                            @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                        string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName));
                    if (text == cmd.CommandText)
                        throw new Exception("FTS was not replaced on: " + text);
                    text = cmd.CommandText;
                }
                else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0)
                {
                    parameter.Size = 4096;
                    parameter.DbType = DbType.AnsiStringFixedLength;
                    value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query
                    value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                    parameter.Value = value;
                    cmd.CommandText = Regex.Replace(text,
                        string.Format(
                            @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                        string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName));
                    if (text == cmd.CommandText)
                        throw new Exception("FTS was not replaced on: " + text);
                    text = cmd.CommandText;
                }
            }
        }

    }

در این کلاس دستوراتی را که توسط متد‌های الحاقی جهت امکان Fts علامت گذاری شده بودند را یافته و دستور LIKE را با دستورات  CONTAINSو FREETEXT جایگزین میکنیم.

در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
public class Note
    {
        /// <summary>
        /// 
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string NoteText { get; set; }
    }

  public class FtsSampleContext : DbContext
    {
        /// <summary>
        /// 
        /// </summary>
        public DbSet<Note> Notes { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new NoteMap());
        }
    }

/// <summary>
    /// 
    /// </summary>
    class Program
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            DbInterception.Add(new FtsInterceptor());
            const string searchTerm = "john";
            using (var db = new FtsSampleContext())
            {
                var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList();
                //SQL Server Profiler result ===>>>
                //exec sp_executesql N'SELECT 
                //    [Extent1].[Id] AS [Id], 
                //    [Extent1].[NoteText] AS [NoteText]
                //    FROM [dbo].[Notes] AS [Extent1]
                //    WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                //char(4096)',@p__linq__0='(john)'
                var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList();
                //SQL Server Profiler result ===>>>
                //exec sp_executesql N'SELECT 
                //    [Extent1].[Id] AS [Id], 
                //    [Extent1].[NoteText] AS [NoteText]
                //    FROM [dbo].[Notes] AS [Extent1]
                //    WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                //char(4096)',@p__linq__0='(john)'
            }
            Console.ReadKey();
        }
    }

ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.com قرار گرفته است.
منبع:
Full Text Search in Entity Framework 6
نظرات مطالب
استفاده از خواص راهبری در Entity framework بجای Join نویسی
با سلام
اگه یک کلاس مخاطب با کد زیر باشه:
  public class Contact
    {
        public int ContactId { get; set; }
        public string FName { get; set; }
        public string LName { get; set; }
        public string FatherName { get; set; }
        public string Email { get; set; }
       public virtual ICollection<Phone> Phones { get; set; } 
}
و یک کلاس هم برای شماره تلفن‌ها با کد زیر:
    public class Phone
    {
        public int PhoneId { get; set; }
        public string PhoneNumber { get; set; }
        public string PhoneNote { get; set; }
        public string PhoneAddress { get; set; }
        public int PhoneTypeId { get; set; }
        public virtual PhoneType PhoneType { get; set; }

        [ForeignKey("ContactId")]
        public virtual Contact Contact { get; set; }
        public int ContactId { get; set; }
    }
حالا در زمان جستجو من از کد زیر استفاده نموده ام :
var listContacts = db.Contacts.Include(p => p.Phones).AsQueryable();
            if (searchContact.ByName)
                listContacts = listContacts.Where(c => c.LName.Contains(searchContact.Name));
            if (searchContact.ByNumber)
            {
                listContacts = listContacts.Where(c=>c.);
               
            }
            var phonelistmodel = await
                 listContacts.OrderBy(p => p.ContactId)
                     .Skip(page * count)
                     .Take(count)
                     .Select(c => new ListPhoneNumberViewmodel()
                     {
                         ContactId = c.ContactId,
                         Email = c.Email,
                         Name = c.FName + " " + c.LName,
                         Phones = c.Phones
                     }).ToListAsync();
ولی اصلا به اطلاعات جدول phone دسترسی ندارم؟
مطالب
افزودن خودکار کلاس‌های تنظیمات نگاشت‌ها در EF Code first
اگر از روش Fluent-API برای تنظیم و افزودن نگاشت‌های کلاس‌ها استفاده کنیم، با زیاد شدن آن‌ها ممکن است در این بین، افزودن یکی فراموش شود یا کلا اضافه کردن دستی آن‌ها در متد OnModelCreating آنچنان جالب نیست. می‌شود این‌کار را به کمک Reflection ساده‌تر و خودکار کرد:
        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
در این متد، در یک اسمبلی مشخص و فضای نامی در آن، به دنبال کلاس‌هایی از نوع EntityTypeConfiguration خواهیم گشت. در ادامه این کلاس‌ها وهله سازی شده و به صورت خودکار به modelBuilder اضافه می‌شوند.

یک مثال کامل که بیانگر نحوه استفاده از متد فوق است:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Reflection;

namespace EFGeneral
{
    public class User
    {
        public int UserNumber { get; set; }
        public string Name { get; set; }
    }

    public class UserConfig : EntityTypeConfiguration<User>
    {
        public UserConfig()
        {
            this.HasKey(x => x.UserNumber);
            this.Property(x => x.Name).HasMaxLength(450).IsRequired();
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // modelBuilder.Configurations.Add(new UserConfig());

            var asm = Assembly.GetExecutingAssembly();
            loadEntityConfigurations(asm, modelBuilder, "EFGeneral");
        }

        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            context.Users.Add(new User { Name = "name-1" });
            context.Users.Add(new User { Name = "name-2" });
            context.Users.Add(new User { Name = "name-3" });
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var user1 = context.Users.Find(1);
                if (user1 != null)
                    Console.WriteLine(user1.Name);
            }
        }
    }
}
در این مثال، در متد OnModelCreating بجای اضافه کردن دستی تک تک تنظیمات تعریف شده، از متد loadEntityConfigurations جهت یافتن آن‌ها در اسمبلی جاری و فضای نام مشخصی به نام EFGeneral استفاده شده است.
مطالب
واکشی اطلاعات به صورت chunk chunk (تکه تکه) و نمایش در ListView
یکی از مسائلی که در هنگام کار با کنترلهای داده‌ای نظیر ListView , GridView و .. با آن روبرو هستیم مسئله صفحه بندی می‌باشد و در بسیاری از موارد، کل اطلاعات در هر درخواست، بارگذاری میشود. در حالیکه روش بهینه به این صورت است که با توجه به PageSize و Index رکورد، می‌توان تعداد رکورد مورد نظر در همان صفحه را بارگذاری کرد، نه کل رکوردها را.
در این مثال که از Ef Code First و الگوی Unit Of Work استفاده کرده ام ، قصد نمایش اطلاعات در یک ListView و صفحه بندی آن را بصورت chunk chunk دارم.
برای آشنایی بیشتر با الگوی Unit Of Work  می‌توانید به مقاله UOW در همین سایت مراجعه کنید.
   
ابتدا یک کنترل ListView روی صفحه ایجاد می‌کنیم. برای آشنایی بیشتر با این کنترل و بررسی قابلیتهای آن میتوان از این مقاله معرفی کنترل‌های ListView و DataPager استفاده کنید.

سپس با توجه به الگوی Unit Of Work از یک مدل ساده استفاده می‌کنیم. همچنین برای بارگذاری اطلاعات به صورت صفحه به صفحه، نیاز به داشتن Index رکورد و PageSize، جهت محاسبه تعداد رکورد مورد نیاز داریم.

public class User
    {       
        public Int64 UserId { get; set; }       
        public String UserName { get; set; }
     }        
 public interface IUserService
    {        
        int GetCustomerCount();
        List<User> GetCustomers(int StartIndex, int PageSize);
    }
public class ImplUserService : IUserService
    {
        IUnitOfWork _uow;
        IDbSet<User> _user;

        public ImplUserService(IUnitOfWork uow)
        {
            _uow = uow;
            _user = uow.Set<User>();
        }

        public int GetCustomerCount()
        {
            int totalCount = _user.ToList().Count;             
            return totalCount;
        }

        public List<User> GetCustomers(int StartIndex, int PageSize)
        {
            return _user.OrderBy(i => i.UserId).Skip(StartIndex).Take(PageSize).ToList();
        }
  }
در کدهای بالا متد GetCustomerCount تعداد کل رکوردهایی که باید واکشی شوند را مشخص می‌کند. همچنین می‌توان این تعداد را در cache یا viewState ذخیره کرد تا در دفعات بعدی در صورت خالی نبودن مقدار Cacheبا viewState ، نیاز به محاسبه مجدد count نداشته باشیم.

متد GetCustomer که کار اصلی را انجام میدهد از دو پارامتر استفاده می‌کند: StartIndex نقطه شروع و PageSize تعداد رکورد مورد نظر. در اینجا از دستورات Linq استفاده شده و دستور Skip مشخص میکند از کدام شماره رکورد به بعد شروع به واکشی نماید و دستور Take مشخص می‌کند که چه تعداد رکورد را واکشی نماید.

حالا به سراغ کدهای HTML می‌رویم. در آنجا علاوه بر ListView نیاز به DataPager جهت صفحه بندی و Object DataSource جهت کنترل بارگذاری اطلاعات به صورت chunk chunk داریم.
<asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1">
            <ItemTemplate>
                <tr style="background-color: #DCDCDC; color: #000000;">
                    <td>
                        <asp:Label ID="UserIdLabel" runat="server" Text='<%# Eval("UserId") %>' />
                    </td>
                    <td>
                        <asp:Label ID="UserNameLabel" runat="server" Text='<%# Eval("UserName") %>' />
                    </td>
                </tr>
            </ItemTemplate>
            
            
            <LayoutTemplate>
                <table runat="server">
                    <tr runat="server">
                        <td runat="server">
                            <table id="itemPlaceholderContainer" runat="server" border="1" style="background-color: #FFFFFF;
                                border-collapse: collapse; border-color: #999999; border-style: none; border-width: 1px;">
                                <tr runat="server" style="background-color: #DCDCDC; color: #000000;">
                                    <th runat="server">
                                        UserId
                                    </th>
                                    <th runat="server">
                                        UserName
                                    </th>
                                </tr>
                                <tr id="itemPlaceholder" runat="server">
                                </tr>
                            </table>
                        </td>
                    </tr>
                    <tr runat="server">
                        <td runat="server" style="text-align: center; background-color: #CCCCCC;
                            color: #000000;">
                            <asp:DataPager ID="DataPager1" runat="server" PageSize="2">
                                <Fields>
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                    <asp:NumericPagerField />
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                </Fields>
                            </asp:DataPager>
                        </td>
                    </tr>
                </table>
            </LayoutTemplate>                        
        </asp:ListView>
همانظور که مشاهده میکنید در DataPager ، مقدار PageSize مشخص شده است.اما Object DataSource
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="True" SelectCountMethod="GetCustomerCount"
            SelectMethod="GetCustomers" TypeName="UserService.ImplUserService" EnableViewState="False"
            MaximumRowsParameterName="PageSize" StartRowIndexParameterName="StartIndex">
که فکر می‌کنم با توجه به متدهای تعریف شده در بالا ، تعریف Object DataSource کاملاٌ گویا میباشد.

نکته مهم:
اگر الان برنامه را اجرا کنید با خطای No parameterless constructor defined for this object ر وبرو خواهید شد که جهت حل این مشکل از کد زیر استفاده میکنیم.
protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {            
            e.ObjectInstance = ObjectFactory.GetInstance<IUserService>();            
        }
در واقع نیاز است تا یک وهله از کلاس مورد نظر را به Object DataSource معرفی کنیم.
حال با اجرای برنامه و Trace آن متوجه خواهید شد که با کلیک بر روی شماره صفحه، تنها به تعداد رکوردهای همان صفحه، واکشی خواهیم داشت.

با تشکر از راهنمایی‌های آقای نصیری، امیدوارم این مقاله برای دوستان مفید باشه. منتظر نظرات دوستان هستم و اینکه چه جوری بتونیم اینکار رو با jquery ajax و به صورت سبکتر انجام بدیم.