مطالب
کارهایی جهت بالابردن کارآیی Entity Framework #2

در ادامه‌ی مطلب قبلی، نکاتی دیگر را جهت افزایش کارآیی سیستم‌های مبتنی بر EF اشاره خواهیم کرد:

عدم استفاده از کوئری‌های کلی

فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر می‌تواند بر اساس آنها جستجو را انجام دهد.

var searchModel = new Pupil
{
    FirstName = "Ben",
    LastName = null,
    City = null,
    PostalZipCode = null
};

List<Pupil> pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.City == null || p.LastName == searchModel.City)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
    .Take(100)
    .ToList()
در کد بالا کاربر فقط فیلد نام را برای جستجو انتخاب کرده است و سایر فیلدها را خالی در نظر گرفته است و کوئری بالا صادر شده است و دستورات زیر به اس کیو ال ارسال شده‌اند:
 USE [EFSchoolSystem]
DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben'
DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben'
DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = ''
DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = ''
DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = ''
DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = ''
DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = ''
DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = ''

-- Executed query
SELECT TOP (100)
        [Extent1].[PupilId] AS [PupilId] ,
        [Extent1].[FirstName] AS [FirstName] ,
        [Extent1].[LastName] AS [LastName] ,
        [Extent1].[Address1] AS [Address1] ,
        [Extent1].[Adderss2] AS [Adderss2] ,
        [Extent1].[PostalZipCode] AS [PostalZipCode] ,
        [Extent1].[City] AS [City] ,
        [Extent1].[PhoneNumber] AS [PhoneNumber] ,
        [Extent1].[SchoolId] AS [SchoolId] ,
        [Extent1].[Picture] AS [Picture]
FROM    [dbo].[Pupils] AS [Extent1]
WHERE   (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1)
        AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3)
        AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5)
        AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
هنگامیکه کوئری، سمت اس کیو ال سرور اجرا می‌شود، در صورتیکه پلن مربوط به آن وجود نداشته باشد، اس کیو ال سرور اقدام به ایجاد پلن، براساس پارامترهای ورودی می‌کند و از این به بعد، کوئری‌هایی یکسان، با پارامترهای متفاوت، از پلن کش شده استفاده خواهند کرد و از ایجاد پلن دیگری بدلیل هزینه بر بودن آن جلوگیری می‌شود.
مشکلی که در این دسته از کوئری‌های عمومی ایجاد می‌گردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی می‌کنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffing شناخته می‌شود و درباره‌ی Parameter Sniffing در اینجا به تفصیل اشاره شده است.
این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آن‌ها در شهر بوستون زندگی می‌کنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.

راه حل اول:
برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
 if (searchModel.City == null)
{
    pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
        .Take(100)
        .ToList();
}
else
{
    pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.City == null || p.LastName == searchModel.City)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
        .Take(100)
        .ToList();
}

راه حل دوم:

کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل می‌کند. بدین منظور مترجم زیر را ایجاد کنید:
public class RecompileDbCommandInterceptor : IDbCommandInterceptor
{
    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if(!command.CommandText.EndsWith(" option(recompile)"))
        {
            command.CommandText += " option(recompile)";
        }
    }
    //and implement other interface members
}
و مانند زیر استفاده کنید:
 var interceptor = new RecompileDbCommandInterceptor();
DbInterception.Add(interceptor);
var pupils = db.Pupils.Where(p => p.City = city).ToList();
DbInterception.Remove(interceptor);

راه حل سوم:

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

var result = db.Pupils.AsQueryable();

if(searchModel.FirstName != null )
     result = result.Where(p => p.FirstName == searchModel.FirstName);

if(searchModel.LastName != null )
     result = result.Where(p => p.LastName == searchModel.LastName);

if(searchModel.PostalZipCode != null )
     result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode);

if(searchModel.City != null )
     result = result.Where(p => p.City == searchModel.City);

افزونگی کشِ پلن

استفاده‌ی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفاده‌ی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئری‌های پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئری‌ها پارامتریک تولید می‌کند؛ اما یک استثناء وجود دارد: ()Skip و ()Take

2 متد فوق بیشتر جهت صفحه بندی استفاده می‌شوند:

var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(model.Page * model.ResultsPerPage)
    .Take(model.ResultsPerPage)
    .ToList();
فرض کنید مقدار ResultsPerPage برابر 100 باشد و مقدار Page برابر 417. کوئری ارسالی به شکل زیر خواهد بود:

حال اگر قصد دریافت اطلاعات صفحه‌ی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده می‌شوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلن‌ها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.

نکته: جهت مشاهده پلن‌های کش شده در اس کیو ال، دستور زیر اجرا کنید:
SELECT  text, query_plan
FROM    sys.dm_exec_cached_plans
        CROSS APPLY sys.dm_exec_query_plan(plan_handle)
        CROSS APPLY sys.dm_exec_sql_text(plan_handle)
درصورتیکه کوئری دوم را برای صفحه 500 اجرا کنید، مشاهده خواهید کرد پلن کش شده‌ی دیگری ایجاد شده است. این افزونگی، نامناسب است به دو دلیل:
  1. صدمه به کارآیی؛ هربار EF یک کوئری و اس کیو ال یک پلن جدید را ایجاد می‌کنند.
  2. افزایش اشغال حافظه؛ کش شدن کوئری‌ها توسط EF سمت کلاینت و کش شدن پلن‌ها در اس کیو ال سرور (کش بی رویه‌ی پلن‌ها باعث حذف سایر پلن‌های مورد استفاده بدلیل محدودیت حافظه می‌شود که امکان بروز اختلال در کارآیی را به‌همراه خواهد داشت.)


علت بروز مشکل:

هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال می‌شود، EF نمی‌تواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمی‌گیرد.


راه حل:

در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق می‌توان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئری‌های پارامتریک خواهد شد.

int resultsToSkip = model.Page * model.ResultsPerPage;
var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(() => resultsToSkip) //must pre-calculate this value
    .Take(() => model.ResultsPerPage)
    .ToList();
اکنون کوئری ارسال شده به اس کیو ال به صورت زیر خواهد بود:

