مطالب
چگونه یک عبارت sql را فرمت شده نمایش دهیم؟
در مورد کتابخانه‌ی SQLDom مطالبی را پیشتر در این سایت مطالعه کرده‌اید (^ و ^). یکی دیگر از کاربردهای آن، فرمت عبارات SQL است. برای مثال تبدیل عبارتی مانند
SELECT * FROM tb1 WHERE x1 = '12';
به نمونه‌ی فرمت شده‌ی آن:
SELECT *
FROM tb1
WHERE x1 = '12';
برای اینکار می‌توان از کلاس ذیل کمک گرفت:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;
 
namespace SqlDomAnalyzer.Core
{
    public static class PrettyPrintTSql
    {
        public static string FormatTSql(string tSql)
        {
            IList<ParseError> errors;
            TSqlScript sqlFragment;
            using (var reader = new StringReader(tSql))
            {
                var parser = new TSql120Parser(initialQuotedIdentifiers: true);
                sqlFragment = (TSqlScript)parser.Parse(reader, out errors);
            }
 
            if (errors != null && errors.Any())
            {
                var sb = new StringBuilder();
                foreach (var error in errors)
                    sb.AppendLine(error.Message);
 
                throw new InvalidOperationException(sb.ToString());
            }
 
            var sql110ScriptGenerator = new Sql120ScriptGenerator(new SqlScriptGeneratorOptions
            {
                SqlVersion = SqlVersion.Sql120
            });
            string finalScript;
            sql110ScriptGenerator.GenerateScript(sqlFragment, out  finalScript);
            return finalScript;
        }
    }
}
در اینجا ابتدا عبارت SQL ورودی Parse شده و سپس به کتابخانه‌ی تولید اسکریپت ScriptDom ارسال می‌شود. خروجی آن، یک خروجی فرمت شده‌است.

نکته‌ی جالب دیگری که در اینجا وجود دارد، تهیه‌ی یک خروجی همواره یک شکل است. برای نمونه سه عبارت SQL زیر را در نظر بگیرید:
SELECT * from tb1 WHERE x1 = '12';
SELECT * from tb1 where x1 = '12';
select * from tb1 WHERE x1 = '12';
در اینجا در عبارت اول، from با حروف کوچک نمایش داده شده‌است. در عبارت دوم، where نیز با حروف کوچک نمایش داده شده‌است و در عبارت سوم اینکار در مورد select نیز تکرار شده‌است.
در هر سه حالت یا هر حالت قابل تصور دیگری، خروجی SQL فرمت شده‌ی حاصل یک چنین شکلی را دارد:
SELECT *
FROM tb1
WHERE x1 = '12';

موارد کاربرد آن؟
علاوه بر نمایش زیبای SQL فرمت نشده، احتمالا برنامه‌های Profiler ایی را دیده‌اید که عنوان می‌کنند قادرند عبارات SQL همانند را تشخیص دهند (جهت یافتن Lazy loading اشتباه). یک چنین خروجی یکسانی، قابلیت تهیه Hash عبارات SQL دریافتی را میسر می‌کند؛ چون دیگر اینبار مهم نیست که اجزای تشکیل دهنده‌ی یک عبارت SQL با حروف بزرگ هستند یا کوچک و فاصله‌ی بین آن‌ها چقدر است و آیا در این بین خطوط جدیدی نیز وجود دارند و امثال آن. خروجی نهایی نرمال شده‌ی توسط Sql120ScriptGenerator همواره یک شکل است. از این دو قابلیت در برنامه‌ی DNTProfiler استفاده شده‌است.
نظرات مطالب
بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
به روز رسانی: روش توصیه شده‌ی مخصوص EF Core 2.0 جهت Log خروجی EF

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;

namespace EFLogging
{
    public class BloggingContextWithFiltering : DbContext
    {
        // It is very important that applications do not create a new ILoggerFactory instance for each context instance. 
        // Doing so will result in a memory leak and poor performance.
        public static readonly LoggerFactory MyLoggerFactory
            = new LoggerFactory(new[]
            {
                new ConsoleLoggerProvider((category, level)
                    => category == DbLoggerCategory.Database.Command.Name
                       && level == LogLevel.Information, true)
            });

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

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time
                .UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=EFLogging;Trusted_Connection=True;ConnectRetryCount=0");
    }
}
UseLoggerFactory روش توصیه شده‌ی EF Core 2.0 است و طول عمر وهله‌ی ارسالی به آن باید singleton باشد تا از بروز نشتی حافظه جلوگیری کند.
مطالب
نمایش تعداد کل صفحات در iTextSharp

در مورد نحوه‌ی نمایش شماره صفحه جاری در مثلا header یک گزارش PDF تهیه شده به کمک writer.PageNumber و ارث بری از کلاس PdfPageEventHelper،‌ در پایان مطلب فارسی نویسی در iTextSharp توضیح داده شد. این مورد جزو ضروریات یک گزارش خوب است، اما عموما نیاز است تا تعداد کل صفحات هم نمایش داده شود. مثلا صفحه n از 100 جایی در تمام صفحات درج شود و ... هیچ خاصیتی به نام TotalNumberOfPages را در این کتابخانه نمی‌توان یافت. علت هم این است که تعداد واقعی کل صفحات فقط در حین بسته شدن شیء Document مشخص می‌شود و نه در هنگام تهیه صفحات. بنابراین نکته تهیه و نمایش تعداد صفحات، در iTextSharp به صورت خلاصه به شرح زیر است:
الف) باید در همان کلاسی که از PdfPageEventHelper به ارث رسیده است، متد OnCloseDocument را تحریف (override) کرد. در اینجا به خاصیت writer.PageNumber دسترسی داریم و writer.PageNumber - 1 مساوی است با تعداد کل صفحات.
ب) در مرحله بعد نیاز است تا این عدد را به نحوی به تمام صفحات تولید شده اضافه کنیم. این کار هم ساده است و مبتنی است بر بکارگیری یک PdfTemplate :
  • در متد تحریف شده‌ی OnOpenDocument ، یک قالب PDF ساده را تولید می‌کنیم (مثلا یک مستطیل کوچک خالی).
  • سپس در متد OnEndPage ، این قالب را به انتهای تمام صفحات در حال تولید اضافه خواهیم کرد.
  • زمانیکه متد OnCloseDocument فراخوانده شد، عدد تعداد کل صفحات را در این قالب که به تمام صفحات اضافه شده، درج خواهیم کرد. به این ترتیب این عدد به صورت خودکار در تمام صفحات نمایش داده خواهد شد.

