نظرات مطالب
خواندن اطلاعات از فایل اکسل با استفاده از LinqToExcel
با استفاده از متد WorksheetNoHeader و با وارد کردن شماره اندیس می‌توانید به اطلاعات سلولها دست پیدا کنید. در مثال زیر تمام سطرهایی که ستون دوم آنها شهر مشهد است انتخاب می‌شوند و سپس سلولهای آن سطرها نمایش داده می‌شوند.  
         var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            var persons = from a in excel.WorksheetNoHeader(sheetName)
                          where a[1] == "Mashhad" //مقدار در ستون دوم 
                          select a;
            foreach (var a in persons)
              {
                  for(int i=0;i<a.Count;i++)
                        MessageBox.Show(a[i]); 
              }
به اسمبلی Microsoft.Office.Interop.Excel نیازی نیست و
LinqToExcel.dll  و  Remotion.Data.Linq.dll مورد نیاز است. 
 
نظرات مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها
ممنون از پیشنهادتون. علاوه بر این میتوان اعتبار سنجی برای اطلاعات فارسی رو هم پیاده سازی کرد. از جمله اعتبار سنجی کد ملی، شناسه ثبت شرکت، شماره‌های تلفن و ...
از دوستان تقاضا دارم برای داشتن یک ماژول کامل اعتبار سنجی در زمینه اطلاعات فارسی و همچنین اعتبار سنجی‌های کاربردی پیشنهاداتشون رو در این لینک  و یا در همین صفحه مطرح کنند تا در اولین فرصت پیاده سازی و تکمیل شوند.
نظرات مطالب
EF Code First #7
یعنی میفرمایید من چه رابطه ای بین این دو کلاس تعریف کنم تا رابطه 0..1-0..1 داشته باشم؟
    public class Order
    {
        public int OrderId { get; set; }
        public virtual Quotation Quotation { get; set; }
    }
    public class Quotation
    {
        public int QuotationId { get; set; }
        public virtual Order Order { get; set; }
    }

مطالب
فیلدهای پویا در NHibernate

یکی از قابلیت‌های جالب NHibernate امکان تعریف فیلدها به صورت پویا هستند. به این معنا که زیرساخت طراحی یک برنامه "فرم ساز" هم اکنون در اختیار شما است! سیستمی که امکان افزودن فیلدهای سفارشی را دارا است که توسط برنامه نویس در زمان طراحی اولیه آن ایجاد نشده‌اند. در ادامه نحوه‌ی تعریف و استفاده از این قابلیت را توسط Fluent NHibernate بررسی خواهیم کرد.

در اینجا کلاسی که قرار است توانایی افزودن فیلدهای سفارشی را داشته باشد به صورت زیر تعریف می‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فیلدهای سفارشی مورد نظر خواهند بود. جهت معرفی صحیح این قابلیت نیاز است تا نگاشت آن‌را از نوع dynamic component تعریف کنیم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترین نکته‌ی این بحث هم همین نگاشت فوق است.
ابتدا از IgnoreProperty جهت ندید گرفتن Attributes استفاده کردیم. زیرا درغیراینصورت در حالت Auto mapping ، یک رابطه چند به یک به علت وجود IDictionary به صورت خودکار ایجاد خواهد شد که نیازی به آن نیست (یافتن این نکته نصف روز کار برد ....! چون مرتبا خطای An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر می‌شد و مشخص نبود که مشکل از کجاست).
سپس Attributes به عنوان یک DynamicComponent معرفی شده است. در اینجا چهار فیلد سفارشی را اضافه کرده‌ایم. به این معنا که اگر نیاز باشد تا فیلد سفارشی دیگری به سیستم اضافه شود باید یکبار Session factory ساخته شود و SchemaUpdate فراخوانی گردد و خوشبختانه با وجود Fluent NHibernate ، تمام این تغییرات در کدهای برنامه قابل انجام است (بدون نیاز به سر و کار داشتن با فایل‌های XML نگاشت‌ها و ویرایش دستی آن‌ها). از تغییر نام جدول که برای مثال در اینجا tblDynamicEntity در نظر گرفته شده تا افزودن فیلدهای دیگر در قسمت DynamicComponent فوق.
همچنین باتوجه به اینکه این نوع تغییرات (ساخت دوبار سشن فکتوری) مواردی نیستند که قرار باشد هر ساعت انجام شوند، بنابراین سربار آنچنانی را به سیستم تحمیل نمی‌کنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

اگر با SchemaExport، اسکریپت خروجی معادل با نگاشت فوق را تهیه کنیم به جدول فوق خواهیم رسید. نوع و طول این فیلدهای سفارشی بر اساس نوعی که برای اشیاء دیکشنری مشخص می‌کنید، تعیین خواهند شد.

چند مثال جهت کار با این فیلدهای سفارشی یا پویا :

نحوه‌ی افزودن رکوردهای جدید بر اساس خاصیت‌های سفارشی:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجی
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ی کوئری گرفتن از این اطلاعات (فعلا پایدارترین روشی را که برای آن یافته‌ام استفاده از HQL می‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجی:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

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

نحوه‌ی به روز رسانی و حذف اطلاعات بر اساس فیلدهای پویا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}

مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

نحوه نصب و راه اندازی LightInject
در پنجره Package Manager Console می‌توانید با نوشتن دستور ذیل، نسخه باینری آن را نصب کنید که به فایل .dll آن Reference میدهد.

PM> Install-Package LightInject
 همچنین می‌توانید توسط دستور ذیل فایل .cs آن را به پروژه اضافه نمایید. 

PM> Install-Package LightInject.Source

 آماده سازی پروژه نمونه 
قبل از شروع کار با LightInject، یک پروژه Windows Forms Application را با ساختار کلاس‌های ذیل ایجاد نمایید. (در مقالات بعدی و پس از آموزش کامل LightInject نحوه استفاده از آن را در ASP.NET MVC نیز آموزش می‌دهیم)
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Family { get; set; }
        public DateTime Birth { get; set; }
    }

    public interface IRepository<T> where T:class
    {
        void Insert(T entity);
        IEnumerable<T> FindAll();
    }

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

    public class PersonRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPersonService
    {
        void Insert(PersonModel entity);
        IEnumerable<PersonModel> FindAll();
    }

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public PersonService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            _personRepository.Insert(entity);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
PersonModel: ساختار داده ای جدول Person در سمت Application، که در لایه Domain Model ایجاد می‌گردد.
توجه: جهت سهولت تست و تسریع کدنویسی از لایه بندی و از کلاس‌های ViewModel استفاده نکردیم.
IRepository: یک Interface عمومی برای تمامی Interface‌های مربوط به Repository که عملیات مربوط به پایگاه داده مثل بروزرسانی و واکشی اطلاعات را انجام می‌دهند.
IPersonRepository: واسط بین لایه Service و لایه Repository می‌باشد.
PersonRepository: پیاده سازی واقعی عملیات مربوط به پایگاه داده برای PersonModel می‌باشد. به کلاسهایی که حاوی پیاده سازی واقعی کد می‌باشند Concrete Class می‌گویند.
IPersonService: واسط بین رابط کاربری و لایه سرویس می‌باشد. رابط کاربری به جای دسترسی مستقیم به PersonService از IPersonService استفاده می‌کند.
PersonService: دریافت درخواست‌های رابط کاربری و بررسی قوانین تجاری، سپس ارسال درخواست به لایه Repository در صورت صحت درخواست، و در نهایت ارسال پاسخ دریافتی به رابط کاربری. در واقع واسطی بین Repository و UI می‌باشد.
پس از ایجاد ساختار فوق کد مربوط به Form1 را بصورت زیر تغییر دهید.
public partial class Form1 : Form
    {
        private readonly IPersonService _personService;
        public Form1(IPersonService personService)
        {
            _personService = personService;
            InitializeComponent();
        }
    }