همانطور که مشاهده می‌کنید این بار  EF کوئری پارامتریک ایجاد و ارسال کرده است.
پاسخ به بازخورد‌های پروژه‌ها
چند دیتا سورس به عنوان ورودی داده
خیلی خیلی ممنونم  از توضیحات شما
البته من در اس کیو ال عمل Pivot  رو انجام میدادم که با توضیحات شما اون رو به این روش تغییر دادم
الان من وقتی این کار رو انجام میدم دیباگ که میکنم دیتا مورد نظر من ایجاد میشه اما این خطا به من داده میشه
Source should be the result of the CrosstabExtension.Pivot method here 
و کد به این صورت نوشته شده است :
 .MainTableDataSource(dataSource =>
                                  {
                                      var crossTabList = Evaluations_Result.Pivot(x => new
                                      {
                                          گروه_سوال = x.QuestionGroupName,
                                          سوال = x.QuestionName,
                                          وزن = x.QuestionWeight.Value
                                      },
                                          x1 => x1.IndexName,
                                          x2 => x2.Count(x => x.IndexName != null));
                                      dataSource.Crosstab(crossTabList);                                    
                                  })
                                  .MainTableAdHocColumnsConventions(adHocColumnsConventions =>
                                  {
                                      adHocColumnsConventions.ShowRowNumberColumn(true);
                                      adHocColumnsConventions.RowNumberColumnCaption("ردیف");
                                  })
                                  .MainTableSummarySettings(summarySettings =>
                                  {
                                      summarySettings.OverallSummarySettings("جمع کل");
                                      summarySettings.PageSummarySettings("جمع صفحه");
                                  })
                                  .MainTableEvents(events =>
                                  {
                                      events.DataSourceIsEmpty(message: "اطلاعاتی برای نمایش وجود ندارد.");
}
و  Evaluations_Result پاسخ یک StoreProcedure است که از سمت دیتابیس میاد
مطالب
طراحی پایگاه داده چند زبانه

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

ستون اضافی :  این ساده‌ترین راه است و به ازای هر ستونی که نیاز به ترجمه داشته باشد، ستون اضافی در نظر می‌گیریم. 
CREATE TABLE app_product (
     Id Int IDENTITY NOT NULL,
     Description_en Text,
     Description_pl Text,
     PRIMARY KEY (Id)
);

مزایا :

  • سادگی
  • کوئری‌های آسان (بدون نیاز به join )

معایب :

  • اضافه کردن زبان جدید نیاز به تغییر جداولی که چند زبانه هستند دارد
  • اگر وارد کردن داده برای همه زبان‌ها الزامی نباشد (بعضی جاها فقط زبان پیش فرض الزامی است) ممکن است داده‌های زیاد و یا فیلد‌های خالی در دیتابیس ایجاد شود
  • نگهداری آن مشکل است  

یک جدول ترجمه :
این روش تمیزترین راه از دیدگاه ساختار دیتابیس به نظر می‌رسد. شما همه متن هایی را که نیاز به ترجمه دارد، در یک جدول ذخیره می‌کنید. 
CREATE TABLE ref_language (
         Code Char(2)NOT NULL,
         Name Varchar(20) NOT NULL,
         PRIMARY KEY (Code)
);
 
CREATE TABLE app_translation (
        Id Int IDENTITY NOT NULL,
        PRIMARY KEY (Id)
);
 
CREATE TABLE app_translation_entry (
       TranslationId Int NOT NULL,
       LanguageCode Char(2) NOT NULL,
       Text Text NOT NULL,
       FOREIGN KEY (TranslationId) REFERENCES app_translation(Id),
       FOREIGN KEY (LanguageCode) REFERENCES ref_language(Code)
);
 
CREATE TABLE app_product (
      Id Int IDENTITY NOT NULL,
      Description Int NOT NULL,
      PRIMARY KEY (Id),
      FOREIGN KEY (Description) REFERENCES app_translation(Id)
);

مزایا :

  • اضافه کردن زبان جدید به تغییر طرح دیتابیس نیاز ندارد
  • به نظر تمیز است و رویکرد رابطه‌ای دارد
  • همه ترجمه‌ها در یک مکان است (بعضی‌ها می‌گویند این جز معایب است چون امکان خوانایی و نگهداری کمتر است)

معایب :

  • کوئری‌های پیچیده (به join ‌های چندگانه نیاز دارد تا شرح کالا را به درستی نمایش دهد) 
  • پیچیدگی زیاد 
جدول ترجمه اضافی به ازای هر جدول چند زبانه : برای هر جدولی که نیاز به ترجمه دارد یک جدول اضافی ساخته می‌شود. جدول اصلی داده‌های غیر مرتبط به زبان و جدول دوم همه اطلاعات ترجمه شده را ذخیره می‌کند. 
CREATE TABLE ref_language (
      Code Char(2)NOT NULL,
      Name Varchar(20) NOT NULL,
      PRIMARY KEY (Code)
);
 
CREATE TABLE app_product (
      Id Int IDENTITY NOT NULL,
      PRIMARY KEY (Id)
);
 
CREATE TABLE app_product_translation (
      ProductId Int NOT NULL,
      LanguageCode Char(2) NOT NULL,
      Description Text NOT NULL,
      FOREIGN KEY (ProductId) REFERENCES app_product(Id),
      FOREIGN KEY (LanguageCode) REFERENCES ref_language(Code)
);

مزایا :

  • اضافه کردن زبان جدید به تغییر طرح دیتابیس نیاز ندارد
  • کوئری‌های نسبتا ساده است

معایب :

  • ممکن است تعداد جداول دو برابر شود 

سه مثال نشان داده شده در بالا به ما ایده می‌دهند که چگونه روش‌های مختلف ممکن است استفاده شوند. البته اینها همه گزینه‌های ممکن نیستند، فقط محبوبترین روش‌ها هستند و شما می‌توانید آنها را ویرایش کنید؛ به عنوان مثال با تعریف View ‌های اضافی که join ‌های پیچیده شما را در کدها، ذخیره می‌کنند. راه حلی  که شما انتخاب می‌کنید به نیازمندی‌های پروژه وابسته است. اگر شما به سادگی نیاز دارید و مطمئن هستید تعداد زبان‌های پیشتیبانی کم و ثابت است، می‌توانید راه حل اول را انتخاب کنید. اگر به انعطاف پذیری بیشتری نیاز دارید، می‌توانید join ‌های ساده در کوئری‌های چند زبانه را انتخاب کنید. راه حل سوم انتخاب مناسبی است. 

منبع :

http://fczaja.blogspot.com/2010/08/multilanguage-database-design.html 

نظرات مطالب
سفارشی کردن ASP.NET Identity در MVC 5
با سلام و احترام 
من دستور زیر رو تو asp.net mvc Identity sample 2 تو اکشن Login  اضافه کردم 
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));         
    var user = UserManager.FindById(User.Identity.GetUserId());
