نظرات مطالب
استفاده از popBox برای کوچک کردن خودکار تصاویر بزرگ
A day without Learning is a lost day
وحید نصیری همچنان وحید نصیری هست.
تشکر فراوان از پشتکار شما و به اشتراک گذاشتن روزانه ی دانسته هایتان
درود
مطالب
کوئری نویسی در EF Core - قسمت هفتم - کار با رشته‌‌ها
هدف از این سری مثال‌ها، آشنایی با متدها و توابعی است که در حین کار با خواص رشته‌ای در LINQ to Entities، مجاز به استفاده‌ی از آن‌ها هستیم و همچنین اگر تابعی در EF-Core هنوز تعریف نشده بود، راه حل چیست.


مثال 1: نام تمام کاربران را با قالب 'Surname, Firstname'  نمایش دهید.

var members = context.Members
                                    .Select(member => new { Name = member.Surname + ", " + member.FirstName })
                                    .ToList();
متد Select می‌تواند به همراه اعمال محاسباتی ساده‌ای نیز باشد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.
با این خروجی:



مثال 2: تمام امکاناتی را که با Tennis شروع می‌شوند، لیست کنید.
این گزارش به همراه تمام ستون‌های جدول است.

var facilities = context.Facilities
                                        .Where(facility => facility.Name.StartsWith("Tennis"))
                                        .ToList();
متدهای استانداردی مانند StartsWith، EndsWith و Contains را می‌توان بر روی خواص رشته‌ای بکار برد.
با این خروجی:



مثال 3: تمام امکاناتی را که با tennis شروع می‌شوند، لیست کنید. این جستجو باید غیرحساس به بزرگی و کوچکی حروف باشد.
این گزارش به همراه تمام ستون‌های جدول است.

نیازی به انجام مجزای این تمرین نیست؛ چون پاسخ آن همان پاسخ مثال 2 است. Collation پیش‌فرض در SQL Server، غیرحساس به بزرگی و کوچکی حروف است. بنابراین چه tennis را جستجو کنیم و یا TeNnis را، تفاوتی نمی‌کند.


مثال 4: شماره تلفن‌های دارای پرانتز را لیست کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

روش اول: در اینجا دوبار از متد Contains استفاده شده‌است:
var members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => member.Telephone.Contains("(")
                                                    && member.Telephone.Contains(")"))
                                    .ToList();
با این خروجی:


روش دوم: اگر می‌خواهیم کنترل بیشتری را بر روی خروجی نهایی LIKE تولیدی داشته باشیم، می‌توان از متد سفارشی استاندارد EF.Functions.Like استفاده کرد که از حروف wild cards نیز پشتیبانی می‌کند:
members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => EF.Functions.Like(member.Telephone, "%[()]%"))
                                    .ToList();
با این خروجی:



مثال 5: کد پستی‌ها 5 رقمی هستند. گزارشی را تهیه کنید که در آن اگر کدپستی کمتر از 5 رقم بود، ابتدای آن با صفر شروع شود.
هدف اصلی از این مثال، اعمال متد PadLeft(5, '0') به خاصیت member.ZipCode است.

روش اول: EF-Core فعلا قابلیت ترجمه‌ی PadLeft(5, '0') را به معادل SQL آن‌را ندارد. به همین جهت مجبور هستیم ابتدا ZipCode‌ها را به صورت رشته‌ای بازگشت دهیم که در اینجا استفاده‌ی از Convert.ToString مجاز است.
با این خروجی:
SELECT   CONVERT (NVARCHAR (MAX), [m].[ZipCode]) AS [Zip]
FROM     [Members] AS [m]
ORDER BY CONVERT (NVARCHAR (MAX), [m].[ZipCode]);
 سپس می‌توان بر روی لیست آماده‌ی موجود در حافظه، از LINQ to Objects استفاده کرد و در این حالت دسترسی کاملی به تمام امکانات زبان #C وجود دارد:
var members = context.Members
                                    .Select(member => new { ZipCode = Convert.ToString(member.ZipCode) })
                                    .OrderBy(m => m.ZipCode)
                                    .ToList();
// Now using LINQ to Objects
members = members.Select(member => new { ZipCode = member.ZipCode.PadLeft(5, '0') })
                                                    .OrderBy(m => m.ZipCode)
                                                    .ToList();

روش دوم: SQL Server به همراه تابع استانداردی به نام Replicate است که از آن می‌توان برای شبیه سازی PadLeft، بدون متوسل شدن به LINQ to Objects، استفاده کرد. اما چون این تابع هنوز به EF-Core معرفی نشده‌است، نیاز است خودمان اینکار را انجام دهیم. در این روش، از متد SqlDbFunctionsExtensions.SqlReplicate استفاده می‌شود. روش تعریف این نوع متدها را در مطلب «امکان تعریف توابع خاص بانک‌های اطلاعاتی در EF Core» پیشتر بررسی کرده‌ایم که برای مثال در اینجا چنین شکلی را پیدا می‌کند:
namespace EFCorePgExercises.Utils
{
    public static class SqlDbFunctionsExtensions
    {
        public static string SqlReplicate(string expression, int count)
            => throw new InvalidOperationException($"{nameof(SqlReplicate)} method cannot be called from the client side.");

        private static readonly MethodInfo _sqlReplicateMethodInfo = typeof(SqlDbFunctionsExtensions)
            .GetRuntimeMethod(
                nameof(SqlDbFunctionsExtensions.SqlReplicate),
                new[] { typeof(string), typeof(int) }
            );


        public static void AddCustomSqlFunctions(this ModelBuilder modelBuilder)
        {
            modelBuilder.HasDbFunction(_sqlReplicateMethodInfo)
                .HasTranslation(args =>
                {
                    return SqlFunctionExpression.Create("REPLICATE",
                        args,
                        _sqlReplicateMethodInfo.ReturnType,
                        typeMapping: null);
                });
        }
    }
}
پس از آن فقط کافی است متد AddCustomSqlFunctions را به Context برنامه معرفی کنیم:
namespace EFCorePgExercises.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
         // ...

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
         // ...
            modelBuilder.AddCustomSqlFunctions();
         // ...
        }
    }
}
اکنون می‌توان از تابع SqlDbFunctionsExtensions.SqlReplicate جهت شبیه سازی PadLeft به صورت زیر استفاده کرد:
var newMembers = context.Members
                                        .Select(member => new
                                        {
                                            ZipCode =
                                                SqlDbFunctionsExtensions.SqlReplicate(
                                                    "0", 5 - Convert.ToString(member.ZipCode).Length)
                                                + member.ZipCode
                                        })
                        .OrderBy(m => m.ZipCode)
                        .ToList();
با این خروجی:



مثال 6: اولین حرف نام خانوادگی کاربران در کل ردیف‌های جدول چندبار تکرار شده‌است؟
این گزارش باید به همراه ستون‌های letter,  count باشد.

var members = context.Members
                                    .Select(member => new { Letter = member.Surname.Substring(0, 1) })
                                    .GroupBy(m => m.Letter)
                                    .Select(g => new
                                    {
                                        Letter = g.Key,
                                        Count = g.Count()
                                    })
                                    .OrderBy(r => r.Letter)
                                    .ToList();
هدف از این مثال بیان مجاز بودن استفاده‌ی از متد Substring بر روی خواص رشته‌ای است که EF-Core امکان ترجمه‌ی آن‌ها را به کدهای SQL دارد.
با این خروجی:



مثال 7: حروف '-','(',')', ' ' را از شماره تلفن‌ها حذف کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