توضیحات
در کد فوق به منظور ارتباط با سرویس از IPersonService استفاده نمودیم که به عنوان پارامتر ورودی برای سازنده Form1 تعریف شده است. حتما با Dependency Inversion و انواع Dependency Injection آشنا هستید که به سراغ مطالعه این مقاله آمدید و علت این نوع کدنویسی را هم می‌دانید. بنابراین توضیح بیشتری در این مورد نمی‌دهم.
حال اگر برنامه را اجرا کنید در Program.cs با خطای عدم وجود سازنده بدون پارامتر برای Form1 مواجه می‌شوید که کد آن را باید به صورت زیر تغییر می‌دهیم.
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonRepository, PersonRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
        }
توضیحات
کلاس ServiceContainer وظیفه‌ی Register کردن یک کلاس را برای یک Interface دارد. زمانی که می‌خواهیم Form1 را نمونه سازی نماییم و Application را راه اندازی کنیم، باید نمونه ای را از جنس IPersonService ایجاد نموده و به سازنده‌ی Form1 ارسال نماییم. با رعایت اصل DIP، نمونه سازی واقعی یک کلاس لایه دیگر، نباید در داخل کلاس‌های لایه جاری انجام شود. برای این منظور از شیء container استفاده نمودیم و توسط متد GetInstance، نمونه‌ای از جنس IPersonService را ایجاد نموده و به Form1 پاس دادیم. حال container از کجا متوجه می‌شود که چه کلاسی را برای IPersonService نمونه سازی نماید؟
در خطوط قبلی توسط متد Register، کلاس PersonService را برای IPersonService ثبت نمودیم. container نیز برای نمونه سازی به کلاس هایی که برایش Register نمودیم مراجعه می‌نماید و نمونه سازی را انجام می‌دهد. جهت استفاده از PersonService به پارامتر ورودی IPersonRepository برای سازنده‌ی آن نیاز داریم که کلاس PersonRepository را برای IPersonRepository ثبت کردیم.
حال اگر برنامه را اجرا کنید، به درستی اجرا خواهد شد. برنامه را متوقف کنید و به کد موجود در Program.cs مراجعه نموده و دو خط مربوط به Register را Comment نمایید. سپس برنامه را اجرا کنید و خطای تولید شده را ببینید. این خطا بیان می‌کند که امکان نمونه سازی برای IPersonService را ندارد. چون قبلا هیچ کلاسی را برای آن Register نکرده ایم.
Named Services
در برخی مواقع، بیش از یک کلاس وجود دارند که ممکن است از یک Interface ارث بری نمایند. در این حالت و در زمان Register، باید به ServiceContainer بگوییم که کدام کلاس را باید نمونه سازی نماید. برای بررسی این موضوع، کلاسهای زیر را به ساختار پروژه اضافه نمایید.
    public class WorkerModel:PersonModel
    {
        public ManagerModel Manager { get; set; }
    }

    public class ManagerModel:PersonModel
    {
        public IEnumerable<WorkerModel> Workers { get; set; }
    }

    public class WorkerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class ManagerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public WorkerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var worker = entity as WorkerModel;
            _personRepository.Insert(worker);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public ManagerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var manager = entity as ManagerModel;
            _personRepository.Insert(manager);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
دو کلاس Manager و Worker به همراه سرویس‌ها و Repository هایشان اضافه شده اند که از IPersonService و IPersonRepository مشتق شده اند.
حال کد کلاس Program را به صورت زیر تغییر می‌دهیم
...
 var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
توضیحات
در کد فوق، چون WorkerService بعد از PersonService ثبت یا Register شده است، LightInject در زمان ارسال پارامتر به Form1، نمونه ای از کلاس WorkerService را ایجاد میکند. اما اگر بخواهیم از کلاس PersonService نمونه سازی نماید باید کد را به صورت زیر تغییر دهیم.
...
            container.Register<IPersonService, PersonService>("PersonService");
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
همانطور که مشاهده می‌نمایید، در زمان Register نامی را به آن اختصاص دادیم که در زمان نمونه سازی از این نام استفاده شده است.
اگر در زمان ثبت، نامی را به نمونه‌ی مورد نظر اختصاص داده باشیم، و فقط یک Register برای آن Interface معرفی نموده باشیم، در زمان نمونه سازی، LightInject آن نمونه را به عنوان سرویس پیش فرض در نظر می‌گیرد.
  container.Register<IPersonService, PersonService>("PersonService");
  Application.Run(new Form1(container.GetInstance<IPersonService>()));
در کد فوق، چون برای IPersonService فقط یک کلاس برای نمونه سازی معرفی شده است، با فراخوانی متد GetInstance، حتی بدون ذکر نام، نمونه ای را از کلاس PersonService ایجاد می‌کند.
IEnumerable<T>
زمانی که چند کلاس را که از یک Interface مشتق شده اند، با هم Register می‌نمایید، LightInject این قابلیت را دارد که این کلاس‌های Register شده را در قالب یک لیست شمارشی برگردانید.
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetInstance<IEnumerable<IPersonService>>();
در کد فوق لیستی با دو آیتم ایجاد می‌شود که یک آیتم از نوع PersonService و دیگری از نوع WorkerService می‌باشد. همچنین از کد زیر نیز می‌توانید استفاده کنید:
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetAllInstances<IPersonService>();
به جای متد GetInstance از متد GetAllInstances استفاده شده است.
LightInject از Collection‌های زیر نیز پشتیبانی می‌نماید:
  • Array
  • ICollection<T>
  • IList<T>
  • IReadOnlyCollection<T>
  • IReadOnlyList<T>