اما user و null میده !
اکشن login
 public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // This doen't count login failures towards lockout only two factor authentication
            // To enable password failures to trigger lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {

                case SignInStatus.Success:
                    {
                        UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
                        var user = UserManager.FindById(User.Identity.GetUserId());
                        return RedirectToLocal(returnUrl);
                    }
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }

مطالب
آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت سوم

در انتهای قسمت قبل، نحوه‌ی ایجاد یک جدول جدید با فیلدی از نوع فایل استریم بررسی شد، حال اگر جدولی از پیش وجود داشت، نحوه‌ی افزودن فیلد ویژه مورد نظر به آن، به صورت زیر است:

alter table tbl_files set(filestream_on ='default')

go
alter table tbl_files
add

[systemfile] varbinary(max) filestream null ,
FileId uniqueidentifier not null rowguidcol unique default (newid())
go

در ادامه جدول tblFiles قسمت قبل را در نظر بگیرید:

CREATE TABLE [tblFiles](
[FileId] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Title] [nvarchar](255) NOT NULL,
[SystemFile] [varbinary](max) FILESTREAM NULL,
UNIQUE NONCLUSTERED
(
[FileId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [fsg1]

ALTER TABLE [dbo].[tblFiles] ADD DEFAULT (newid()) FOR [FileId]
GO

نحوه‌ی افزودن رکوردی جدید به جدول tblFiles :

INSERT INTO [tblFiles]
(
[Title],
[SystemFile]
)
VALUES
(
'file-1',
CAST('data data data' AS VARBINARY(MAX))
)
در اینجا سعی کرده‌ایم یک رشته ساده را در فیلدی از نوع فایل استریم ذخیره کنیم که روش کار به صورت فوق است. از آنجائیکه مقدار پیش فرض FileId را هنگام تعریف جدول به NEWID تنظیم کرده‌ایم، نیازی به ذکر آن نیست و به صورت خودکار محاسبه و ذخیره خواهد شد.
اگر کنجکاو باشید که این فایل اکنون کجا ذخیره شده و نحوه‌ی مدیریت آن توسط اس کیوال سرور به چه صورتی است، فقط کافی است به مسیری که هنگام افزودن گروه فایل‌ها و فایل مربوطه در تنظیمات خواص دیتابیس در قسمت قبل مشخص کردیم، مراجعه کرد (شکل زیر).



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

using System;
using System.IO;
using System.Data.SqlClient;
using System.Data;

namespace FileStreamTest
{
class CFS
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//ساخت عبارت افزودن پارامتری
using (SqlCommand objSqlCmd = new SqlCommand(
"INSERT INTO [tblFiles]([Title],[SystemFile]) VALUES(@title , @file)",
objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.Text;

//تعریف وضعیت پارامترها و مقدار دهی آن‌ها
objSqlCmd.Parameters.AddWithValue("@title", title);
objSqlCmd.Parameters.AddWithValue("@file", buffer);

//اجرای فرامین
objSqlCmd.ExecuteNonQuery();
}

//پایان تراکنش
objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] data = null;

using (SqlConnection objConn = new SqlConnection())
{
//کوئری اس کیوال پارامتری جهت دریافت محتویات فایل
string cmdText = "SELECT SystemFile FROM tblFiles WHERE FileId=@id";
using (SqlCommand objCmd = new SqlCommand(cmdText, objConn))
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objConn.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objConn.Open();

//تنظیم کردن وضعیت و مقدار پارامتر تعریف شده در کوئری
objCmd.Parameters.AddWithValue("@id", fileId);

//اجرای فرامین و دریافت فایل
using (SqlDataReader objread = objCmd.ExecuteReader())
{
if (objread != null)
if (objread.Read())
{
if (objread["SystemFile"] != DBNull.Value)
data = (byte[])objread["SystemFile"];
}
}
}
}

return data;
}
}
}

مثالی در مورد روش استفاده از کلاس فوق :

using System.IO;

namespace FileStreamTest
{
class Program
{
static void Main(string[] args)
{
CFS.AddNewRecord(@"C:\filest05.PNG", "test1");

//آی دی رکورد ذخیره شده در دیتابیس برای مثال
byte[] data = CFS.GetDataFromDb("BB848D45-382C-4D95-BF4E-52C3509407D4");
if (data != null)
{
File.WriteAllBytes(@"C:\tst.PNG", data);
}
}
}
}
روش فوق با روش متداول افزودن یک فایل به دیتابیس اس کیوال سرور هیچ تفاوتی ندارد و این‌جا هم بدون مشکل کار می‌کند. اطلاعات نهایی به صورت فایل‌هایی بر روی سیستم که توسط اس کیوال سرور مدیریت خواهند شد و با جدول شما یکپارچه‌اند، ذخیره می‌شوند.

در روش دیگری که در اکثر مقالات مرتبط مورد استفاده است، از شیء SqlFileStream کمک گرفته شده و نحوه‌ی انجام آن نیز به صورت زیر می‌باشد.
در ابتدا دو رویه ذخیره شده زیر را ایجاد می‌کنیم:

CREATE PROCEDURE [AddFile](@Title NVARCHAR(255), @filepath VARCHAR(MAX) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;

DECLARE @ID UNIQUEIDENTIFIER
SET @ID = NEWID()

INSERT INTO [tblFiles]
(
[FileId],
[title],
[SystemFile]
)
VALUES
(
@ID,
@Title,
CAST('' AS VARBINARY(MAX))
)

SELECT @filepath = SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
GO

CREATE PROCEDURE [GetFilePath](@Id VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;

SELECT SystemFile.PathName()
FROM tblFiles
WHERE FileId = @ID
END
در رویه ذخیره شده AddFile ، ابتدا رکوردی بر اساس عنوان دلخواه ورودی با یک فایل خالی ایجاد می‌شود. سپس مسیر سیستمی این فایل را در آرگومان خروجی filepath قرار می‌دهیم. SystemFile.PathName از اس کیوال سرور 2008 جهت فیلدهای فایل استریم به اس کیوال سرور اضافه شده است. از این مسیر در برنامه خود جهت نوشتن بایت‌های فایل مورد نظر در آن توسط شیء SqlFileStream استفاده خواهیم کرد.
رویه ذخیره شده GetFilePath نیز تنها مسیر سیستمی فایل استریم ذخیره شده را بر می‌گرداند.
به این ترتیب کدهای برنامه به صورت زیر تغییر خواهند کرد:

using System.Data.SqlClient;
using System.Data;
using System.Data.SqlTypes;
using System.IO;

namespace FileStreamTest
{
class CFSqlFileStream
{
/// <summary>
/// افزودن رکورد به جدول حاوی ستونی از نوع فایل استریم
/// </summary>
/// <param name="filePath">مسیر فایل</param>
/// <param name="title">عنوانی دلخواه</param>
public static void AddNewRecord(string filePath, string title)
{
//آیا فایل وجود دارد؟
if (!File.Exists(filePath))
throw new FileNotFoundException(
"لطفا مسیر فایل معتبری را مشخص نمائید", filePath);

//خواندن اطلاعات فایل در آرایه‌ای از بایت‌ها
byte[] buffer = File.ReadAllBytes(filePath);

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده افزودن فایل
using (SqlCommand objSqlCmd = new SqlCommand(
"AddFile", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن وضعیت و مقدار پارامتر عنوان
SqlParameter objSqlParam1 = new SqlParameter("@Title", SqlDbType.NVarChar, 255);
objSqlParam1.Value = title;

//مشخص ساختن پارامتر خروجی رویه ذخیره شده
SqlParameter objSqlParamOutput = new SqlParameter("@filepath", SqlDbType.VarChar, -1);
objSqlParamOutput.Direction = ParameterDirection.Output;

//افزودن پارامترها به شیء کامند
objSqlCmd.Parameters.Add(objSqlParam1);
objSqlCmd.Parameters.Add(objSqlParamOutput);

//اجرای رویه ذخیره شده
objSqlCmd.ExecuteNonQuery();

//و سپس دریافت خروجی آن
string Path = objSqlCmd.Parameters["@filepath"].Value.ToString();

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای نوشتن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();
using (SqlFileStream objSqlFileStream =
new SqlFileStream(Path, objContext, FileAccess.Write))
{
objSqlFileStream.Write(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}
}

/// <summary>
/// دریافت اطلاعات فایل ذخیره شده به صورت آرایه‌ای از بایت‌ها
/// </summary>
/// <param name="fileId">کلید مورد استفاده</param>
/// <returns></returns>
public static byte[] GetDataFromDb(string fileId)
{
byte[] buffer = null;

using (SqlConnection objSqlCon = new SqlConnection())
{
//todo: کانکشن استرینگ باید از یک فایل کانفیگ خوانده شود
objSqlCon.ConnectionString =
"Data Source=(local);Initial Catalog=testdb2009;Integrated Security = true";
objSqlCon.Open();

//شروع یک تراکنش
using (SqlTransaction objSqlTran = objSqlCon.BeginTransaction())
{
//استفاده از رویه ذخیره شده دریافت مسیر فایل
using (SqlCommand objSqlCmd =
new SqlCommand("GetFilePath", objSqlCon, objSqlTran))
{
objSqlCmd.CommandType = CommandType.StoredProcedure;

//مشخص ساختن پارامتر ورودی رویه ذخیره شده و مقدار دهی آن
SqlParameter objSqlParam1 = new SqlParameter("@ID", SqlDbType.VarChar, 50);
objSqlParam1.Value = fileId;
objSqlCmd.Parameters.Add(objSqlParam1);

//اجرای رویه ذخیره شده و دریافت مسیر سیستمی فایل استریم
string path = string.Empty;
using (SqlDataReader sdr = objSqlCmd.ExecuteReader())
{
sdr.Read();
path = sdr[0].ToString();
}

//زمینه تراکنش فایل استریم موجود را دریافت کرده و از آن برای خواندن محتویات فایل استفاده خواهیم کرد
//این مورد نیز یکی از تازه‌های اس کیوال سرور 2008 است
using (SqlCommand objCmd = new SqlCommand(
"SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()", objSqlCon, objSqlTran))
{
byte[] objContext = (byte[])objCmd.ExecuteScalar();

using (SqlFileStream objSqlFileStream =
new SqlFileStream(path, objContext, FileAccess.Read))
{
buffer = new byte[(int)objSqlFileStream.Length];
objSqlFileStream.Read(buffer, 0, buffer.Length);
}
}
}

objSqlTran.Commit();
}
}

return buffer;
}
}
}
در پایان برای تکمیل بحث می‌توان به مقاله‌ی مرجع زیر مراجعه کرد:
FILESTREAM Storage in SQL Server 2008

مطالب دوره‌ها
افزونه‌ای برای کپسوله سازی نکات ارسال یک فرم ASP.NET MVC به سرور توسط jQuery Ajax
اگر مطالب سایت جاری را مطالعه و دنبال کرده باشید، تاکنون به صورت پراکنده نکات زیادی را در مورد استفاده از jQuery Ajax تهیه و ارائه کرده‌ایم. در این مطلب قصد داریم تا این نکات را نظم بخشیده و جهت استفاده مجدد، به صورت یک افزونه کپسوله سازی کنیم.

در کدها و افزونه‌ای که در ادامه ارائه خواهند شد، این مسایل درنظر گرفته شده است:

- چگونه اعتبار سنجی سمت کاربر را در حین استفاده از Ajax فعال کنیم.
- چگونه از چندبار کلیک کاربر در حین ارسال فرم به سرور جلوگیری نمائیم.
- چگونه Complex Types قابل تعریف در EF Code first را نیز در اینجا مدیریت کنیم.
- نحوه تعریف صحیح آدرس‌های کنترلرها چگونه باید باشد.
- نحوه اعلام وضعیت لاگین شخص به او، در صورت بروز مشکل.
- ارسال صحیح anti forgery token در حین اعمال Ajax ایی.
- بررسی Ajax بودن درخواست رسیده و تهیه یک فیلتر سفارشی مخصوص آن.
- از کش شدن اطلاعات Ajax ایی جلوگیری شود.


ابتدا معرفی مدل برنامه
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace jQueryMvcSample01.Models
{
    public class User
    {
        [Required(ErrorMessage = "(*)"), DisplayName("نام")]
        public string Name { set; get; }

        public PhoneInfo PhoneInfo { set; get; }
    }

    public class PhoneInfo
    {
        [Required(ErrorMessage = "(*)"), DisplayName("تلفن")]
        public string Phone { get; set; }

        [Required(ErrorMessage = "(*)"), DisplayName("پیش شماره")]
        public string Ext { get; set; }
    }
}
همانطور که ملاحظه می‌کنید، خاصیت PhoneInfo، تو در تو یا به نوعی Complex است. اگر از ابزارهای Scafolding توکار VS.NET برای تولید View متناظر استفاده کنیم، فیلد تو در توی PhoneInfo را لحاظ نخواهد کرد، اما ... مهم نیست. تعریف دستی آن هم کار می‌کند.


کدهای کنترلر برنامه

using System.Web.Mvc;
using jQueryMvcSample01.Models;
using jQueryMvcSample01.Security;

namespace jQueryMvcSample01.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش فرم
        }

        [HttpPost]
        [AjaxOnly] //فقط در حالت ای‌جکس قابل دسترسی باشد
        [ValidateAntiForgeryToken]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                // ذخیره سازی در بانک اطلاعاتی ...
                System.Threading.Thread.Sleep(3000);

                return Content("ok");//اعلام موفقیت آمیز بودن کار
            }

            return Content(null);//ارسال خطا
        }
    }
}
در اینجا در متد Index، اطلاعات شیء User به صورت Ajaxایی دریافت شده و پس از آن برای مثال قابلیت ذخیره سازی را خواهد داشت.
چند نکته در اینجا حائز اهمیت هستند:
الف) استفاده از ویژگی AjaxOnly (که کدهای آن‌را در پروژه پیوست می‌توانید مشاهده نمائید)، جهت صرفا پردازش درخواست‌های Ajaxایی.
ب) استفاده از ویژگی ValidateAntiForgeryToken در حین اعمال اجکسی. اگر سایت‌های مختلف را در اینباره جستجو کنید، عموما برای پردازش آن در حین استفاده از jQuery Ajax بسیار مشکل دارند.
ج) استفاده از return Content برای اعلام نتیجه کار. اگر اطلاعات ثبت شد، یک ok یا هر عبارت دیگری که علاقمند بودید ارسال گردیده و در غیراینصورت null بازگشت داده می‌شود.