پیاده سازی این توضیحات را در ادامه ملاحظه خواهید کرد:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
public class PdfWriterPageEvents : PdfPageEventHelper
{
PdfContentByte _pdfContentByte;
// عدد نهایی تعداد کل صفحات را در این قالب قرار خواهیم داد
PdfTemplate _template;
BaseFont _baseFont;

public override void OnOpenDocument(PdfWriter writer, Document document)
{
_baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
_pdfContentByte = writer.DirectContent;
_template = _pdfContentByte.CreateTemplate(50, 50);
}

public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
String text = writer.PageNumber + "/";
float len = _baseFont.GetWidthPoint(text, 8);
Rectangle pageSize = document.PageSize;
_pdfContentByte.SetRGBColorFill(100, 100, 100);
_pdfContentByte.BeginText();
_pdfContentByte.SetFontAndSize(_baseFont, 8);
_pdfContentByte.SetTextMatrix(pageSize.GetLeft(40), pageSize.GetBottom(30));
_pdfContentByte.ShowText(text);
_pdfContentByte.EndText();
//در پایان هر صفحه یک جای خالی را مخصوص تعداد کل صفحات رزرو خواهیم کرد
_pdfContentByte.AddTemplate(_template, pageSize.GetLeft(40) + len, pageSize.GetBottom(30));
}
public override void OnCloseDocument(PdfWriter writer, Document document)
{
base.OnCloseDocument(writer, document);
_template.BeginText();
_template.SetFontAndSize(_baseFont, 8);
_template.SetTextMatrix(0, 0);
//درج تعداد کل صفحات در تمام قالب‌های اضافه شده
_template.ShowText((writer.PageNumber - 1).ToString());
_template.EndText();
}
}

public class AddTotalNoPages
{
public static void CreateTestPdf()
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("tpn.pdf", FileMode.Create));
pdfWriter.PageEvent = new PdfWriterPageEvents();
pdfDoc.Open();


pdfDoc.Add(new Phrase("Page1"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("Page2"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("Page3"));
}
}
}
}

مطالب
OpenCVSharp #2
کتابخانه‌ی اصلی OpenCV، دارای دو نوع اینترفیس C و ++C است. اینترفیس C آن مرتبط است به نگارش‌های 1x آن و اینترفیس ++C آن به همراه نگارش‌های 2x آن ارائه شده‌اند. کتابخانه‌ی OpenCVSharp هر دو نوع اینترفیس یاد شده را پشتیبانی می‌کند. در این قسمت نگاهی خواهیم داشت به نحوه‌ی بارگذاری و نمایش تصاویر در OpenCV به کمک متدهای اینترفیس C آن، مانند cvLoadImage، cvShowImage، cvReleaseImage.


بارگذاری و نمایش تصاویر به کمک OpenCVSharp

متدهای اینترفیس C مربوط به OpenCV، در OpenCVSharp با ذکر کلاس Cv آن قابل دسترسی هستند. برای نمونه متدهای C یاد شده‌ی در ابتدای بحث، چنین معادلی را در OpenCVSharp دارند:
using OpenCvSharp;
 
namespace OpenCVSharpSample02
{
  class Program
  {
   static void Main(string[] args)
   {
    var img = Cv.LoadImage(@"..\..\images\ocv02.jpg");
 
    Cv.NamedWindow("window");
    Cv.ShowImage("window", img);
 
    Cv.WaitKey();
 
    Cv.DestroyWindow("window");
 
    Cv.ReleaseImage(img);
   }
  }
}
متد cvLoadImage اینترفیس C، به Cv.LoadImage تبدیل شده‌است و مابقی نیز به همین ترتیب.
در اینجا با استفاده از متد LoadImage، تصویری را از مسیر مشخصی، بارگذاری می‌کنیم. سپس یک پنجره‌ی OpenCV ایجاد و این تصویر در آن نمایش داده می‌شود. متد WaitKey منتظر فشرده شدن یک کلید بر روی پنجره‌ی OpenCV می‌شود. پس از آن این پنجره تخریب و همچنین منابع native این تصویر آزاد می‌شوند.


متد LoadImage، پارامتر دومی را نیز می‌پذیرد:
 var img = Cv.LoadImage(@"..\..\images\ocv02.jpg", LoadMode.GrayScale);
برای مثال در اینجا می‌توان به کمک مقدار LoadMode.GrayScale، تصویر را به صورت سیاه و سفید بارگذاری کرد.
Enum تعریف شده‌ی در اینجا قابلیت or یا جمع منطقی را نیز دارد. برای مثال می‌توان مقدار  LoadMode.AnyColor | LoadMode.AnyDepth را نیز مشخص کرد؛ جهت بارگذاری تصویر اصلی با مشخصات کامل آن که حالت پیش فرض است.


کلاس‌های پشت صحنه‌ی اینترفیس C در OpenCVSharp