بانک اطلاعاتی PostgreSQL به همراه تابع استاندارد regexp_replace است و می‌توان از آن برای حل یک چنین مسایلی استفاده کرد:
select memid, regexp_replace(telephone, '[^0-9]', '', 'g') as telephone
from members
order by memid;
اما SQL Server هنوز هم به همراه یک چنین تابعی نیست. بنابراین از روش زیر نیز می‌توان مثال جاری را حل کرد:
var members = context.Members
                                .Select(member => new
                                {
                                    member.MemId,
                                    Telephone = member.Telephone.Replace("-", "")
                                                        .Replace("(", "")
                                                        .Replace(")", "")
                                                        .Replace(" ", "")
                                })
                                .OrderBy(r => r.MemId)
                                .ToList();
با این خروجی:



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
آشنایی با NHibernate - قسمت ششم

آشنایی با Automapping در فریم ورک Fluent NHibernate

اگر قسمت‌های قبل را دنبال کرده باشید، احتمالا به پروسه طولانی ساخت نگاشت‌ها توجه کرده‌اید. با کمک فریم ورک Fluent NHibernate می‌توان پروسه نگاشت domain model خود را به data model متناظر آن به صورت خودکار نیز انجام داد و قسمت عمده‌ای از کار به این صورت حذف خواهد شد. (این مورد یکی از تفاوت‌های مهم NHibernate با نمونه‌های مشابهی است که مایکروسافت تا تاریخ نگارش این مقاله ارائه داده است. برای مثال در نگار‌ش‌های فعلی LINQ to SQL یا Entity framework ، اول دیتابیس مطرح است و بعد ساخت کد از روی آن، در حالیکه در اینجا ابتدا کد و طراحی سیستم مطرح است و بعد نگاشت آن به سیستم داده‌ای و دیتابیس)

امروز قصد داریم یک سیستم ساده ثبت خبر را از صفر با NHibernate پیاده سازی کنیم و همچنین مروری داشته باشیم بر قسمت‌های قبلی.

مطابق کلاس دیاگرام فوق، این سیستم از سه کلاس خبر، کاربر ثبت کننده‌ی خبر و گروه خبری مربوطه تشکیل شده است.

ابتدا یک پروژه کنسول جدید را به نام NHSample2 آغاز کنید. سپس ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

سپس پوشه‌ای را به نام Domain به این پروژه اضافه نمائید (کلیک راست روی نام پروژه در VS.Net و سپس مراجعه به منوی Add->New folder). در این پوشه تعاریف موجودیت‌های برنامه را قرار خواهیم داد. سه کلاس جدید Category ، User و News را در این پوشه ایجاد نمائید. محتویات این سه کلاس به شرح زیر هستند:

namespace NHSample2.Domain
{
public class User
{
public virtual int Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
}
}


namespace NHSample2.Domain
{
public class Category
{
public virtual int Id { get; set; }
public virtual string CategoryName { get; set; }
}
}


using System;

namespace NHSample2.Domain
{
public class News
{
public virtual Guid Id { get; set; }
public virtual string Subject { get; set; }
public virtual string NewsText { get; set; }
public virtual DateTime DateEntered { get; set; }
public virtual Category Category { get; set; }
public virtual User User { get; set; }
}
}
همانطور که در قسمت‌های قبل نیز ذکر شد، تمام خواص پابلیک کلاس‌های Domain ما به صورت virtual تعریف شده‌اند تا lazy loading را در NHibernate فعال سازیم. در حالت lazy loading ، اطلاعات تنها زمانیکه به آن‌ها نیاز باشد بارگذاری خواهند شد. این مورد در حالتیکه نیاز به نمایش اطلاعات تنها یک شیء وجود داشته باشد بسیار مطلوب می‌باشد، یا هنگام ثبت و به روز رسانی اطلاعات نیز یکی از بهترین روش‌ها است. اما زمانیکه با لیستی از اطلاعات سروکار داشته باشیم باعث کاهش افت کارآیی خواهد شد زیرا برای مثال نمایش آن‌ها سبب خواهد شد که 100 ها کوئری دیگر جهت دریافت اطلاعات هر رکورد در حال نمایش اجرا شود (مفهوم دسترسی به اطلاعات تنها در صورت نیاز به آن‌ها). Lazy loading و eager loading (همانند مثال‌های قبلی) هر دو در NHibernate به سادگی قابل تنظیم هستند (برای مثال LINQ to SQL به صورت پیش فرض همواره lazy load است و تا این تاریخ راه استانداردی برای امکان تغییر و تنظیم این مورد پیش بینی نشده است).