کدهای افزونه PostMvcFormAjax

// <![CDATA[
(function ($) {
    $.fn.PostMvcFormAjax = function (options) {
        var defaults = {
            postUrl: '/',
            loginUrl: '/login',
            beforePostHandler: null,
            completeHandler: null,
            errorHandler: null
        };
        var options = $.extend(defaults, options);

        var validateForm = function (form) {
            //فعال سازی دستی اعتبار سنجی جی‌کوئری
            var val = form.validate();
            val.form();
            return val.valid();
        };

        return this.each(function () {
            var form = $(this);
            //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود
            if (!validateForm(form)) return;

            //در اینجا می‌توان مثلا دکمه‌ای را غیرفعال کرد
            if (options.beforePostHandler)
                options.beforePostHandler(this);

            //اطلاعات نباید کش شوند
            $.ajaxSetup({ cache: false });

            $.ajax({
                type: "POST",
                url: options.postUrl,
                data: form.serialize(), //تمام فیلدهای فرم منجمله آنتی فرجری توکن آن‌را ارسال می‌کند
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا می‌شود
                    }
                    else if (status === 'error' || !data) {
                        if (options.errorHandler)
                            options.errorHandler(this);
                    }
                    else {
                        if (options.completeHandler)
                            options.completeHandler(this);
                    }
                }

            });
        });
    };
})(jQuery);
// ]]>
چند نکته مهم در تهیه این افزونه رعایت شده:
الف) فعال سازی دستی اعتبار سنجی جی‌کوئری، از این جهت که این نوع اعتبار سنجی به صورت پیش فرض تنها در حالت postback و ارسال کامل صفحه به سرور فعال می‌شود.
ب) استفاده از متد serialize جهت پردازش یکباره کل اطلاعات و فیلدهای یک فرم.
نکته مهم این متد ارسال فیلد مخفی anti forgery token نیز می‌باشد. فقط باید دقت داشت که این فیلد در حالتی که dataType به json تنظیم شود و همچنین از متد serialize استفاده گردد، در ASP.NET MVC پردازش نمی‌گردد (خیلی مهم!). به همین جهت در اینجا dataType تنظیمات jQuery Ajax حذف شده است.
ج) تنظیم cache به false در تنظیمات ابتدایی jQuery Ajax تا اطلاعات ارسالی و دریافتی کش نشوند و مشکل ساز نگردند.
د) بررسی xhr.status == 403 که توسط SiteAuthorizeAttribute (جایگزین بهتر فیلتر Authorize توکار ASP.NET MVC که کدهای آن در پروژه پیوست قابل دریافت است) و هدایت کاربر به صفحه لاگین


تعریف View ایی که از اشیاء تو در تو استفاده می‌کند و همچنین از افزونه فوق برای ارسال اطلاعات بهره خواهد برد:

@model jQueryMvcSample01.Models.User
@{
    ViewBag.Title = "تعریف کاربر";
    var postUrl = Url.Action(actionName: "Index", controllerName: "Home");
}
@using (Html.BeginForm(actionName: "Index", controllerName: "Home",
                       method: FormMethod.Post,
                       htmlAttributes: new { id = "UserForm" }))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()

    <fieldset>
        <legend>تعریف کاربر</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Ext)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Phone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Phone)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Phone)
        </div>
        <p>
            <input type="submit" id="btnSave" value="ارسال" />
        </p>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#btnSave").click(function (event) {
                //جلوگیری از پست بک به سرور
                event.preventDefault();

                var button = $(this);

                $("#UserForm").PostMvcFormAjax({
                    postUrl: '@postUrl',
                    loginUrl: '/login',
                    beforePostHandler: function () {
                        //غیرفعال سازی دکمه ارسال
                        button.attr('disabled', 'disabled');
                        button.val("...");
                    },
                    completeHandler: function () {
                        //فعال سازی مجدد دکمه ارسال
                        alert('انجام شد');
                        button.removeAttr('disabled');
                        button.val("ارسال");
                    },
                    errorHandler: function () {
                        alert('خطایی رخ داده است');
                    }
                });
            });
        });
    </script>
}
همانطور که عنوان شد، مهم نیست که اشیاء تو در تو توسط ابزار Scafolding پشتیبانی نمی‌شود. این نوع خواص را به همان نحو متداول ذکر زنجیره وار خواص می‌توان معرفی و استفاده کرد:
 @Html.EditorFor(model => model.PhoneInfo.Phone)