علت وجود کلاس Cv در OpenCVSharp، سهولت برگرداندن مثال‌های C کتابخانه‌ی OpenCV به نمونه‌ها‌ی دات نتی است. اما اگر قصد داشته باشید از کلاس‌های پشت صحنه‌ی این اینترفیس در OpenCVSharp استفاده کنید، می‌توان کدهای فوق را به نحو ذیل نیز بازنویسی کرد:
using (var img = new IplImage(@"..\..\images\ocv02.jpg", LoadMode.Unchanged))
{
  using (var window = new CvWindow("window"))
  {
   window.Image = img;
   Cv.WaitKey();
  }
}
خروجی متد LoadImage از نوع کلاس IplImage است. در اینجا می‌توان همین کلاس را وهله سازی کرد و مورد استفاده قرار داد. به علاوه اینبار این کلاس تهیه شده، اینترفیس IDisposable را نیز پیاده سازی می‌کند. بنابراین می‌توان با استفاده از عبارت using کار آزاد سازی منابع آن‌را خودکار کرد.
همچنین پنجره‌ی OpenCV نیز در اینجا با کلاس CvWindow پیاده سازی می‌شود که این کلاس نیز اینترفیس IDisposable را پیاده سازی می‌کند.


یک نکته‌ی تکمیلی

اگر متد LoadImage کتابخانه‌ی OpenCV قادر به بارگذاری تصویر شما نبود، متد دیگری به نام IplImage.FromFile نیز پیش بینی شده‌است. این متد از امکانات System.Drawing.Bitmap دات نت برای بارگذاری تصویر و تبدیل آن به فرمت OpenCV استفاده می‌کند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
نوشتن TagHelperهای سفارشی برای ASP.NET Core
پیاده سازی  TagHelper سفارشی ImageGravatar  جهت نمایش آواتار کاربر از سایت gravatar 


پیاده سازی کلاس GarvatarTagHelper
[HtmlTargetElement("img-gravatar")]
    public class GravatarTagHelper : TagHelper
    {
        [HtmlAttributeName("email")]
        public string Email { get; set; }

        [HtmlAttributeName("alt")]
        public string Alt { get; set; }

        [HtmlAttributeName("class")]
        public string Class { get; set; }

        [HtmlAttributeName("size")]
        public int Size { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (!string.IsNullOrWhiteSpace(Email))
            {
                var hash = Md5HashHelper.GetHash(Email);

                output.TagName = "img";
                if (!string.IsNullOrWhiteSpace(Class))
                {
                    output.Attributes.Add("class", Class); 
                }

                if (!string.IsNullOrWhiteSpace(Alt))
                {
                    output.Attributes.Add("alt", Alt);
                }
                
                output.Attributes.Add("src", GetAvatarUrl(hash, Size));
                output.TagMode = TagMode.SelfClosing;
            } 
        }

        private static string GetAvatarUrl(string hash, int size)
        {
            var sizeArg = size > 0 ? $"?s={size}" : "";

            return $"https://www.gravatar.com/avatar/{hash}{sizeArg}";
        }
 
استفاده از TagHepler  با مقدار  Email فرد در View مورد نظر
 <img-gravatar email="@Model.Email" class="img-thumbnail" size="150" />

مطالب
خلاصه‌ای از مبحث نمایش اطلاعات hierarchical در WPF

در این مطلب خلاصه‌ای را در مورد نحوه‌ی نمایش اطلاعات hierarchical (سلسله مراتبی، درختی) در WPF به همراه یک سری لینک مرتبط ملاحظه خواهید نمود.

کلاس زیر را در نظر بگیرید:
using System.Collections.Generic;

namespace WpfTests.Hierarchy.Raw.Model
{
public class Person
{
private readonly List<Person> _children = new List<Person>();
public IList<Person> Children
{
get { return _children; }
}

public string Name { get; set; }
}
}
و همچنین یک ObservableCollection ساخته شده از آن‌را با مقدار دهی اولیه:
using System.Collections.ObjectModel;

namespace WpfTests.Hierarchy.Raw.Model
{
public class People : ObservableCollection<Person>
{
public People()
{
this.Add(
new Person
{
Name = "P1",
Children =
{
new Person
{
Name="P2",
Children=
{
new Person
{
Name="P3",
Children=
{
new Person
{
Name="P4",
}
}
}
}
}
}
}
);
}
}
}
قصد داریم این اطلاعات را در یک TreeView نمایش دهیم.
روش صحیح Binding این نوع اطلاعات در WPF استفاده از HierarchicalDataTemplate است به صورت زیر :
<TreeView ItemsSource="{Binding People}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>


یک سری منبع آموزشی برای آشنایی بیشتر با HierarchicalDataTemplate
Hierarchical Databinding in WPF
Binding WPF Treeview and Objects
A TreeView, a HierarchicalDataTemplate, and a 2D collection
Non-recursive WPF TreeView controls

همچنین هنگام کار با بانک‌های اطلاعاتی:
- یک Extension method عالی قابل استفاده در LINQ to SQL و همچنین Entity framework به نام AsHierarchy
- مثالی دیگر از کاربرد LINQ to SQL برای این منظور
- و یا مثالی از ADO.NET و DataSets و مثالی دیگر

نظرات مطالب
EF Code First #12
به این ترتیب باید پیاده سازی بشه. یک حالت عمومی است و به کلاس و شیء خاصی گره نخورده:
using System;
using System.ComponentModel.DataAnnotations;
namespace Test
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class CompareAttribute : ValidationAttribute
    {
        public CompareAttribute(string originalProperty, string confirmProperty)
        {
            OriginalProperty = originalProperty;
            ConfirmProperty = confirmProperty;
        }
        public string ConfirmProperty { get; private set; }
        public string OriginalProperty { get; private set; }
        protected override ValidationResult IsValid(object value, ValidationContext ctx)
        {
            if (value == null)
                return new ValidationResult("لطفا فیلدها را تکمیل نمائید");
            var confirmProperty = ctx.ObjectType.GetProperty(ConfirmProperty);
            if (confirmProperty == null)
                throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", ConfirmProperty));
            var confirmValue = confirmProperty.GetValue(ctx.ObjectInstance, null) as string;
            if (string.IsNullOrWhiteSpace(confirmValue))
                return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", ConfirmProperty));
            var originalProperty = ctx.ObjectType.GetProperty(OriginalProperty);
            if (originalProperty == null)
                throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", OriginalProperty));
            var originalValue = originalProperty.GetValue(ctx.ObjectInstance, null) as string;
            if (string.IsNullOrWhiteSpace(originalValue))
                return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", OriginalProperty));
            return originalValue == confirmValue ? ValidationResult.Success : new ValidationResult("مقادیر وارد شده یکسان نیستند");
        }
    }
}