اکنون کلاس جدید Config را به برنامه اضافه نمائید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace NHSample2
{
class Config
{
public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly).Configure(cfg);

return cfg;
}

public static void GenerateDbScript(Configuration config, string filePath)
{
bool script = true;//فقط اسکریپت دیتابیس تولید گردد
bool export = false;//نیازی نیست بر روی دیتابیس هم اجرا شود
new SchemaExport(config).SetOutputFile(filePath).Create(script, export);
}

public static void BuildDbSchema(Configuration config)
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool drop = false;//آیا اطلاعات موجود دراپ شوند
new SchemaExport(config).Execute(script, export, drop);
}

public static void CreateSQL2008DbPlusScript(string connectionString, string filePath)
{
Configuration cfg =
GenerateMapping(
MsSqlConfiguration
.MsSql2008
.ConnectionString(connectionString)
.ShowSql()
);
GenerateDbScript(cfg, filePath);
BuildDbSchema(cfg);
}

public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
{
return
Fluently.Configure().Database(dbType)
.Mappings(m => m.AutoMappings
.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly))
)
.BuildSessionFactory();
}
}
}

در متد GenerateMapping از قابلیت Automapping موجود در فریم ورک Fluent Nhibernate استفاده شده است (بدون نوشتن حتی یک سطر جهت تعریف این نگاشت‌ها). این متد نوع دیتابیس مورد نظر را جهت ساخت تنظیمات خود دریافت می‌کند. سپس با کمک کلاس AutoPersistenceModel این فریم ورک، به صورت خودکار از اسمبلی برنامه نگاشت‌های لازم را به کلاس‌های موجود در پوشه Domain ما اضافه می‌کند (مرسوم است که این پوشه در یک پروژه Class library مجزا تعریف شود که در این برنامه جهت سهولت کار در خود برنامه قرار گرفته است). قسمت Where ذکر شده به این جهت معرفی گردیده است تا Fluent Nhibernate برای تمامی کلاس‌های موجود در اسمبلی جاری، سعی در تعریف نگاشت‌های لازم نکند. این نگاشت‌ها تنها به کلاس‌های موجود در پوشه دومین ما محدود شده‌اند.
سه متد بعدی آن، جهت ایجاد اسکریپت دیتابیس از روی این نگاشت‌های تعریف شده و سپس اجرای این اسکریپت بر روی دیتابیس جاری معرفی شده، تهیه شده‌اند. برای مثال CreateSQL2008DbPlusScript یک مثال ساده از استفاده دو متد قبلی جهت ایجاد اسکریپت و دیتابیس متناظر اس کیوال سرور 2008 بر اساس نگاشت‌های برنامه است.
با متد CreateSessionFactory در قسمت‌های قبل آشنا شده‌اید. تنها تفاوت آن در این قسمت، استفاده از کلاس AutoPersistenceModel جهت تولید خودکار نگاشت‌ها است.

در ادامه دیتابیس متناظر با موجودیت‌های برنامه را ایجاد خواهیم کرد:

using System;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
Config.CreateSQL2008DbPlusScript(
"Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true",
"db.sql");

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

پس از اجرای برنامه، ابتدا فایل اسکریپت دیتابیس به نام db.sql در پوشه اجرایی برنامه تشکیل خواهد شد و سپس این اسکریپت به صورت خودکار بر روی دیتابیس معرفی شده اجرا می‌گردد. دیتابیس دیاگرام حاصل را در شکل زیر می‌توانید ملاحظه نمائید:



همچنین اسکریپت تولید شده آن، صرفنظر از عبارات drop اولیه، به صورت زیر است:

create table [Category] (
Id INT IDENTITY NOT NULL,
CategoryName NVARCHAR(255) null,
primary key (Id)
)