Values
توسط LightInject می‌توانید مقادیر ثابت را نیز تعریف کنید
            container.RegisterInstance<string>("SomeValue");
            var value = container.GetInstance<string>();
متغیر value با رشته "SomeValue" مقداردهی می‌گردد. اگر چندین ثابت رشته ای داشته باشید می‌توانید نام جداگانه ای را به هر کدام اختصاص دهید و در زمان فراخوانی مقدار به آن نام اشاره کنید.
            container.RegisterInstance<string>("SomeValue","String1");
            container.RegisterInstance<string>("OtherValue","String2");
            var value = container.GetInstance<string>("String2");
متغیر value با رشته "OtherValue" مقداردهی می‌گردد.
مطالب دوره‌ها
مفاهیم کلیدی مورد استفاده در مدل سازی داده کاوی (بخش دوم)

مفاهیم کلیدی


Case
مهمترین مفهومی است که در تحلیل یک مسئله داده کاوی می‌بایست شناسائی شود و تشخیص اشتباه در شناسائی آن منجر به عدم موفقیت پروژه داده کاوی خواهد شد. Case به معنای یک موجودیت پایه از اطلاعات می‌باشد که عملیات داده کاوی بر روی آن انجام می‌شود و هدف از معرفی آن، معرفی ساختار مسئله به موتور داده کاوی است.  هر Case شامل مجموعه ای از ویژگی‌ها (Attributes) می‌باشد؛ مانند سن، جنسیت. ویژگی‌ها می‌توانند دارای یک مجموعه از مقادیر ممکن باشند که به آنها وضعیت یا مقدار (State/Value) می‌گویند؛ مانند جنسیت که دارای دو وضعیت زن یا مرد می‌باشد.

Case می‌تواند ساده باشد؛ برای نمونه زمانیکه قصد دارید «از اطلاعات آماری مشتریان به منظور تحلیل ریسک وام گرفتن» استفاده کنید، بدین ترتیب هر Case شامل اطلاعات یک مشتری و یا ردیفی از داده مشتریان است.

Case می‌تواند کمی پیچیده‌تر باشد؛ برای مثال زمانیکه می‌خواهید «رفتار خرید مشتری را بر اساس تاریخچه خرید مشتری» تحلیل کنید، که در این صورت هر Case شامل یک رکورد از اطلاعات مشتری به همراه لیستی از محصولاتی که خریداری کرده است، می‌باشد. (توجه کنید تعریف رفتار به طور ضمنی، بیانگر عملکرد در طول زمان می‌باشد)

Case مثال فوق نمونه ای از Nested Case است، که به اطلاعات Details در ساختار Master/Details اشاره دارد. چنانچه Case ای از نوع Nested باشد، الگوریتم‌ها به Case ای به عنوان ورودی فرمت مجموعه ردیف سلسله مراتبی (Hierarchical Row-set) نیاز دارند.

Case Key مشخصه ای است که یکتا بودن هر Case را مشخص می‌کند و اغلب Primary Key یک جدول رابطه ای است، همچنین ممکن است یک کلید ترکیبی باشد. ذکر این نکته ضروری است که بدانیم Case Key فقط یک شناسه است و شامل هیچ الگویی نمی‌باشد و بدین ترتیب غالباً بوسیله الگوریتم‌های داده کاوی نادیده گرفته می‌شود.

Nested Key مهمترین مشخصه ویژگی از بخش Nested هر Case است و در واقع کلید معنایی تحلیل می‌باشد که شامل اطلاعات مفیدی درباره‌ی الگوهاست. به بیان دیگر ویژگی است که عناصر مختلف موجود در Nested Case را به ازای هر Case تفکیک می‌کند. همچنین در نظر داشته باشید که Nested Key یک شناسه نیست و دارای مفهومی متفاوت با Foreign Key است، بدین ترتیب سایر مشخصه‌های دیگر در بخش Nested؛ جهت توصیف Nested Key بکار می‌روند. برای نمونه چنانچه مدلی برای یادگیری الگوهایی درباره رفتار خرید مشتری داشته باشیم، Nested Key برابر با محصول و میزان خرید است.

به همین ترتیب Case Table جدولی است شامل اطلاعات Case و بطور مشابه Nested Table جدولی است که شامل اطلاعات مرتبط با قسمت Nested از Case می‌باشد. از اپراتور Shape به منظور پیوند میان Case Table و Nested Table استفاده می‌شود.

در خصوص Attribute ها (ویژگی ها) از آنجا که هر ویژگی؛ توصیف کننده مسئله داده کاوی از یک منظر خاص می‌باشد، می‌توان اینگونه بیان نمود که هر چه تعداد ویژگی‌ها در یک پروژه بیشتر باشد، توان تحلیل در آن پروژه افزایش می‌یابد. انواع ویژگی‌ها به دو دسته Discrete (گسسته) و Continuous (پیوسته) تقسیم می‌شوند. برای نمونه ویژگی جنسیت، تحصیلات و ... گسسته و همچنین ویژگی سن، درآمد و ... پیوسته هستند. به مقادیر موجود در یک ویژگی پیوسته Value و بطور مشابه به وضعیت‌های موجود در یک ویژگی گسسته State گفته می‌شود. ویژگی‌ها در یک الگوریتم از حیث کاربرد (Attribute Usage) به دو دسته Input و Output تقسیم می‌شوند.

یک الگوریتم از ویژگی‌های ورودی (Input) استفاده می‌کند تا الگویی برای پیش بینی ویژگی‌های خروجی (Output) پیدا کند. همچنین لازم است در نظر داشته باشید که برخی الگوریتم‌ها نظیر Naïve Bayes صرفاً با داده‌های گسسته و بطور مشابه الگوریتم هایی نظیر Logistic Regression تنها با مقادیر پیوسته کار می‌کنند.

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

1- مشکل مطرح شده اصطلاحا Lost Update نام داره (که در مثال جاری باعث میشه یکی از بروزرسانی‌های عمل خرید گم بشه!)
این مشکل توسط Isolation Level سطوح Repeatable Read و Serializable قابل حل هست. 
جدول زیر لیست مشکلات همزمانی به ازای هر سطح از Isolation Level رو نشون میده.


2- استفاده از سطح Isolation Level بالاتر به معنی سخت گیری و احتیاط بیشتر هست و باعث افزایش میزان Blocking و متعاقبا احتمال وقوع Deadlock و نیز کاهش Performance و ظرفیت Concurrency (همزمانی) دیتابیس میشه (و بلعکس)
پس اگر مشکلی رو تونستین با Isolation Level سطح پایین‌تری مثل Repeatable Read حل کنید بهتره نسبت به اینکه Isolation Level سطح بالا‌تری مثل Serializable رو انتخاب کنین
 در تصویر زیر نحوه حل اش با Isolation Level سطح Repeatable Read رو مشاهده میکنین