مطالب
گروه بندی اطلاعات و گزارشات Master-Details در PdfReport
اگر به بانک اطلاعاتی مثال‌های همراه سورس‌های PdfReport در مسیر Bin\Data\blogs.sqlite مراجعه کنید، دو جدول والدین و فرزندان هم در آن وجود دارند:



بر این اساس قصد داریم رابطه یک به چند فوق را گروه بندی شده نمایش دهیم:


(البته این اعداد و اطلاعات، به صورت اتفاقی تولید شده‌اند و الزامی ندارد که والد متولد 2002 هنوز والد شده باشد؛ یا اینکه فرزندی متولد 2003 داشته باشد!)

بنابراین صورت مساله ما به این ترتیب خواهد بود:
بر اساس اطلاعات دو جدول والدین و فرزندان فوق، اطلاعات نهایی را در جداول مجزایی بر اساس والدین و فرزندان آن‌ها گروه بندی نمائید.

سورس کامل این مثال را در ادامه مشاهده می‌کنید:
using System;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.MasterDetails
{
    public class MasterDetailsPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\arial.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(new MasterDetailsHeaders { PdfRptFont = header.PdfFont });
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .MainTableTemplate(t => t.BasicTemplate(BasicTemplate.SilverTemplate))
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.GroupsPreferences(new GroupsPreferences
                {
                    GroupType = GroupType.HideGroupingColumns,
                    RepeatHeaderRowPerGroup = true,
                    ShowOneGroupPerPage = false,
                    SpacingBeforeAllGroupsSummary = 5f,
                    NewGroupAvailableSpacingThreshold = 170
                });
            })
            .MainTableDataSource(dataSource =>
            {
                dataSource.GenericDataReader(
                   providerName: "System.Data.SQLite",
                   connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                   sql: @"select 
                            tblParents.BirthDate as ParentBirthDate,
                            tblParents.Name as ParentName,
                            tblParents.LastName as ParentLastName,
                            tblKids.Name as KidName,
                            tblKids.BirthDate as KidBirthDate
                            from tblParents
                                left outer join tblKids
                                     on tblKids.ParentId = tblParents.Id
                            order by 
                                tblParents.Name,
                                tblParents.LastName,
                                tblKids.Name"
               );
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentBirthDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(1);
                    column.Width(2);
                    column.HeaderCell("ParentBirthDate");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        var date1 = (DateTime)val1;
                        var date2 = (DateTime)val2;
                        return date1.Year == date2.Year && date1.Month == date2.Month && date1.Day == date2.Day;
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(2);
                    column.Width(2);
                    column.HeaderCell("ParentName");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentLastName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("ParentLastName");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("KidName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(4);
                    column.Width(2);
                    column.HeaderCell("Child Name");
                    column.IsVisible(true);
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("KidBirthDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(5);
                    column.Width(2);
                    column.HeaderCell("BirthDate");
                    column.IsVisible(true);
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(e => e.ToExcel())
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptMasterDetailsSample.pdf"));
        }
    }
}
به همراه سر ستون‌های مجزای هر گروه و صفحه:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.ColumnsItemsTemplates;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.MasterDetails
{
    public class MasterDetailsHeaders : IPageHeader
    {
        public IPdfFont PdfRptFont { set; get; }

        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData)
        {
            var parentName = newGroupInfo.GetSafeStringValueOf("ParentName");
            var parentLastName = newGroupInfo.GetSafeStringValueOf("ParentLastName");
            var parentBirthDate = newGroupInfo.GetSafeStringValueOf("ParentBirthDate");

            var table = new PdfPTable(relativeWidths: new[] { 1f, 5f }) { WidthPercentage = 100 };
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Last Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentLastName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.Value = "Birth Date:";
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
               },
               (cellData, cellProperties) =>
               {
                   cellData.Value = parentBirthDate;
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
               });
            return table.AddBorderToTable(borderColor: BaseColor.LIGHT_GRAY, spacingBefore: 5f);
        }

        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            var table = new PdfPTable(numColumns: 1) { WidthPercentage = 100 };
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.CellTemplate = new ImageFilePathField();
                   cellData.Value = AppPath.ApplicationPath + "\\Images\\01.png";
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Center;
               });
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.Value = "Family rpt";
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Center;
               });
            return table.AddBorderToTable();
        }
    }
}
توضیحات:
- منبع داده مورد استفاده در اینجا از نوع GenericDataReader است؛ جهت خواندن رکوردهای بانک اطلاعاتی SQLite ذکر شده در ابتدای بحث. (دو مثال دیگر هم به پوشه مثال‌های سورس‌های PdfReport اضافه شده‌اند به نام‌های Grouping و WrapGroupsInColumns که به همین موضوع گروه بندی می‌پردازند؛ البته با استفاده از StronglyTypedList‌ها. ولی درکل مفاهیم و اصول آن‌ها یکی است.)
select 
          tblParents.BirthDate as ParentBirthDate,
          tblParents.Name as ParentName,
          tblParents.LastName as ParentLastName,
          tblKids.Name as KidName,
          tblKids.BirthDate as KidBirthDate
          from tblParents
                  left outer join tblKids
                        on tblKids.ParentId = tblParents.Id
                            order by 
                                tblParents.Name,
                                tblParents.LastName,
                                tblKids.Name