create table [User] (
Id INT IDENTITY NOT NULL,
UserName NVARCHAR(255) null,
Password NVARCHAR(255) null,
primary key (Id)
)

create table [News] (
Id UNIQUEIDENTIFIER not null,
Subject NVARCHAR(255) null,
NewsText NVARCHAR(255) null,
DateEntered DATETIME null,
Category_id INT null,
User_id INT null,
primary key (Id)
)

alter table [News]
add constraint FKE660F9E1C9CF79
foreign key (Category_id)
references [Category]

alter table [News]
add constraint FKE660F95C1A3C92
foreign key (User_id)

references [User]

اکنون یک سری گروه خبری، کاربر و خبر را به دیتابیس خواهیم افزود:

using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample2.Domain;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
//با توجه به کلیدهای خارجی تعریف شده ابتدا باید گروه‌ها را اضافه کرد
Category ca = new Category() { CategoryName = "Sport" };
session.Save(ca);
Category ca2 = new Category() { CategoryName = "IT" };
session.Save(ca2);
Category ca3 = new Category() { CategoryName = "Business" };
session.Save(ca3);

//سپس یک کاربر را به دیتابیس اضافه می‌کنیم
User u = new User() { Password = "123$5@1", UserName = "VahidNasiri" };
session.Save(u);

//اکنون می‌توان یک خبر جدید را ثبت کرد

News news = new News()
{
Category = ca,
User = u,
DateEntered = DateTime.Now,
Id = Guid.NewGuid(),
NewsText = "متن خبر جدید",
Subject = "عنوانی دلخواه"
};
session.Save(news);

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

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
جهت بررسی انجام عملیات ثبت هم می‌توان به دیتابیس مراجعه کرد، برای مثال:



و یا می‌توان از LINQ استفاده کرد:
برای مثال کاربر VahidNasiri تعریف شده را یافته، اطلاعات آن‌را نمایش دهید؛ سپس نام او را به Vahid ویرایش کرده و دیتابیس را به روز کنید.

برای اینکه کوئری‌های LINQ ما شبیه به LINQ to SQL شوند، کلاس NewsContext را به صورت ذیل تشکیل می‌دهیم. این کلاس از کلاس پایه NHibernateContext مشتق شده و سپس به ازای تمام موجودیت‌های برنامه، یک متد از نوع IOrderedQueryable را تشکیل خواهیم داد.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample2.Domain;

namespace NHSample2
{
class NewsContext : NHibernateContext
{
public NewsContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<News> News
{
get { return Session.Linq<News>(); }
}

public IOrderedQueryable<Category> Categories
{
get { return Session.Linq<Category>(); }
}

public IOrderedQueryable<User> Users
{
get { return Session.Linq<User>(); }
}
}
}
اکنون جهت یافتن کاربر و به روز رسانی اطلاعات او در دیتابیس خواهیم داشت:

using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using System.Linq;
using NHSample2.Domain;

namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
using (NewsContext db = new NewsContext(session))
{
var query = from x in db.Users
where x.UserName == "VahidNasiri"
select x;

//اگر چیزی یافت شد
if (query.Any())
{
User vahid = query.First();
//نمایش اطلاعات کاربر
Console.WriteLine("Id: {0}, UserName: {0}", vahid.Id, vahid.UserName);
//به روز رسانی نام کاربر
vahid.UserName = "Vahid";
session.Update(vahid);

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

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مباحث تکمیلی AutoMapping

اگر به اسکریپت دیتابیس تولید شده دقت کرده باشید، عملیات AutoMapping یک سری پیش فرض‌هایی را اعمال کرده است. برای مثال فیلد Id را از نوع identity و به صورت کلید تعریف کرده، یا رشته‌ها را به صورت nvarchar با طول 255 ایجاد نموده است. امکان سفارشی سازی این موارد نیز وجود دارد.

مثال:

using FluentNHibernate.Conventions.Helpers;

public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add(
PrimaryKey.Name.Is(x => "ID"),
DefaultLazy.Always(),
ForeignKey.EndsWith("ID"),
Table.Is(t => "tbl" + t.EntityType.Name)
)
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);

return cfg;
}