3- برخلاف روش‌های دیگه، استفاده از Isolation Level سطح Repeatable Read و نیز Serializable در مثال جاری میتونه باعث وقوع Deadlock (بن بست) بشه و این بستگی به این داره که 2 تراکنش در چه نقطه ای به همزمانی میخورن
همونطور که در 2 تصویر زیر میبینین WAITFOR DELAY اولی باعث قوقع Deadlock میشه ولی دومی نمیشه
مثال وقوع Deadlock

توضیح:
اجازه بدید قبل از توضیح چرایی وقوع Deadlock مروری بر چیستی اون داشته باشیم
این مشکل زمانی پیش میاد که 2تا تراکنش مانع اجرای هم دیگه میشن و در بن بستی گیر میکنن که هیچ کدوم نمیتونن کارشون رو تموم کن. به عنوان مثال تراکنش اول قفل A رو به دست میگیره و منتظر آزاد شدن قفل B میشه در حالی که تراکنش دوم قفل B رو به دست گرفته و منتظر آزاد شدن قفل A میشه، در این حالت هر دو تراکنش منتظر اتمام کار یکدیگر هستند و در بن بستی گیر میکنن (Deadlock) که هیچ کدوم نمتونن کارشون رو تموم کنن
در این شرایط SQL Server به ناچار یکی از اون‌ها (در واقع تراکنشی که Rollback اش هزینه کمتری داره) رو به عنوان Victim (قربانی) حساب میکنه و اون رو  Rollback و سپس Kill میکنه تا حداقل دیگری بتونه به کارش ادامه بده

در Isolation Level سطح Serializable و Repeatable Read هر رکوردی که خونده (SELECT) بشه، از تغییر (UPDATE و DELETE) شدن همون رکورد توسط دیگر تراکنش‌ها جلوگیری میشه مادامی که تراکنش اول کارش تموم بشه
پس ترکنش اول مقدار balance رو SELECT میکنه، در همین حال تراکنش دوم نیز مقدار balance رو SELECT میکنه
سپس تراکنش اول میخواد مقدار balance رو UPDATE کنه ولی Block (مسدود) میشه چرا کنه همین رکورد قبلا توسط تراکنش دوم قفل شده، پس منتظر (Wait) تراکنش دوم میشه 
تراکنش دوم نیز میخواد مقدار balance رو UPDATE کنه و این هم Block (مسدود) میشه چرا کنه همین رکورد قبلا توسط تراکنش اول قفل شده، پس منتظر (Wait) تراکنش اول میشه و BOOM !! بن بست یا Deadlock رخ میده، چرا که هر دو تراکنش Block یکدیگه شدن و منتظر آزاد شدن قفل توسط دیگری هستند 

مثال عدم وقوع Deadlock

توضیح:
در این حالت اما تراکنش اول عمل SELECT و UPDATE رو زودتر از تراکنش دوم انجام میده و عمل UPDATE اش توسط تراکنش دوم بلاک (Block) نمیشه چرا که تراکنش دوم هنوز شروع نشده
دقت داشته باشین که در این مثال از WAITFOR TIME استفاده نکردیم که بخواد دقیقا در یک زمان مشخص، هر دو تراکنش رو اجرا بکنه بلکه چون دستی داریم کوئری‌ها رو اجرا میکنیم، همین تاخیر یک ثانیه ایی باعث میشه تراکنش اول کارش رو زودتر شروع کنه و فقط در میانه راه و بعد از عمل UPDATE به همزمانی بخورن

4- هینت HOLDLOCK معادل Isolation Level سطح Serializable هست، برای استفاده از سطح Repeatable Read میتونیم از هینت REPEATABLEREAD استفاده کنیم
صرفا جهت مرور:
عبارات Table Hints دستور هایی هستند که رفتار پیشفرض Query Optimizer (بهینه ساز کوئری) رو به هنگام دستورات DML (مثل SELECT/INSERT/UPDATE/DELETE) تغییر میده (override میکنن) و معمولا برای تغییر سطح قفل و Isolation Level و یا انتخاب Index دلخواه استفاده میشه


5- هینت UPDLOCK دو تا کار انجام میده
1- باعث میشه به جای قفل Shared Lock یا (S) از قفل Update Lock یا (U) بر روی رکورد‌های خوانده شده استفاده بشه
2- همانند سطح Repeatable Read و Serializable (هینت HOLDLOCK) قفل رو تا اتمام Trasanction (و نه صرفا Statement) نگه میداره (Hold میکنه) 
پس در این مثال خاص (و نه همه جا، که دلیل اون رو جلوتر بررسی میکنیم) میتونیم بدون HOLDLOCK هم انجامش بدیم و نیازی به اون نخواهیم داشت.
هینت UPDLOCK معمولا زمانی استفاده میشه که میخوایم رکورد یا رکورد هایی رو  در Statement‌های بعدی تراکنش جاری UPDATE کنیم و نمی‌خوایم در این بین تراکنش همزمان دیگری این دیتا رو تغییر بده


6- دقت داشته باشین که این تصور که چون UPDLOCK و HOLDLOCK هر دو در نگه داشتن (Hold کردن) قفل تا انتهای تراکنش (و نه Statement جاری) مشترک هستند پس به هنگام استفاده از UPDLOCK دیگر نیازی به HOLDLOCK نداریم تصور اشتباهی هست و علت ظریفی داره
یا بهتره این سوال رو اینطور مطرح کنیم که: 
پس چرا استفاده از  HOLDLOCK در کنار UPDLOCK رایج هست؟ و چه فرقی میکنه که HOLDLOCK در کنار UPDLOCK استفاده بکنیم یا خیر؟

قبل از بررسی چرایی این موضوع بهتره مروری بر روی Repeatable Read و Serializable از منظر Lock Mode‌ها (حالات قفل) داشته باشیم

سطح Repeatable Read
در این سطح قفل Shared صرفا به ازای رکورد‌های SELECT شده ایجاد میشه ولی برخلاف Read Committed قفل رو تا اتمام Transaction نگه میداره داره (Hold میکنه) - (نه به محض اتمام Statement)
در نتیجه تا پایان تراکنش جاری  از هر گونه تغییر بر روی دیتای Read شده توسط دیگر تراکنش‌های همزمان جلوگیری میکنه