در کوئری فوق (و کلا گروه بندی اطلاعات) دو نکته حائز اهمیت است:
الف) چون قرار است اطلاعات بر اساس مشخصات والدین و فرزندان آن‌ها گروه بندی شود، نیاز است حتما order by  و مرتب سازی رکوردها قید گردد.
ب) در PdfReport نمی‌توانید در خواص معرفی شده جهت تعریف ستون‌ها، از نام‌های تکراری استفاده کنید. برای رفع این مشکل استفاده از Alias پیشنهاد می‌شود؛ مانند:
tblParents.Name as ParentName,
tblKids.Name as KidName,
- مشخص سازی خاصیت و ستونی که قرار است در گروه بندی شرکت کند بسیار ساده است:
column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
در اینجا به کمک متد Group، قابلیت گروه بندی بر روی این ستون فعال شده و سپس باید فرمولی را جهت مشخص سازی حد و مرز گروه مشخص کنیم. برای مثال در اینجا اگر مقادیر ردیف جاری (val2) و ردیف قبلی (val1) یکسان نبودند، یعنی گروه خاتمه یافته و گروه جدیدی شروع می‌شود (به همین جهت عنوان شد که مرتب سازی اطلاعات ضروری است).

- تنظیم دیگری را که در اینجا می‌توان ذکر کرد، مورد ذیل است:
                table.GroupsPreferences(new GroupsPreferences
                {
                    GroupType = GroupType.HideGroupingColumns,
                    RepeatHeaderRowPerGroup = true,
                    ShowOneGroupPerPage = false,
                    SpacingBeforeAllGroupsSummary = 5f,
                    NewGroupAvailableSpacingThreshold = 170
                });
به این ترتیب می‌توان مشخص کرد که آیا باید ستون‌های دخیل در گروه بندی، در گزارش نمایش داده شوند یا خیر (GroupType.HideGroupingColumns)، آیا سر ستون هر جدول، به ازای هر گروه باید تکرار شود؟ (RepeatHeaderRowPerGroup)، آیا در هر صفحه یک گروه نمایش داده شود (ShowOneGroupPerPage) یا اینکه گروه‌ها به صورت متوالی در صفحات درج شوند. توسط SpacingBeforeAllGroupsSummary، فاصله جمع نهایی تمام گروه‌ها از آخرین گروه نمایش داده شده مشخص می‌شود. به کمک NewGroupAvailableSpacingThreshold مشخص می‌کنیم که در چه فاصله‌ای از انتهای صفحه، گروه جدیدی نباید درج شود و این گروه باید به صفحه بعدی منتقل شده و از آنجا شروع شود.

- اگر به تصویر ابتدای مطلب دقت کرده باشید، علاوه بر هدر صفحه، هر گروه نیز یک هدر مجزا دارد. برای طراحی آن باید اینترفیس IPageHeader را پیاده سازی کرد که نمونه‌ای از آن‌را در کلاس MasterDetailsHeaders فوق مشاهده می‌کنید.
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData)
        {
            var parentName = newGroupInfo.GetSafeStringValueOf("ParentName");
            var parentLastName = newGroupInfo.GetSafeStringValueOf("ParentLastName");
            var parentBirthDate = newGroupInfo.GetSafeStringValueOf("ParentBirthDate");

            var table = new PdfPTable(relativeWidths: new[] { 1f, 5f }) { WidthPercentage = 100 };
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
ساختار آن هم بسیار ساده است. توسط  newGroupInfo می‌توان به اطلاعات گروه جدید، دسترسی یافت. برای مثال در اینجا اطلاعات والد گروه جدید در حال تهیه، دریافت شده و سپس در ردیف‌های یک جدول دو ستونه درج می‌شود. در ستون اول آن یک برچسب و در ستون دوم، مقدار دریافتی نمایش داده شده است و همینطور الی آخر برای سایر ردیف‌ها.
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش اول

ObsoleteAttribute

ObsoleteAttribute بر روی تمامی عناصر یک برنامه بجز assemblies, modules، پارامترها و مقادیر بازگشتی قابل استفاده است. علامتگذاری یک عنصر به عنوان منسوخ شده، به کاربر استفاده کننده اطلاع می‌دهد که این عنصر در نسخه‌های آینده حذف خواهد شد.

با استفاده از پروپرتی Message آن پیامی را به کاربر استفاده کننده نشان خواهد داد و توصیه می‌شود در این پیام یک راه حل نیز ارائه شود.

پروپرتی IsError در صورتی که مقدار آن به true تعیین شده باشد و کامپایلر در صورتی که عنصری که این خصوصیت بر روی آن تعریف شده است، استفاده شده باشد، در پنجره Error List، پیام مربوط به Obsolete را نشان می‌دهد. برای مثال پس از استفاده از کلاس زیر، OrderDetailTotal به صورت warning و CalculateOrderDetailTotal به صورت Error در پنجره Error List نشان داده می‌شود.

public static class ObsoleteExample
{
    // Mark OrderDetailTotal As Obsolete.
    [ObsoleteAttribute("This property (OrderDetailTotal) is obsolete. Use InvoiceTotal instead.", false)]
    public static decimal OrderDetailTotal
    {
        get  {  return 12m; }
    }

    public static decimal InvoiceTotal
    {
        get  {  return 25m;  }
    }

    // Mark CalculateOrderDetailTotal As Obsolete.
    [ObsoleteAttribute("This method is obsolete. Call CalculateInvoiceTotal instead.", true)]
    public static decimal CalculateOrderDetailTotal()
    {
        return 0m;
    }

    public static decimal CalculateInvoiceTotal()
    {
        return 1m;
    }
}

DefaultValueAttribute

DefaultValueAttribute جهت تعیین مقدار پیش فرض یک پروپرتی استفاده می‌شود. شما می‌توانید یک DefaultValueAttribute را با هر مقداری ایجاد کنید. ایجاد مقدار پیش فرض برای یک پروپرتی باعث نمی‌شود که مقداردهی اولیه‌ای به آن انجام گیرد؛ برای این کار نیاز به کدنویسی می‌باشد.
مثال زیر نحوه استفاده و مقداردهی اولیه پروپرتی‌ها را نشان می‌دهد.
public class DefaultValueAttributeTest
{
    public DefaultValueAttributeTest()
    {
        // Use the DefaultValue propety of each property to actually set it, via reflection.
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
        {
            var attr = prop.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute;
            if (attr != null)
                prop.SetValue(this, attr.Value);
        }
    }

    [DefaultValue(28)]
    public int Age { get; set; }

    [DefaultValue("Vahid")]
    public string FirstName { get; set; }

    [DefaultValue("Mohammad Taheri")]
    public string LastName { get; set; }

    public override string ToString()
    {
        return $"{this.FirstName} {this.LastName} is {this.Age}.";
    }
}

DebuggerBrowsableAttribute 

در صورت استفاده از DebuggerBrowsableAttribute ، شما می‌توانید نحوه نمایش یک عضو را در پنجره متغیرها، در زمان دیباگ، تعیین کنید.
public class DebuggerBrowsableTest
{
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] // عدم نمایش در زمان دیباگ در پنجره متغیرها
    public string FirstName { get; set; }

    [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] // مقدار پیش فرض
    public string LastName { get; set; }

    [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // عدم نمایش در زمان دیباگ در پنجره متغیرها
    public string FullName => FirstName + " " + LastName;

    [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // تنها در زمانی که یک آرایه یا لیست باشد نمایش داده می‌شود
    public string[] FullNameArray => new string[] { FirstName + " " + LastName };
}

 اگر از کد مثال بالا استفاده کنید و با استفاده از کلید F11 به صورت خط به خط دستورات را اجرا کنید، مشاهده خواهید کرد متغیر FirstName و FullName در پنجره Autos نشان داده نخواهد شد.

 

Operator ??

عملگر ??  در صورتی که عملوند سمت چپ آن تهی (null) نباشد، مقدار آن را باز می‌گرداند و در غیر اینصورت مقدار عملوند سمت راست خود را باز می‌گرداند. نوع‌های تهی پذیر (nullable) می‌توانند دارای مقدار و یا به صورت تعریف نشده باشند. عملگر ?? وقتی که یک نوع تهی پذیر به یک نوع غیرتهی پذیر انتساب داده می‌شود، مقدار پیش فرض آن را باز می‌گرداند.

int? x = null;
int y = x ?? -1;
Console.WriteLine("y now equals -1 because x was null => {0}", y);
int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int);
Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i);
string s = DefaultValueOperatorTest.GetStringValue();
Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
مطالب
مروری بر مفاهیم مقدماتی NoSQL
هدف از این مبحث، آشنایی با مفاهیم پایه‌ای اغلب بانک‌های اطلاعاتی NoSQL است که به صورت مشترکی در تمام آن‌ها بکار رفته است. برای مثال بانک‌های اطلاعاتی NoSQL چگونه مباحث یکپارچگی اطلاعات را مدیریت می‌کنند؟ نحوه ایندکس نمودن اطلاعات در آن‌ها چگونه است؟ چگونه از اطلاعات کوئری می‌گیرند؟ الگوریتم‌های محاسباتی مانند MapReduce چیستند و چگونه در اینگونه بانک‌های اطلاعاتی بکار رفته‌‌اند؟ همچنین الگوهای Sharding و Partitioning  که در اغلب بانک‌های اطلاعاتی NoSQL مشترکند، به چه نحوی پیاده سازی شده‌اند.