تابع GenerateMapping معرفی شده را اینجا با قسمت Conventions.Add تکمیل کرده‌ایم. به این صورت دقیقا مشخص شده است که فیلدهایی با نام ID باید primary key در نظر گرفته شوند، همواره lazy loading صورت گیرد و نام کلید خارجی به ID ختم شود. همچنین نام جداول با tbl شروع گردد.
روش دیگری نیز برای معرفی این قرار دادها و پیش فرض‌ها وجود دارد. فرض کنید می‌خواهیم طول رشته پیش فرض را از 255 به 500 تغییر دهیم. برای اینکار باید اینترفیس IPropertyConvention را پیاده سازی کرد:

using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;

namespace NHSample2.Conventions
{
class MyStringLengthConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Length(500);
}
}
}
سپس نحوه‌ی معرفی آن به صورت زیر خواهد بود:

public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());

new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add<MyStringLengthConvention>()
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);

return cfg;
}

نکته:
اگر برای یافتن اطلاعات بیشتر در این مورد در وب جستجو کنید، اکثر مثال‌هایی را که مشاهده خواهید کرد بر اساس نگارش بتای fluent NHibernate هستند و هیچکدام با نگارش نهایی این فریم ورک کار نمی‌کنند. در نگارش رسمی نهایی ارائه شده، تغییرات بسیاری صورت گرفته که آن‌ها را در این آدرس می‌توان مشاهده کرد.

دریافت سورس برنامه قسمت ششم


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

اشتراک‌ها
تقدم و تاخر مسیریابی‌ها در ASP.NET MVC و Web API

Routing order can be broken down into the following steps :

Check the Order property (if available).
Order all routes without an explicit Order attribute as follows:
  Literal segments.
  Route parameters with constraints.
  Route parameters without constraints.
  Wildcard parameter segments with constraints.
  Wildcard parameter segments without constraints.
As a tie-breaker, order the routes via a case-insensitive string comparison.

تقدم و تاخر مسیریابی‌ها در ASP.NET MVC و Web API
مطالب
بررسی Bad code smell ها: زنجیره پیام یا Message chain
این کد بد بو در دسته «جلوگیری کنندگان از تغییر» قرار می‌گیرد. معمولا زمانیکه فراخوانی‌هایی مانند تکه کد زیر را در بخشی از کد مشاهده کردید، با چنین کد بد بویی مواجه هستید.  
MethodA().MethodB().MethodC();
فراخوانی هر یک از این متدها در خطی مجزا از کد نیز تشکیل دهنده‌ی این الگوی بد است. استفاده کننده‌ی از این زنجیره پیام، برای استفاده‌ی درست از آن، باید در جریان هریک از حلقه‌های زنجیره و ترتیب فراخوانی آنها باشد. در صورتیکه هر یک از حلقه‌های زنجیره تغییری داشتند، استفاده کننده  نیز باید تغییر کنند. 
به طور مثال:
public class RepresentativeEmployeeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class RepresentativeQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
} 
public class CustomerQuery 
{ 
    public dynamic GetById(int id) 
    { 
        throw new NotImplementedException(); 
    } 
}
public static class Programm 
{ 
    static void Main(string[] args) 
    { 
        var customer = new CustomerQuery().GetById(1); 
        var representativeId = customer.RepresentativeId; 
        var representative = new RepresentativeQuery().GetById(representativeId); 
        var managerId = representative.ManagerId; 
        var manager  = new RepresentativeEmployeeQuery().GetById(managerId); 
        var managerName = manager.FullName; 
    } 
}
  • کلاس CustomerQuery پرس و جوهای مربوط به مشتری را مدیریت می‌کند.
  • کلاس RepresentativeQuery پرس و جوهای مربوط به نمایندگی را مدیریت می‌کند .
  • کلاس RepresentativeEmployeeQuery پرس و جوهای مربوط به کارمندان نمایندگی را مدیریت می‌کند.