سطح Serializable
این سطح مشابه سطح Repeatable Read عمل میکنه (یعنی قفل رو تا اتمام تراکنش و نه صرفا Statement جاری نگه میداره) با این تفاوت که از قفل Key-Range Lock به جای Shared Lock استفاده میکنه (البته نه همیشه و استثنا هایی هم وجود داره که جلوتر بررسی میکنیم) و کل بازه (محدوده) رکورد‌های SELECT شده بر اساس شرط WHERE رو قفل گذاری میکنه (بر خلاف Repeatable Read که صرفا به ازای رکورد‌های SELECT شده قفل ایجاد میکرد)
و بدین صورت از مشکل Phantom Read (مانند INSERT شدن رکورد جدیدی در بازه/محدوده قفل شده) جلوگیری میشه
به عنوان مثال در Serializable شرط WHERE Age BETWEEN 18 AND 35 یک قفل Key-Range Lock بر روی بازه (محدوده) 18 تا 35 گذاشته میشه و تمامی اعداد داخل این بازه رو شامل میشه (حتی اگه هیچ رکوردی در این بازه نداشته باشیم) در صورتی که Repeatable Read چون صرفا به ازای رکورد‌های SELECT شده قفل گذاری میشه، که اگه فرض کنیم هیچ رکوردی در این بازه نداریم، هیچ قفلی هم ایجاد نخواهد شد

بررسی نحوه عملکرد Serializable و استثنا‌های اون
در سطح Serializable بر اساس یکی از حالات زیر قفل ایجاد میشه
1- قفل Shared یا (S) روی کل Table
اگه جدول Index ایی نداشته باشه بر روی کل Table قفل Shared Lock یا (S) میگذاره (فرقی هم نمیکنه شرط WHERE داشته باشیم یا نه)
2- قفل Shared یا (S) روی رکورد (Row/Key)‌های Read شده
اگه شرط WHERE مون بر روی یک ستون Index باشه و مستقیما با مقدار مقایسه کنه (مثل عملگر  = یا IN) روی رکورد (Row/Key)‌های Read شده قفل Shared Lock یا (S) میگذاره
3- قفل RangeS-S روی رکورد (Row/Key)‌های Read شده
اگه شرط WHERE مون بر روی یک ستون Index باشه و دستوراتی که بازه (محدوده) رو مقایسه میکنن (مثل عملگر < و > و... یا BETWEEN) روی سطح رکورد (Row/Key) قفل Key-Range Lock یا (RangeS-S) میگذاره
4- قفل RangeS-S روی تمام رکورد‌ها (حتی Read نشده)
اگه شرط WHERE نداشته باشیم یا شرط WHERE مون بر روی یک ستون Index نباشه روی تمام رکورد‌ها (Row/Key) حتی Read نشده، قفل Key-Range Lock یا (RangeS-S) میگذاره

حال بر گردیم به سوال اولمون
در نکته قبلی (شماره 5) دیدیم که در این مثال «خاص» میتونیم بدون استفاده از HOLDLOCK در کنار UPDLOCK کارمون رو انجام بدیم
اما چی چیزی این مسئله رو «خاص» کرده؟
چون شرط WHERE مون بر روی Index جدول (یعنی فیلد user_id) هست و با عملگر (=) که مستقیما مقایسه میکنه (و نه در یک بازه - محدوده)
پس صرفا بر روی رکورد‌های Read شده (SELECT شده) قفل Shared Lock یا (S) گذاشته میشه دقیقا مانند کاری که Repeatable Read انجام میده (پس در این مورد خاص، سطح Repeatable Read و Serializable فرقی با هم ندارن)
همچنین گفتیم که هینت UPDLOCK هم عمل قفل گذاری رو صرفا بر روی رکورد‌های Read شده ایجاد میکنه (پس در این مثال خاص کاملا مشترک و شبیه هستند) و به همین دلیل هست که میتونیم بدون نیاز به HOLDLOCK در کنار UPDLOCK به همون نتیجه برسیم
در تصویر زیر Lock‌های ایجاد شده در 3 حالت رو مشاهده میکنین که هیچ تفاوتی با هم ندارن (از DMV سیستمی sys.dm_tran_locks کمک گرفتیم تا لیست Lock‌های در حال اجرا رو مشاهده کنیم)


7- چه زمانی استفاده از HOLDLOCK در کنار UPDLOCK مفید هست؟
زمانی که به Range-Key Lock نیاز داریم و میخوایم بر روی بازه (محدوده) رکورد‌های SELECT شده قفل بگذاریم (مانند Serializable) و نه صرفا خود رکورد‌های SELECT شده (مانند Repeatable Read)
در واقع شرط WHERE مون بر روی Index و توسط عملگر مساوی هایی که مستقیما مقدار رو چک میکنین (مانند عملگر = یا IN) نیست  
و چون این نکته ای ظریف و نیازمند دقت هست برنامه نویسان ترجیح میدن جهت محکم کاری بیشتر از HOLDLOCK در کنار UPDLOCK استفاده کنند و همین دلیل رایج بودنش هست
مطالب دوره‌ها
پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
در این قسمت قصد داریم با نحوه پیاده سازی امتیاز دهی ستاره‌ای به مطالب، که نمونه‌ای از آن‌را در سایت جاری در قسمت‌های مختلف آن مشاهده می‌کنید، آشنا شویم.


مدل برنامه

در ابتدای کار نیاز است تا ساختاری را جهت ارائه لیستی از مطالب که دارای گزینه امتیاز دهی می‌باشند، تهیه کنیم:
namespace jQueryMvcSample03.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        /// <summary>
        /// اطلاعات رای گیری یک مطلب به صورت یک خاصیت تو در تو یا پیچیده
        /// </summary>
        public Rating Rating { set; get; }

        public BlogPost()
        {
            Rating = new Rating();
        }
    }
}

namespace jQueryMvcSample03.Models
{
    //[ComplexType]
    public class Rating
    {
        public double? TotalRating { get; set; }
        public int? TotalRaters { get; set; }
        public double? AverageRating { get; set; }
    }
}
اگر با EF Code first آشنا باشید، خاصیت Rating تعریف شده در اینجا می‌تواند از نوع ComplexType تعریف شود که شامل جمع امتیازهای داده شده، تعداد کل رای دهنده‌ها و همچنین میانگین امتیازهای حاصل است.


منبع داده فرضی برنامه

using System.Collections.Generic;
using System.Linq;
using jQueryMvcSample03.Models;

namespace jQueryMvcSample03.DataSource
{
    /// <summary>
    /// منبع داده فرضی
    /// </summary>
    public static class BlogPostDataSource
    {
        private static IList<BlogPost> _cachedItems;
        /// <summary>
        /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانی‌ها صورت خواهد گرفت
        /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای
        /// تهیه یک لیست برای تمام کاربران سایت است
        /// </summary>
        static BlogPostDataSource()
        {
            _cachedItems = createBlogPostsInMemoryDataSource();
        }