لیست مشترکات بانک‌های اطلاعاتی NoSQL

قبل از اینکه بخواهیم وارد ریز جزئیات بانک‌های اطلاعاتی NoSQL شویم، نیاز است لیست و سرفصلی از مفاهیم اصلی و مشترک بین اینگونه بانک‌های اطلاعاتی را تدارک ببینیم که شامل موارد ذیل می‌شود:

الف) Non-Relational یا غیر رابطه‌ای
از کلمه NoSQL عموما اینطور برداشت می‌شود که در اینجا دیگر خبری از SQL نویسی نیست که در عمل برداشت نادرستی است. شاید جالب باشد که بدانید، تعدادی از بانک‌های اطلاعاتی NoSQL از زبان SQL نیز به عنوان اینترفیسی برای نوشتن کوئری‌های مرتبط، پشتیبانی می‌کنند.
کلمه NoSQL بیشتر به Non-Relational یا غیر رابطه‌ای بودن اینگونه بانک‌های اطلاعاتی بر می‌گردد. مباحثی مانند مدل‌های داده‌ای نرمال شده، اتصالات و Join جداول، در دنیای NoSQL وجود خارجی ندارند.

ب) Non-schematized/schema free یا بدون اسکیما
مفهوم مهم و مشترک دیگری که در بین بانک‌های اطلاعاتی NoSQL وجود دارد، بدون اسکیما بودن اطلاعات آن‌ها است. به این معنا که با حرکت از رکورد یک به رکورد دو،  ممکن است با دو ساختار داده‌ای متفاوت مواجه شوید.

ج) Eventual consistency یا عاقبت یک دست شدن
عاقبت یک دست شدن، به معنای دریافت دستوری از شما و نحوه پاسخ دادن به آن (یا حتی پاسخ ندادن به آن) از طرف بانک اطلاعاتی NoSQL است. برای مثال، زمانیکه یک رکورد جدید را اضافه می‌کنید، یا اطلاعات موجودی را به روز رسانی خواهید کرد، اغلب بانک‌های اطلاعاتی NoSQL این دستور را بسیار سریع دریافت و پردازش خواهند کرد. اما تفاوت است بین دریافت پیام و پردازش واقعی آن در اینجا.
اکثر بانک‌های اطلاعاتی NoSQL، پردازش و اعمال واقعی دستورات دریافتی را با یک تاخیر انجام می‌دهند. به این ترتیب می‌توان خیلی سریع به بانک اطلاعاتی اعلام کرد که چه می‌خواهیم و بانک اطلاعاتی بلافاصله مجددا کنترل را به شما بازخواهد گرداند. اما اعمال و انتشار واقعی این دستور، مدتی زمان خواهد برد.

د) Open source یا منبع باز بودن
اغلب بانک‌های اطلاعاتی NoSQL موجود، منبع باز هستند که علاوه بر بهره بردن از مزایای اینگونه پروژه‌ها، استفاده کنندگان سورس باز دیگری را نیز ترغیب به استفاده از آن‌ها کرده‌اند.

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