در مثال ذکر شده می‌خواهیم نام مدیریت نمایندگی ای را که یک مشتری از آن خرید کرده است، بدانیم. صرفا جهت نمایش مثال، این کار را در متد main انجام داده‌ایم.  
مشاهده می‌کنید که زنجیره‌ای از پیام‌ها از CustomerQuery تا پایین‌ترین قسمت یعنی RepresentativeEmployeeQuery ارسال شده است. هر یک از مراحل زنجیره بخشی از منطق دریافت نام را مدیریت می‌کنند. مانند دریافت مشتری، دریافت نمایندگی آن و دریافت مدیریت نمایندگی. 

مشکلات این کد بد بو 

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


روش‌ها اصلاح این کد بد بو 

روش‌های اصلاح این کد بد بو حول مخفی کردن زنجیره فراخوانی‌ها هستند. اما در مواردی مانند مثال ذکر شده در این مطلب امکان ادغام یک یا چند متد نیز وجود دارد. به شرطی که این کار ناقض اصل Single responsibility یا دیگر اصول شیء گرایی نباشد. دو نمونه از روش‌های اصلاح این کد بد بو به صورت زیر هستند: 
  • مخفی کردن زنجیره فراخوانی و مستقل سازی استفاده کننده از زنجیره فرخوانی (Hide delegate)  
  • ادغام بخشی از متدها در زنجیره فراخوانی و از بین بردن زنجیره فراخوانی 


چه کدهایی Message Chain نیستند؟ 

معمولا کدهایی که از الگوی domain specific language پیروی می‌کنند، ممکن است شباهت بسیاری به مثال مطرح شده داشته باشند. اما از نظر مفهومی با الگوی مطرح شده متفاوت هستند؛ به این صورت که در کد بد بوی زنجیره پیام‌ها، استفاده کننده از متد نیاز به دانستن تمامی حلقه‌ها و ترتیب آنها را دارد، ولی در domain specific language‌ها معمولا نحوه استفاده از متدها به صورت شبه زبانی گویا هدایت شده و معمولا ترتیب به صورت مخفی مدیریت می‌شود.
مطالب
بررسی Bad code smell ها: تعداد زیاد پارامترهای ورودی
برای مشاهده طبقه بندی Bad code smell‌ها می‌توانید به  اینجا  مراجعه کنید. 
زمانیکه متدی بیش از سه یا چهار پارامتر ورودی داشته باشد، به چنین مشکلی برخورده‌ایم. این بوی بد کد از دسته «کدهای متورم» است. کدهای متورم معمولا به مرور زمان ایجاد و کار را برای نگهداری کد سخت می‌کنند. 
 توجه به این نکته که کدهای متورم به مرور زمان به این وضعیت دچار می‌شوند امری ضروری در درک بهتر و جلوگیری از این حالت بد کد است.  
این نوع کد بد بو معمولا در شرایط زیر ایجاد می‌شود:
  • زمانیکه کارهای زیادی به مرور زمان به یک متد محول و پارامترهایی برای کنترل رفتار متد در شرایط مختلف ایجاد می‌شود. 
  • این الگوی بد می‌تواند محصول جانبی مستقل کردن کلاس‌ها و متدها باشد. فرض کنید در بدنه متدی، شیء‌ای نیاز است و مکانیزم ساخته شدن این شیء نیز در بدنه همان متد پیاده سازی شده‌است. برای جداسازی منطق ایجاد شیء مربوطه، ممکن است تصمیم به انتقال آن به کلاس استفاده کننده از متد باشد. به این صورت که در آن کلاس، شیء مورد نیاز این متد ایجاد شود و به صورت پارامتر به این متد ارسال شود. زمانیکه تعداد این پارامترها زیاد شدند باید دقت بیشتری به کد داشت.