        /// <summary>
        /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
        /// </summary>        
        private static IList<BlogPost> createBlogPostsInMemoryDataSource()
        {
            var results = new List<BlogPost>();
            for (int i = 1; i < 30; i++)
            {
                results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i, Rating = new Rating { TotalRaters = i + 1, AverageRating = 3.5 } });
            }
            return results;
        }

        /// <summary>
        /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند
        /// شماره صفحه از یک شروع می‌شود
        /// </summary>
        public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _cachedItems
                        .OrderByDescending(x => x.Id)
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
در این مثال نیز از یک منبع داده فرضی تشکیل شده در حافظه استفاده خواهیم کرد تا امکان اجرای پروژه پیوستی را بدون نیاز به بانک اطلاعاتی خاصی و بدون نیاز به مقدمات برپایی آن، به سادگی داشته باشید.
در این منبع داده ابتدا لیستی از مطالب تهیه شده و سپس کش می‌شوند. در ادامه توسط متد GetLatestBlogPosts بازه‌ای از این اطلاعات قابل بازیابی خواهند بود که برای استفاده در حالات صفحه بندی اطلاعات بهینه سازی شده است.


آشنایی با طراحی افزونه jQuery Star Rating

افزودن CSS نمایش امتیازها در ذیل هر مطلب

/* star rating system */
.post_rating
{
direction: ltr;
}
.rating
{
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
display: inline-block;
width: 8px;
height: 16px;
}
.rating.stars
{
background-image: url('Images/star_rating.png');
}
.rating.stars.active
{
cursor: pointer;
}
.star-left_off
{
background-position: -0px -0px;
}
.star-left_on
{
background-position: -16px -0px;
}
.star-right_off
{
background-position: -8px -0px;
}
.star-right_on
{
background-position: -24px -0px;
}
برای نمایش ستاره‌ها و کار با تصویر Images/star_rating.png (که در پروژه پیوست قرار دارد) ابتدا نیاز است CSS فوق را به پروژه خود اضافه نمائید.

افزودن افزونه jQuery Star rating

// <![CDATA[
(function ($) {
    $.fn.StarRating = function (options) {
        var defaults = {            
            ratingStarsSpan: '.rating.stars',
            postInfoUrl: '/',
            loginUrl: '/login',
            errorHandler: null,
            completeHandler: null,
            onlyOneTimeHandler: null
        };
        var options = $.extend(defaults, options);

        return this.each(function () {
            var ratingStars = $(this);

            $(ratingStars).unbind('mouseover');
            $(ratingStars).mouseover(function () {
                var span = $(this).parent("span");
                var newRating = $(this).attr("value");
                setRating(span, newRating);
            });

            $(ratingStars).unbind('mouseout');
            $(ratingStars).mouseout(function () {
                var span = $(this).parent("span");
                var rating = span.attr("rating");
                setRating(span, rating);
            });

            $(ratingStars).unbind('click');
            $(ratingStars).click(function () {
                var span = $(this).parent("span");
                var newRating = $(this).attr("value");
                var text = span.children("span");
                var pID = span.attr("post");
                var type = span.attr("sectiontype");
                postData({ postID: pID, rating: newRating, sectionType: type });
                span.attr("rating", newRating);
                setRating(span, newRating);
            });

            function setRating(span, rating) {
                span.find(options.ratingStarsSpan).each(function () {
                    var value = parseFloat($(this).attr("value"));
                    var imgSrc = $(this).attr("class");
                    if (value <= rating)
                        $(this).attr("class", imgSrc.replace("_off", "_on"));
                    else
                        $(this).attr("class", imgSrc.replace("_on", "_off"));
                });
            }

            function postData(dataJsonArray) {
                $.ajax({
                    type: "POST",
                    url: options.postInfoUrl,
                    data: JSON.stringify(dataJsonArray),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    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 (data == "nok") {
                            if (options.onlyOneTimeHandler)
                                options.onlyOneTimeHandler(this);
                        }
                        else {
                            if (options.completeHandler)
                                options.completeHandler(this);
                        }
                    }
                });
            }
        });
    };
})(jQuery);
// ]]>
اطلاعات فوق، فایل jquery.StarRating.js را تشکیل می‌دهند که باید به پروژه اضافه گردند.
کاری که این افزونه انجام می‌دهد ردیابی حرکت ماوس بر روی ستاره‌های نمایش داده شده و سپس ارسال سه پارامتر ذیل به اکشن متدی که توسط پارامتر postInfoUrl مشخص می‌گردد، پس از کلیک کاربر می‌باشد:
 { postID: pID, rating: newRating, sectionType: type }
همانطور که ملاحظه می‌کنید به ازای هر قطعه رای گیری که به صفحه اضافه می‌شود، Id مطلب، رای داده شده و نام قسمت جاری، به اکشن متدی خاص ارسال خواهند گردید. sectionType از این جهت اضافه گردیده است تا بتوانید با بیش از یک جدول کار کنید و از این افزونه در قسمت‌های مختلف سایت به سادگی بتوانید استفاده نمائید.
در اینجا از errorHandler برای نمایش خطاها، از completeHandler برای نمایش تشکر به کاربر و از onlyOneTimeHandler برای نمایش اخطار مثلا «یکبار بیشتر مجاز نیستید به ازای یک مطلب رای دهید»، می‌توان استفاده کرد.

بنابراین تا اینجا فایل layout برنامه تقریبا چنین مداخلی را خواهد داشت:
<head>
    <title>@ViewBag.Title</title>    
    <link href="@Url.Content("Content/starRating.css")" rel="stylesheet" type="text/css" />
    <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.StarRating.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>

طراحی یک HTML helper برای نمایش ستاره‌های امتیاز دهی

ابتدا پوشه استاندارد app_code را به پروژه اضافه کرده و سپس فایلی را به نام StarRatingHelper.cshtml، با محتوای ذیل به آن اضافه نمائید:
@using System.Globalization
@helper AddStarRating(int postId,
                      double? average = 0, int? postRatingsCount = 0, string type = "BlogPost",
                      string tooltip = "لطفا جهت رای دادن کلیک نمائید")
    {
        string actIt = "active ";
        if (!average.HasValue) { average = 0; }
        if (!postRatingsCount.HasValue) { postRatingsCount = 0; }
    
    <span class='postRating' rating='@average' post='@postId' title='@tooltip' sectiontype='@type'>
        @for (double i = .5; i <= 5.0; i = i + .5)
        {
            string left;
            if (i <= average)
            {
                left = (i * 2) % 2 == 1 ? "left_on" : "right_on";
            }
            else
            {
                left = (i * 2) % 2 == 1 ? "left_off" : "right_off";
            }
            <span class='rating stars @(actIt)star-@left' value='@i'></span>
        }
        &nbsp;
        @if (postRatingsCount > 0)
        {
            var ratingInfo = string.Format(CultureInfo.InvariantCulture, "امتیاز {0:0.00} از 5 توسط {1} نفر", average, postRatingsCount);
            <span>@ratingInfo</span>                
        }
        else
        {
            <span></span>
        }
    </span>
}
از این Html helper برای تشکیل ساختار نمایش قطعه امتیاز دهی به یک مطلب استفاده خواهیم کرد که توسط افزونه جی‌کوئری فوق ردیابی می‌شود.