هم اعتبار سنجی سمت کلاینت آن کار می‌کند و هم اطلاعات آن به اشیاء و خواص متناظر به خوبی نگاشت خواهد شد:


در ادامه نحوه استفاده از افزونه PostMvcFormAjax را مشاهده می‌کنید. چند نکته نیز در اینجا حائز اهمیت هستند:
الف) توسط htmlAttributes یک id برای فرم تعریف کرده‌ایم تا در افزونه PostMvcFormAjax مورد استفاده قرار گیرد.
ب) postUrl و loginUrl را همانند متغیر تعریف شده در ابتدای View توسط Url.Action باید تعریف کرد تا در صورتیکه سایت ما در ریشه اصلی قرار نداشت، باز هم به صورت خودکار مسیر صحیحی محاسبه و ارائه گردد.
ج) نحوه غیرفعال سازی و فعال سازی دکمه submit را در روال‌های beforePostHandler و completeHandler ملاحظه می‌کنید. این مساله برای جلوگیری از کلیک‌های مجدد یک کاربر ناشکیبا و جلوگیری از ثبت اطلاعات تکراری بسیار مهم است.
د) کل این اطلاعات، در یک section به نام JavaScript ثبت شده است. این section در فایل layout برنامه به صورت زیر مورد استفاده قرار خواهد گرفت و به این ترتیب مقدار دهی خواهد شد:
<head>
    <title>@ViewBag.Title</title>    
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.PostMvcFormAjax.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>

دریافت کدهای کامل این قسمت
jQueryMvcSample01.zip

 
مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
چرا JSON.NET؟
JSON.NET یک کتابخانه‌ی سورس باز کار با اشیاء JSON در دات نت است. تاریخچه‌ی آن به 8 سال قبل بر می‌گردد و توسط یک برنامه نویس نیوزیلندی به نام James Newton King تهیه شده‌است. اولین نگارش آن در سال 2006 ارائه شد؛ مقارن با زمانی که اولین استاندارد JSON نیز ارائه گردید.
این کتابخانه از آن زمان تا کنون، 6 میلیون بار دانلود شده‌است و به علت کیفیت بالای آن، این روزها پایه اصلی بسیاری از کتابخانه‌ها و فریم ورک‌های دات نتی می‌باشد؛ مانند RavenDB تا ASP.NET Web API و SignalR مایکروسافت و همچنین گوگل نیز از آن جهت تدارک کلاینت‌های کار با API خود استفاده می‌کنند.
هرچند دات نت برای نمونه در نگارش سوم آن جهت مصارف WCF کلاسی را به نام DataContractJsonSerializer ارائه کرد، اما کار کردن با آن محدود است به فرمت خاص WCF به همراه عدم انعطاف پذیری و سادگی کار با آن. به علاوه باید درنظر داشت که JSON.NET از دات نت 2 به بعد تا مونو، Win8 و ویندوز فون را نیز پشتیبانی می‌کند.

برای نصب آن نیز کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> install-package Newtonsoft.Json

معماری JSON.NET

کتابخانه‌ی JSON.NET از سه قسمت عمده تشکیل شده‌است:
الف) JsonSerializer
ب) LINQ to JSON
ج) JSON Schema


الف) JsonSerializer
کار JsonSerializer تبدیل اشیاء دات نتی به JSON و برعکس است. مزیت مهم آن امکانات قابل توجه تنظیم عملکرد و خروجی آن می‌باشد که این تنظیمات را به شکل ویژگی‌های خواص نیز می‌توان اعمال نمود. به علاوه امکان سفارشی سازی هر کدام نیز توسط کلاسی به نام JsonConverter، پیش بینی شده‌است.
یک مثال:
 var roles = new List<string>
{
   "Admin",
   "User"
};
string json = JsonConvert.SerializeObject(roles, Formatting.Indented);
در اینجا نحوه‌ی استفاده از JSON.NET را جهت تبدیل یک شیء دات نتی، به معادل JSON آن مشاهده می‌کنید. اعمال تنظیم Formatting.Indented سبب خواهد شد تا خروجی آن دارای Indentation باشد. برای نمونه اگر در برنامه‌ی خود قصد دارید فرمت JSON تو در تویی را به نحو زیبا و خوانایی نمایش دهید یا چاپ کنید، همین تنظیم ساده کافی خواهد بود.
و یا در مثال ذیل استفاده از یک anonymous object را مشاهده می‌کنید:
 var jsonString = JsonConvert.SerializeObject(new
{
   Id =1,
   Name = "Test"
}, Formatting.Indented);
به صورت پیش فرض تنها خواص عمومی کلاس‌ها توسط JSON.NET تبدیل خواهند شد.


تنظیمات پیشرفته‌تر JSON.NET

مزیت مهم JSON.NET بر سایر کتابخانه‌ها‌ی موجود مشابه، قابلیت‌های سفارشی سازی قابل توجه آن است. در مثال ذیل نحوه‌ی معرفی JsonSerializerSettings را مشاهده می‌نمائید:
var jsonData = JsonConvert.SerializeObject(new
{
   Id = 1,
   Name = "Test",
   DateTime = DateTime.Now
}, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   Converters =
   {
      new JavaScriptDateTimeConverter()
   }
});
در اینجا با استفاده از تنظیم JavaScriptDateTimeConverter، می‌توان خروجی DateTime استانداردی را به مصرف کنندگان جاوا اسکریپتی سمت کاربر ارائه داد؛ با خروجی ذیل:
 {
  "Id": 1,
  "Name": "Test",
  "DateTime": new Date(1409821985245)
}


نوشتن خروجی JSON در یک استریم