و) Web scale یا مناسب برای برنامه‌های تحت وب پر کاربر
امروزه بسیاری از کمپانی‌های بزرگ اینترنتی، برای مدیریت تعداد بالایی از کاربران همزمان خود، مانند فیس‌بوک، یاهو، گوگل، Linkedin، مایکروسافت و غیره، نیاز به بانک‌های اطلاعاتی پیدا کرده‌اند که باید در مقابل این حجم عظیم درخواست‌ها و همچنین اطلاعاتی که دارند، بسیار بسیار سریع پاسخ دهند. به همین جهت بانک‌های اطلاعاتی NoSQL ابداع شده‌اند تا بتوان برای این نوع سناریوها پاسخی را ارائه داد.
و نکته مهم دیگر اینجا است که خود این کمپانی‌های بزرگ اینترنتی، بزرگترین توسعه دهنده‌های بانک‌های اطلاعاتی NoSQL نیز هستند.



نحوه مدیریت یکپارچگی اطلاعات در بانک‌های اطلاعاتی NoSQL

مدیریت یکپارچگی اطلاعات بانک‌های اطلاعاتی NoSQL به علت ذات و طراحی توزیع شده آن‌ها، با نحوه مدیریت یکپارچگی اطلاعات بانک‌های اطلاعاتی رابطه‌ای متفاوت است. اینجا است که تئوری خاصی به نام CAP مطرح می‌شود که شامل یکپارچگی یا Consistency به همراه Availability یا دسترسی پذیری (همیشه برقرار بودن) و partition tolerance یا توزیع پذیری است. در تئوری CAP مطرح می‌شود که هر بانک اطلاعاتی خاص، تنها دو مورد از سه مورد مطرح شده را می‌تواند با هم پوشش دهد.
به این ترتیب بانک‌های اطلاعاتی رابطه‌ای عموما دو مورد C و P یا یکپارچگی (Consistency) و partition tolerance یا میزان تحمل تقسیم شدن اطلاعات را ارائه می‌دهند. اما بانک‌های اطلاعاتی NoSQL از این تئوری، تنها دو مورد A و P را پوشش می‌دهند (دسترسی پذیری و توزیع پذیری مطلوب).
بنابراین مفهومی به نام ACID که در بانک‌های اطلاعاتی رابطه‌ای ضامن یکپارچگی اطلاعات آن‌ها است، در دنیای NoSQL وجود خارجی ندارد. کلمه ACID مخفف موارد ذیل است:
Atomicity، Consistency، Isolation و Durability
ACID در بانک‌های اطلاعاتی رابطه‌ای تضمین شده است. در این نوع سیستم‌ها، با ایجاد تراکنش‌ها، مباحث ایزوله سازی و یکپارچگی اطلاعات به نحو مطلوبی مدیریت می‌گردد؛ اما دنیای NoSQL، دسترسی پذیری را به یکپارچگی ترجیح داده است و به همین جهت پیشتر مطرح شد که مفهوم «Eventual consistency یا عاقبت یک دست شدن» در این نوع بانک‌های اطلاعاتی در پشت صحنه بکار گرفته می‌شود. یک مثال دنیای واقعی از عاقبت یک دست شدن اطلاعات را حتما در مباحث DNS مطالعه کرده‌اید. زمانیکه یک رکورد DNS اضافه می‌شود یا به روز خواهد شد، اعمال این دستورات در سراسر دنیا به یکباره و همزمان نیست. هرچند اعمال این اطلاعات جدید در یک نود شبکه ممکن است آنی باشد، اما پخش و توزیع آن در سراسر سرورهای DNS دنیا، مدتی زمان خواهد برد (گاهی تا یک روز یا بیشتر).
به همین جهت است که بانک‌های اطلاعاتی رابطه‌ای در حجم‌های عظیم اطلاعات و تعداد کاربران همزمان بالا، کند عمل می‌کنند. حجم اطلاعات بالا است، مدتی زمان خواهد برد تا تغییرات اعمال شوند، و چون مفهوم ACID در این نوع بانک‌های اطلاعاتی تضمین شده است، کاربران باید مدتی منتظر بمانند و نمونه‌ای از آن‌ها را با dead lockهای شایع، احتمالا پیشتر بررسی یا تجربه کرده‌اید. در مقابل، بانک‌های اطلاعاتی NoSQL بجای یکپارچگی، دسترسی پذیری را اولویت اول خود می‌دانند و نه یکپارچگی اطلاعات را. در یک بانک اطلاعاتی NoSQL، دستور ثبت اطلاعات دریافت می‌شود (این مرحله آنی است)، اما اعمال نهایی آن آنی نیست و مدتی زمان خواهد برد تا تمام اطلاعات در کلیه سرورها یک دست شوند.



نحوه مدیریت Indexing اطلاعات در بانک‌های اطلاعاتی NoSQL