کنترلر ذخیره سازی اطلاعات دریافتی برنامه

using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample03.DataSource;
using jQueryMvcSample03.Security;

namespace jQueryMvcSample03.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var postsList = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0);
            return View(postsList); //نمایش صفحه اصلی
        }


        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult SaveRatings(int? postId, double? rating, string sectionType)
        {
            if (postId == null || rating == null || string.IsNullOrWhiteSpace(sectionType))
                return Content(null); //اعلام بروز خطا

            if (!this.HttpContext.CanUserVoteBasedOnCookies(postId.Value, sectionType))
                return Content("nok"); //اعلام فقط یکبار مجاز هستید رای دهید

            switch (sectionType) //قسمت‌های مختلف سایت که در جداول مختلفی قرار دارند نیز می‌توانند گزینه امتیاز دهی داشته باشند
            {
                case "BlogPost":
                    //الان شماره مطلب و رای ارسالی را داریم که می‌توان نسبت به ذخیره آن اقدام کرد
                    //مثلا
                    //_blogPostsService.SaveRating(postId.Value, rating.Value);
                    break;

                //... سایر قسمت‌های دیگر سایت

                default:
                    return Content(null); //اعلام بروز خطا
            }

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

        [HttpGet]
        public ActionResult Post(int? id)
        {
            if (id == null)
                return Redirect("/");

            //todo: show the content here
            return Content("Post " + id.Value);
        }
    }
}
در اینجا کنترلری را که کار پردازش کلیک کاربر را بر روی امتیازی خاص انجام می‌دهد، ملاحظه می‌کنید.
امضای اکشن متد SaveRatings دقیقا بر اساس سه پارامتر ارسالی توسط jquery.StarRating.js که پیشتر توضیح داده شد، تعیین گردیده است. در این متد ابتدا بررسی می‌شود که آیا اطلاعاتی دریافت شده است یا خیر. اگر خیر، null را بازگشت خواهد داد. سپس توسط متد CanUserVoteBasedOnCookies بررسی می‌شود که آیا کاربر می‌تواند (خصوصا مجددا) رای دهد یا خیر. این افزونه برای رای دهی کاربران وارد نشده به سیستم نیز مناسب است. به همین جهت از کوکی‌ها برای ثبت اطلاعات رای دادن کاربران استفاده گردیده است. پیاده سازی متد CanUserVoteBasedOnCookies را در ادامه ملاحظه خواهید نمود.
در ادامه در متد SaveRatings، یک switch تشکیل شده است تا بر اساس نام قسمت مرتبط به رای گیری، اطلاعات را بتوان به سرویس خاصی در برنامه هدایت کرد. مثلا اطلاعات قسمت مطالب به سرویس مطالب و قسمت نظرات به سرویس نظرات هدایت شوند.


متدهایی برای کار با کوکی‌ها در ASP.NET MVC

using System;
using System.Web;

namespace jQueryMvcSample03.Security
{
    public static class CookieHelper
    {
        public static bool CanUserVoteBasedOnCookies(this HttpContextBase httpContext, int postId, string sectionType)
        {
            string key = sectionType + "-" + postId;
            var value = httpContext.GetCookieValue(key);
            if (string.IsNullOrWhiteSpace(value))
            {
                httpContext.AddCookie(key, key);
                return true;
            }
            return false;
        }

        public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value)
        {
            httpContextBase.AddCookie(cookieName, value, DateTime.Now.AddDays(30));
        }

        public static void AddCookie(this HttpContextBase httpContextBase, string cookieName, string value, DateTime expires)
        {
            var cookie = new HttpCookie(cookieName)
            {
                Expires = expires,
                Value = httpContextBase.Server.UrlEncode(value) // For Cookies and Unicode characters
            };
            httpContextBase.Response.Cookies.Add(cookie);
        }

        public static string GetCookieValue(this HttpContextBase httpContext, string cookieName)
        {
            var cookie = httpContext.Request.Cookies[cookieName];
            if (cookie == null)
                return string.Empty; //cookie doesn't exist

            // For Cookies and Unicode characters
            return httpContext.Server.UrlDecode(cookie.Value);
        }
    }
}
در اینجا یک سری متد الحاقی را ملاحظه می‌کنید که برای ثبت اطلاعات رای داده شده یک کاربر بر اساس Id مطلب و نام قسمت متناظر با آن در یک کوکی طراحی شده‌اند. بدیهی است اگر تمام قسمت‌های برنامه شما محافظت شده هستند و کاربران حتما نیاز است ابتدا به سیستم لاگین نمایند، می‌توانید این قسمت را حذف کرده و اطلاعات postId و SectionType را به ازای هر کاربر، جداگانه در بانک اطلاعاتی ثبت و بازیابی نمائید (دقیق‌ترین حالت ممکن؛ البته برای سیستمی بسته که حتما تمام قسمت‌های آن نیاز به اعتبار سنجی دارند).


پیشنهادی در مورد نحوه ذخیره سازی اطلاعات دریافتی

using jQueryMvcSample03.Models;

namespace jQueryMvcSample03.DataSource
{
    public interface IBlogPostsService
    {
        void SaveRating(int postId, double rating);
    }

    public class SampleService : IBlogPostsService
    {
        /// <summary>
        /// یک نمونه از متد ذخیره سازی اطلاعات پیشنهادی
        /// فقط برای ایده گرفتن
        /// بدیهی است محل قرارگیری اصلی آن در لایه سرویس برنامه شما خواهد بود
        /// </summary>
        public void SaveRating(int postId, double rating)
        {
            BlogPost post = null;
            //post = _blogCtx.Find(postId); // بر اساس شماره مطلب، مطلب یافت شده و فیلدهای آن تنظیم می‌شوند
            if (post == null) return;

            if (!post.Rating.TotalRaters.HasValue) post.Rating.TotalRaters = 0;
            if (!post.Rating.TotalRating.HasValue) post.Rating.TotalRating = 0;
            if (!post.Rating.AverageRating.HasValue) post.Rating.AverageRating = 0;

            post.Rating.TotalRaters++;
            post.Rating.TotalRating += rating;
            post.Rating.AverageRating = post.Rating.TotalRating / post.Rating.TotalRaters;

            // todo: call save changes at the end.
        }
    }
}
همانطور که عنوان شد، سه داده Id مطلب، رای داده شده و نام قسمت متناظر به اکشن متد ارسال می‌شود. از نام قسمت، برای انتخاب سرویس ذخیره سازی اطلاعات استفاده خواهیم کرد. این سرویس می‌تواند شامل متدی به نام SaveRating، همانند کدهای فوق باشد که Id مطلب و عدد رای حاصل به آن ارسال می‌گردند. ابتدا بر اساس این Id، مطلب متناظر یافت شده و سپس اطلاعات Rating آن به روز خواهد شد. در پایان هم ذخیره سازی اطلاعات باید صورت گیرد.