خروجی متد JsonConvert.SerializeObject یک رشته‌است که در صورت نیاز به سادگی توسط متد File.WriteAllText در یک فایل قابل ذخیره می‌باشد. اما برای رسیدن به حداکثر کارآیی و سرعت می‌توان از استریم‌ها نیز استفاده کرد:
using (var stream = File.CreateText(@"c:\output.json"))
{
    var jsonSerializer = new JsonSerializer
   {
      Formatting = Formatting.Indented
   };
   jsonSerializer.Serialize(stream, new
   {
     Id = 1,
     Name = "Test",
     DateTime = DateTime.Now
   });
}
کلاس JsonSerializer و متد Serialize آن یک استریم را نیز جهت نوشتن خروجی می‌پذیرند. برای مثال response.Output برنامه‌های وب نیز یک استریم است و در اینجا نوشتن مستقیم در استریم بسیار سریعتر است از تبدیل شیء به رشته و سپس ارائه خروجی آن؛ زیرا سربار تهیه رشته JSON از آن حذف می‌گردد و نهایتا GC کار کمتری را باید انجام دهد.


تبدیل JSON رشته‌ای به اشیاء دات نت

اگر رشته‌ی jsonData ایی را که پیشتر تولید کردیم، بخواهیم تبدیل به نمونه‌ای از شیء User ذیل کنیم:
public class User
{
   public int Id { set; get; }
   public string Name { set; get; }
   public DateTime DateTime { set; get; }
}
خواهیم داشت:
 var user = JsonConvert.DeserializeObject<User>(jsonData);
در اینجا از متد DeserializeObject به همراه مشخص سازی صریح نوع شیء نهایی استفاده شده‌است.
البته در اینجا با توجه به استفاده از JavaScriptDateTimeConverter برای تولید jsonData، نیاز است چنین تنظیمی را نیز در حالت DeserializeObject مشخص کنیم:
var user = JsonConvert.DeserializeObject<User>(jsonData, new JsonSerializerSettings
{
   Converters = {  new JavaScriptDateTimeConverter() }
});


مقدار دهی یک نمونه یا وهله‌ی از پیش موجود

متد JsonConvert.DeserializeObject یک شیء جدید را ایجاد می‌کند. اگر قصد دارید صرفا تعدادی از خواص یک وهله‌ی موجود، توسط JSON.NET مقدار دهی شوند از متد PopulateObject استفاده کنید:
 JsonConvert.PopulateObject(jsonData, user);


کاهش حجم JSON تولیدی

زمانیکه از متد JsonConvert.SerializeObject استفاده می‌کنیم، تمام خواص عمومی تبدیل به معادل JSON آن‌ها خواهند شد؛ حتی خواصی که مقدار ندارند. این خواص در خروجی JSON، با مقدار null مشخص می‌شوند. برای حذف این خواص از خروجی JSON نهایی تنها کافی است در تنظیمات JsonSerializerSettings، مقدار NullValueHandling = NullValueHandling.Ignore مشخص گردد.
var jsonData = JsonConvert.SerializeObject(object, new JsonSerializerSettings
{
   NullValueHandling = NullValueHandling.Ignore,
   Formatting = Formatting.Indented
});
مورد دیگری که سبب کاهش حجم خروجی نهایی خواهد شد، تنظیم DefaultValueHandling = DefaultValueHandling.Ignore است. در این حالت کلیه خواصی که دارای مقدار پیش فرض خودشان هستند، در خروجی JSON ظاهر نخواهند شد. مثلا مقدار پیش فرض خاصیت int مساوی صفر است. در این حالت کلیه خواص از نوع int که دارای مقدار صفر می‌باشند، در خروجی قرار نمی‌گیرند.
به علاوه حذف Formatting = Formatting.Indented نیز توصیه می‌گردد. در این حالت فشرده‌ترین خروجی ممکن حاصل خواهد شد.


مدیریت ارث بری توسط JSON.NET

در مثال ذیل کلاس کارمند و کلاس مدیر را که خود نیز در اصل یک کارمند می‌باشد، ملاحظه می‌کنید:
public class Employee
{
    public string Name { set; get; }
}

public class Manager : Employee
{
    public IList<Employee> Reports { set; get; }
}
در اینجا هر مدیر لیست کارمندانی را که به او گزارش می‌دهند نیز به همراه دارد. در ادامه نمونه‌ای از مقدار دهی این اشیاء ذکر شده‌اند:
 var employee = new Employee { Name = "User1" };
var manager1 = new Manager { Name = "User2" };
var manager2 = new Manager { Name = "User3" };
manager1.Reports = new[] { employee, manager2 };
manager2.Reports = new[] { employee };
با فراخوانی
 var list = JsonConvert.SerializeObject(manager1, Formatting.Indented);