طراحی کلاس‌ها و متدها باید به گونه‌ای باشد که تا حد امکان متدها از داده‌های موجود در شیء خود استفاده کنند و در صورتیکه به هیچ طریقی داده مربوطه از طریق شیء آنها قابل دسترسی نبود، آن داده به صورت پارامتر به متد ارسال شود.

روش‌های اصلاح این نوع کد بد بو 

1) اگر در پارامترهای متد نوعی (type) وجود دارد که خود در زمان صدا زدن متد توسط روالی ایجاد می‌شود، می‌توان در شرایط مناسب روال ایجاد پارامتر را در بدنه خود متد صدا زد (Replace parameter with method call). 
به طور مثال به تکه کد زیر توجه کنید. 
...   
var basePrice = _quantity * _itemPrice;    
var discountLevel = getDiscountLevel();   
var finalPrice = discountedPrice (..., ..., ..., basePrice, discountLevel);
...
همان طور که ملاحظه می‌کنید شیء مربوط به discountLevel توسط متد getDiscountLevel ساخته شده و به متد ارسال شده است. پیاده سازی بهتر این موضوع می‌تواند به صورت زیر باشد:
...    
var basePrice = _quantity * _itemPrice;
var finalPrice = discountedPrice (..., ..., ..., basePrice);
...
همان طور که مشخص است متد ساختن discountLevel در بدنه discountedPrice صدا زده شده است. این بهبود معمولا در شرایطی انجام می‌شود که متد discountedPrice در همان کلاسی تعریف شده باشد که متد جاری وجود دارد. زیرا در غیر این صورت این بهبود عملا منجر به انتقال مسئولیت غیر مربوط، به کلاس دیگری می‌شود.  

2) اگر تعدادی پارامتر از یک شیء استخراج شده و به متد ارسال می‌شود، می‌توان خود آن شیء را به صورت کامل به متد ارسال کرد (Preserve whole object). 
... 
var dueDate = invoice.DueDate; 
var amount = invoice.Amount; 
var discount = invoice.Discount; 
var code = invoice.Code; 
var id = invoice.Id; 
IssuePayment(paymentType, id,dueDate,amount,discount,code); 
...
در مثال بالا ملاحظه می‌کنید که مقادیر اطلاعاتی مورد نیاز برای صادر کردن یک پرداخت مانند نوع پرداخت و اطلاعات مبلغ و تاریخ پرداخت آن از invoice و مبداهای متفاوتی بدست آمده‌است. بخشی از اطلاعات را که از invoice بدست می‌آید، می‌توان بجای دستیابی جداگانه و ارسال جداگانه آن، توسط کل شیء invoice انجام داد. به طوریکه کل شیء invoice به متد صدور پرداخت ارسال شود. مانند تکه کد زیر:    
...
IssuePayment(paymentType, invoice); 
...
یکی از مزایای استفاده از چنین روشی کاسته شدن کدهای تکراری مورد نیاز برای فراخوانی متد است. همچنین خوانایی و قابلیت گسترش این مکانیزم نیز بالا خواهد رفت. 

3) اگر تعداد پارامترهای زیادی وجود دارند، می‌توان یک کلاس پارامتر ساخت و پارامترها را در آن کلاس تعریف، مقداردهی و به متد ارسال کرد (Parameter object). 

جمع بندی

موارد مطرح شده برای رفع این بوی بد، در واقع روش‌های مختلف Refactoring هستند که برای این شرایط پیشنهاد شده‌اند. در مباحث مربوط به Refactoring این راه حل‌ها به صورت مفصل‌تری بررسی شده‌اند.  
زمانیکه این بوی بد برطرف شد، معمولا شاهد کدی خواناتر و قابل توسعه‌تر خواهیم بود. همچنین احتمال اینکه کدهای تکراری حذف شوند و جلوی ایجاد کدهای تکراری جدید نیز گرفته شود، بسیار زیاد است.
بازخوردهای دوره
آشنایی با مدل برنامه نویسی TAP
منظورم از متدهای Async مربوط به EF 6 هست آیا نمیشه از این متدها با دات نت 4 استفاده کرد.