Viewهای برنامه

قسمت پایانی کار ما در اینجا تهیه دو View است:
الف) یک Partial view که لیست مطالب را به همراه گزینه رای دهی به آن‌ها رندر می‌کند.
ب) View کاملی که از این Partial View استفاده کرده و همچنین افزونه jquery.StarRating.js را فراخوانی می‌کند.
@using System.Text.RegularExpressions
@model IList<jQueryMvcSample03.Models.BlogPost>
<ul>
    @foreach (var item in Model)
    {
        <li>
            <fieldset>
            <legend>مطلب @item.Id</legend>
                <h5>
                    @Html.ActionLink(linkText: item.Title,
                                 actionName: "Post",
                                 controllerName: "Home",
                                 routeValues: new { id = item.Id },
                                 htmlAttributes: null)
                </h5>
                @item.Body
                <div class="post_rating">
                    @Html.Raw(Regex.Replace(@StarRatingHelper.AddStarRating(item.Id, item.Rating.AverageRating, item.Rating.TotalRaters, "BlogPost").ToHtmlString(), @">\s+<", "><"))
                </div>
            </fieldset>
        </li>
    }
</ul>
کدهای _ItemsList.cshtml را در اینجا ملاحظه می‌کند که در آن نحوه فراخوانی متد کمکی StarRatingHelper.AddStarRating ذکر شده است.
اگر به کدهای آن دقت کنید از Regex.Replace برای حذف فاصله‌های خالی و خطوط جدید بین تگ‌ها استفاده گردیده است. اگر اینکار انجام نشود، نیمه‌های ستاره‌های نمایش داده شده، با فاصله از یکدیگر رندر می‌شوند که صورت خوشایندی ندارد.

و نهایتا View ایی که از این اطلاعات استفاده می‌کنید ساختار زیر را خواهد داشت:
@model IList<jQueryMvcSample03.Models.BlogPost>
@{
    ViewBag.Title = "Index";
    var postInfoUrl = Url.Action(actionName: "SaveRatings", controllerName: "Home");
}
<h2>
    سیستم امتیاز دهی</h2>
@{ Html.RenderPartial("_ItemsList", Model); }
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $(".rating.stars.active").StarRating({
                ratingStarsSpan: '.rating.stars',
                postInfoUrl: '@postInfoUrl',
                loginUrl: '/login',
                errorHandler: function () {
                    alert('خطایی رخ داده است');
                },
                completeHandler: function () {
                    alert('با تشکر! رای شما با موفقیت ثبت شد');
                },
                onlyOneTimeHandler: function () {
                    alert('فقط یکبار می‌توانید به ازای هر مطلب رای دهید');
                }
            });
        });
    </script>
}
در این View لیستی از مطالب دریافت و به partial view طراحی شده برای نمایش ارسال می‌شود. سپس افزونه StarRating نیز تنظیم و به صفحه اضافه خواهد گردید. نکته مهم آن تعیین صحیح اکشن متدی است که قرار است اطلاعات را دریافت کند و نحوه مقدار دهی آن‌را توسط متغیر postInfoUrl مشاهده می‌کنید.

دریافت کدها و پروژه کامل این قسمت
jQueryMvcSample03.zip
نظرات مطالب
کار با کلیدهای اصلی و خارجی در EF Code first
ولی با این مثالی که زدم برای من رکوردها ثبت نشدند که! :-؟
اما این راههایی که گفتید رو رفتم بیشترشو و در نهایت مشکل با حذف خط 9 حل شد.
فکر کنم مشکل از 2بار ثبت شدن موجودیتی تکراری از Tb2 در این جدول باشه! (گرچه علت رو نفهمیدم فکر کنم هنوز!) یکبار در خط 9 که مستقیما موجودیت را به نشانه Added علامت گذاری می‌کنم در ChangeTracker :
sv2.Add(t2);
و یکبار در خط 12 که همان موجودیت را در navigation property مربوط به موجودیتی از Tb1 مقدار دهی می‌کنم و در خط بعدش که SaveChanges را فراخوانی می‌کنم :
t1.ListTb2.Add(t2);
 (گرچه فکر می‌کردم یک خطای منطقی باید رخ بدهد و حداقل 2بار بصورت duplicate و با id متفاوت ثبت بشه نه اینکه به اینصورت خطا بگیره)

من CodeFirst کار می‌کنم که البته بیشتر اوقات بانک ساخته شده هست و باید به بانک موجود mapping کنم. بیشتر می‌خواستم نحوه عملکرد ef رو روی روابط چند-ب-چند ببینم و اینکه روی یک بانک از قبل آماده به چه صورت باید انجامش بدم. از  Ef PowerTools کمک می‌گیرم و روی مسایل حول اینها مشغول تست و کلنجار هستم. مخصوصا حالت‌های مختلف در روابط و جداول رابطه.
تشکر مجدد
نظرات مطالب
بررسی Source Generators در #C - قسمت پنجم - نوشتن آزمون‌های واحد
یک نکته‌ی تکمیلی: روش آزمایش کردن یک کنترلر MVC

فرض کنید می‌خواهید یک قطعه کد به شکل زیر را آزمایش کنید:
public class WeatherForecastController : ControllerBase
اگر از روش فوق استفاده کنید، آزمون واحد در سطر زیر با شکست مواجه می‌شود:
Assert.IsTrue(outputCompilation.GetDiagnostics().IsEmpty)
چون این سطر، بیانگر خطاهای کامپایلر است که خالی نیست و نتوانسته ارجاعات پروژه را بازیابی کند. برای رفع این مشکل، در متد کمکی RunGenerators که تهیه شد، باید ارجاعات زیر را به ارجاعات موجود، اضافه کرد:
references = references.Concat(new[]
        {
            MetadataReference.CreateFromFile(typeof(ControllerBase).GetTypeInfo().Assembly.Location),
            MetadataReference.CreateFromFile(typeof(IActionResult).GetTypeInfo().Assembly.Location)
        });
و همچنین جهت شناسایی آن‌ها باید تعریف زیر را نیز به فایل csproj. پروژه‌ی آزمون واحد، اضافه کرد:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App"/>
    </ItemGroup>
</Project>
به علاوه می‌توان نوع خروجی را هم بجای یک برنامه‌ی کنسول، به یک dll. تغییر داد:
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)