اغلب بانک‌های اطلاعاتی NoSQL تنها بر اساس اطلاعات کلیدهای اصلی جداول آن‌ها index می‌شوند (البته نام خاصی به نام «جدول»، بسته به نوع بانک اطلاعاتی NoSQL ممکن است متفاوت باشد، اما منظور ظرف دربرگیرنده تعدادی رکورد است در اینجا). این ایندکس نیز از نوع clustered است. به این معنا که اطلاعات به صورت فیزیکی، بر همین مبنا ذخیره و مرتب خواهند شد.
یک مثال: بانک اطلاعاتی NoSQL خاصی به نام Hbase که بر فراز Hadoop distributed file system طراحی شده است، دقیقا به همین روش عمل می‌کند. این فایل سیستم، تنها از روش Append only برای ذخیره سازی اطلاعات استفاده می‌کند و در آن مفهوم دسترسی اتفاقی یا random access پیاده سازی نشده است. در این حالت، تمام نوشتن‌ها در بافر، لاگ می‌شوند و در بازه‌های زمانی متناوب و مشخصی سبب باز تولید فایل‌های موجود و مرتب سازی مجدد آن‌ها از ابتدا خواهند شد. دسترسی به این اطلاعات پس از تکمیل نوشتن، به علت مرتب سازی فیزیکی که صورت گرفته، بسیار سریع است. همچنین مصرف کننده سیستم نیز چون بلافاصله پس از ثبت اطلاعات در بافر سیستم، کنترل را به دست می‌گیرد، احساس کار با سیستمی را خواهد داشت که بسیار سریع است.
به علاوه Indexهای دیگری نیز وجود دارند که بر اساس کلیدهای اصلی جداول تولید نمی‌شوند و به آن‌ها ایندکس‌های ثانویه یا secondary indexes نیز گفته می‌شود و تنها تعداد محدودی از بانک‌های اطلاعاتی NoSQL از آن‌ها پشتیبانی می‌کنند. این مساله هم از اینجا ناشی می‌شود که با توجه به بدون اسکیما بودن جداول بانک‌های اطلاعاتی NoSQL، چگونه می‌توان اطلاعاتی را ایندکس کرد که ممکن است در رکورد دیگری، ساختار متناظر با آن اصلا وجود خارجی نداشته باشد.



نحوه پردازش Queries در بانک‌های اطلاعاتی NoSQL

بانک‌های اطلاعاتی NoSQL عموما از زبان کوئری خاصی پشتیبانی نمی‌کنند. در اینجا باید به اطلاعات به شکل فایل‌هایی که حاوی رکوردها هستند نگاه کرد. به این ترتیب برای پردازش و یافتن اطلاعات درون این فایل‌ها، نیاز به ایجاد برنامه‌هایی است که این فایل‌ها را گشوده و بر اساس منطق خاصی، اطلاعات مورد نظر را استخراج کنند. گاهی از اوقات زبان SQL نیز پشتیبانی می‌شود ولی آنچنان عمومیت ندارد. الگوریتمی که در این برنامه‌ها بکار گرفته می‌شود، Map Reduce نام دارد.
Map Reduce به معنای نوشتن کدی است، با دو تابع. اولین تابع اصطلاحا Map step یا مرحله نگاشت نام دارد. در این مرحله کوئری به قسمت‌های کوچکتری خرد شده و بر روی سیستم‌های توزیع شده به صورت موازی اجرا می‌شود. مرحله بعد Reduce step نام دارد که در آن، نتیجه دریافتی حاصل از کوئری‌های اجرا شده بر روی سیستم‌های مختلف، با هم یکی خواهند شد.
این روش برای نمونه در سیستم Hadoop بسیار مرسوم است. Hadoop دارای یک فایل سیستم توزیع شده است (که پیشتر در مورد آن بحث شد) به همراه یک موتور Map Reduce توکار. همچنین رده دیگری از بانک‌های اطلاعاتی NoSQL، اصطلاحا Wide column store نام دارند (مانند Hbase) که عموما به همراه Hadoop بکارگرفته می‌شوند. موتور Map Reduce متعلق به Hadoop بر روی جداول Hbase اجرا می‌شوند.
به علاوه Amazon web services دارای سرویسی است به نام Elastic map reduce یا EMR که در حقیقت مجموعه‌ی پردازش ابری است که بر مبنای Hadoop کار می‌کند. این سرویس قادر است با بانک‌های اطلاعاتی NoSQL دیگر و یا حتی بانک‌های اطلاعاتی رابطه‌ای نیز کار کند.
بنابراین MapReduce، یک بانک اطلاعاتی نیست؛ بلکه یک روش پردازش اطلاعات است که فایل‌ها را به عنوان ورودی دریافت کرده و یک فایل را به عنوان خروجی تولید می‌کند. از آنجائیکه بسیاری از بانک‌های اطلاعاتی NoSQL کار عمده‌اشان، ایجاد و تغییر فایل‌ها است، اغلب جداول اطلاعات آن‌ها ورودی و خروجی‌های معتبری برای یک موتور Map reduce به حساب می‌آیند.
در این بین، افزونه‌ای برای Hadoop به نام Hive طراحی شده است که با ارائه HiveSQL، امکان نوشتن کوئری‌هایی SQL مانند را بر فراز موتور‌های Map reduce ممکن می‌سازد. این افزونه با Hive tables خاص خودش و یا با Hbase سازگار است.



آشنایی مقدماتی با مفاهیمی مانند الگوهای Sharding و Partitioning در بانک‌های اطلاعاتی NoSQL

Sharding (شاردینگ تلفظ می‌شود) یک الگوی تقسیم اطلاعات بر روی چندین سرور است که اساس توزیع شده بودن بانک‌های اطلاعاتی NoSQL را تشکیل می‌دهد. این نوع تقسیم اطلاعات، از کوئری‌هایی به نام Fan-out پشتیبانی می‌کند. به این معنا که شما کوئری خود را به نود اصلی ارسال می‌کنید و سپس به کمک موتور‌های Map reduce، این کوئری بر روی سرورهای مختلف اجرا شده و نتیجه نهایی جمع آوری خواهد شد. به این ترتیب تقسیم اطلاعات، صرفا به معنای قرار دادن یک سری فایل بر روی سرورهای مختلف نیست، بلکه هر کدام از این سرورها به صورت مستقل نیز قابلیت پردازش اطلاعات را دارند.
امکان تکثیر و همچنین replication هر کدام از سرورها نیز وجود دارد که قابلیت بازیابی سریع و مقاومت در برابر خرابی‌ها و مشکلات را افزایش می‌دهند.
از آنجائیکه Shardها را می‌توان در سرورهای بسیار متفاوت و گسترده‌ای از لحاظ جغرافیایی قرار داد، هر Shard می‌تواند همانند مفاهیم CDN نیز عمل کند؛ به این معنا که می‌توان Shard مورد نیاز سروری خاص را در محلی نزدیک‌تر به او قرار داد. به این ترتیب سرعت عملیات افزایش یافته و همچنین بار شبکه نیز کاهش می‌یابد.