یک چنین خروجی JSON ایی حاصل می‌شود:
{
  "Reports": [
    {
      "Name": "User1"
    },
    {
      "Reports": [
        {
          "Name": "User1"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
این خروجی JSON جهت تبدیل به نمونه‌ی معادل دات نتی خود، برای مثال جهت رسیدن به manager1 در کدهای فوق، چندین مشکل را به همراه دارد:
- در اینجا مشخص نیست که این اشیاء، کارمند هستند یا مدیر. برای مثال مشخص نیست User2 چه نوعی دارد و باید به کدام شیء نگاشت شود.
- مشکل دوم در مورد کاربر User1 است که در دو قسمت تکرار شده‌است. این شیء JSON اگر به نمونه‌ی معادل دات نتی خود نگاشت شود، به دو وهله از User1 خواهیم رسید و نه یک وهله‌ی اصلی که سبب تولید این خروجی JSON شده‌است.

برای حل این دو مشکل، تغییرات ذیل را می‌توان به JSON.NET اعمال کرد:
var list = JsonConvert.SerializeObject(manager1, new JsonSerializerSettings
{
   Formatting = Formatting.Indented,
   TypeNameHandling = TypeNameHandling.Objects,
   PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
با این خروجی:
{
  "$id": "1",
  "$type": "JsonNetTests.Manager, JsonNetTests",
  "Reports": [
    {
      "$id": "2",
      "$type": "JsonNetTests.Employee, JsonNetTests",
      "Name": "User1"
    },
    {
      "$id": "3",
      "$type": "JsonNetTests.Manager, JsonNetTests",
      "Reports": [
        {
          "$ref": "2"
        }
      ],
      "Name": "User3"
    }
  ],
  "Name": "User2"
}
- با تنظیم TypeNameHandling = TypeNameHandling.Objects سبب خواهیم شد تا خاصیت اضافه‌ای به نام $type به خروجی JSON اضافه شود. این نوع، در حین فراخوانی متد JsonConvert.DeserializeObject جهت تشخیص صحیح نگاشت اشیاء بکار گرفته خواهد شد و اینبار مشخص است که کدام شیء، کارمند است و کدامیک مدیر.
- با تنظیم PreserveReferencesHandling = PreserveReferencesHandling.Objects شماره Id خودکاری نیز به خروجی JSON اضافه می‌گردد. اینبار اگر به گزارش دهنده‌ها با دقت نگاه کنیم، مقدار $ref=2 را خواهیم دید. این مورد سبب می‌شود تا در حین نگاشت نهایی، دو وهله متفاوت از شیء با Id=2 تولید نشود.

باید دقت داشت که در حین استفاده از JsonConvert.DeserializeObject نیز باید JsonSerializerSettings یاد شده، تنظیم شوند.


ویژگی‌های قابل تنظیم در JSON.NET

علاوه بر JsonSerializerSettings که از آن صحبت شد، در JSON.NET امکان تنظیم یک سری از ویژگی‌ها به ازای خواص مختلف نیز وجود دارند.
- برای نمونه ویژگی JsonIgnore معروفترین آن‌ها است:
public class User
{
   public int Id { set; get; }

   [JsonIgnore]
   public string Name { set; get; }

   public DateTime DateTime { set; get; }
}
JsonIgnore سبب می‌شود تا خاصیتی در خروجی نهایی JSON تولیدی حضور نداشته باشد و از آن صرفنظر شود.

- با استفاده از ویژگی JsonProperty اغلب مواردی را که پیشتر بحث کردیم مانند NullValueHandling، TypeNameHandling و غیره، می‌توان تنظیم نمود. همچنین گاهی از اوقات کتابخانه‌های جاوا اسکریپتی سمت کاربر، از اسامی خاصی که از روش‌های نامگذاری دات نتی پیروی نمی‌کنند، در طراحی خود استفاده می‌کنند. در اینجا می‌توان نام خاصیت نهایی را که قرار است رندر شود نیز صریحا مشخص کرد. برای مثال:
[JsonProperty(PropertyName = "m_name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { set; get; }
همچنین در اینجا امکان تنظیم Order نیز وجود دارد. برای مثال مشخص کنیم که خاصیت X در ابتدا قرار گیرد و پس از آن خاصیت Y رندر شود.

- استفاده از ویژگی JsonObject به همراه مقدار OptIn آن به این معنا است که از کلیه خواصی که دارای ویژگی JsonProperty نیستند، صرفنظر شود. حالت پیش فرض آن OptOut است؛ یعنی تمام خواص عمومی در خروجی JSON حضور خواهند داشت منهای مواردی که با JsonIgnore مزین شوند.
[JsonObject(MemberSerialization.OptIn)]
public class User
{
    public int Id { set; get; }

    [JsonProperty]
    public string Name { set; get; }
 
    public DateTime DateTime { set; get; }
}

- با استفاده از ویژگی JsonConverter می‌توان نحوه‌ی رندر شدن مقدار خاصیت را سفارشی سازی کرد. برای مثال:
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime DateTime { set; get; }


تهیه یک JsonConverter سفارشی

با استفاده از JsonConverterها می‌توان کنترل کاملی را بر روی اعمال serialization و deserialization مقادیر خواص اعمال کرد. مثال زیر را در نظر بگیرید:
public class HtmlColor
{
   public int Red { set; get; }
   public int Green { set; get; }
   public int Blue { set; get; }
}

var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
}, Formatting.Indented);
در اینجا علاقمندیم، در حین عملیات serialization، بجای اینکه مقادیر اجزای رنگ تهیه شده به صورت int نمایش داده شوند، کل رنگ با فرمت hex رندر شوند. برای اینکار نیاز است یک JsonConverter سفارشی را تدارک دید:
    public class HtmlColorConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(HtmlColor);
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var color = value as HtmlColor;
            if (color == null)
                return;

            writer.WriteValue("#" + color.Red.ToString("X2")
                + color.Green.ToString("X2") + color.Blue.ToString("X2"));
        }
    }
کار با ارث بری از کلاس پایه JsonConverter شروع می‌شود. سپس باید تعدادی از متدهای این کلاس پایه را بازنویسی کرد. در متد CanConvert اعلام می‌کنیم که تنها اشیایی از نوع کلاس HtmlColor را قرار است پردازش کنیم. سپس در متد WriteJson منطق سفارشی خود را می‌توان پیاده سازی کرد.
از آنجائیکه این تبدیلگر صرفا قرار است برای حالت serialization استفاده شود، قسمت ReadJson آن پیاده سازی نشده‌است.

در آخر برای استفاده از آن خواهیم داشت:
var colorJson = JsonConvert.SerializeObject(new HtmlColor
{
  Red = 255,
  Green = 0,
  Blue = 0
},  new JsonSerializerSettings
    {
      Formatting = Formatting.Indented,
      Converters = { new HtmlColorConverter() }
    });   
اشتراک‌ها
MessagePack چیست؟

It's like JSON. but fast and small.

MessagePack is supported by over 50 programming languages and environments. 

//C#
// Creates serializer.
var serializer = SerializationContext.Default.GetSerializer<T>();

// Pack obj to stream.
serializer.Pack(stream, obj);

// Unpack from stream.
var unpackedObject = serializer.Unpack(stream);
MessagePack چیست؟
راهنماهای پروژه‌ها
فشرده سازی گزارش‌های تولیدی
برای فعال کردن فشرده سازی فایل‌های گزارش تولیدی (خصوصا جهت برنامه‌های وب) تنها کافی است تنظیم زیر را به DocumentPreferences اضافه نمائید:
            return new PdfReport().DocumentPreferences(doc =>
            {
                // ...
                doc.Compression(new CompressionSettings
                {
                    EnableCompression = true,
                    EnableFullCompression = true
                });
            })
برای نمونه با افزودن تنظیم فوق به گزارش IList، حجم فایل تولیدی از حدود 400KB به حدود 100KB کاهش پیدا می‌کند.