Quantcast
Channel: ‫فید گروه entity framework .NET Tips
Viewing all 112 articles
Browse latest View live

‫یک دست سازی ی و ک در برنامه‌های Entity framework 6

$
0
0
تا قبل از EF 6 برای طراحی یک سیستم عمومی تغییر مقادیر ثبت شده در بانک اطلاعاتی، می‌شد با استفاده از امکانات توکار Trackingآن، مقادیر تغییر کرده را یافت و برای مثال ی و ک آن‌ها را پیش از درج در بانک اطلاعاتی، یک دست کرد. در EF 6 با معرفی یک سری interceptor می‌توان به مراحل پیش و پس از اجرای کوئری‌ها دسترسی پیدا کرد. عمده‌ترین کاربرد آن، لاگ کردن SQLهای تولیدیو نوشتن برنامه‌هایی شبیه به EF Profiler است. اما ... استفاده‌ی دیگری را نیز می‌توان از IDbCommandInterceptor جدید آن تدارک دید: دستکاری SQL تولیدی توسط آن پیش از اعمال به بانک اطلاعاتی.

طراحی یک Interceptor برای یک دست سازی ی و ک

در اینجا کدهای کلاس YeKeInterceptor را ملاحظه می‌کنید. در متدهایی که به کلمه‌ی Executing ختم می‌شوند، می‌توان به دستورات SQL تولید شده توسط EF، پیش از اعمال بر روی بانک اطلاعاتی دسترسی داشت:
    public class YeKeInterceptor : IDbCommandInterceptor
    {
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }

        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }

        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }
    }
DbCommand، حاوی تمام اطلاعاتی است که به آن نیاز داریم؛ شامل CommandText یا همان SQL تولید شده و همچنین command.Parameters برای دسترسی به مقادیر پارامترهای کوئری. نکته‌ی مهم تمام این موارد، قابل ویرایش بودن آن‌ها است.
    public static class YeKe
    {
        public const char ArabicYeChar = (char)1610;
        public const char PersianYeChar = (char)1740;

        public const char ArabicKeChar = (char)1603;
        public const char PersianKeChar = (char)1705;

        public static string ApplyCorrectYeKe(this object data)
        {
            return data == null ? null : ApplyCorrectYeKe(data.ToString());
        }

        public static string ApplyCorrectYeKe(this string data)
        {
            return string.IsNullOrWhiteSpace(data) ?
                        string.Empty :
                        data.Replace(ArabicYeChar, PersianYeChar).Replace(ArabicKeChar, PersianKeChar).Trim();
        }

        public static void ApplyCorrectYeKe(this DbCommand command)
        {
            command.CommandText = command.CommandText.ApplyCorrectYeKe();

            foreach (DbParameter parameter in command.Parameters)
            {
                switch (parameter.DbType)
                {
                    case DbType.AnsiString:
                    case DbType.AnsiStringFixedLength:
                    case DbType.String:
                    case DbType.StringFixedLength:
                    case DbType.Xml:
                        parameter.Value = parameter.Value.ApplyCorrectYeKe();
                        break;
                }
            }
        }
    }
در اینجا پیاده سازی متد الحاقی ApplyCorrectYeKe را که در کلاس YeKeInterceptor مورد استفاده قرار گرفت، ملاحظه می‌کنید.
در آن، CommandText و همچنین parameter.Valueها در صورت رشته‌ای بودن، اصلاح می‌شوند.
سربار این روش نسبت به روش‌های پیشیناستفاده از Reflection کمتر است. همچنین اشیاء پیچیده و تو در تو را نیز بهتر پشتیبانی می‌کند؛ چون در مرحله Executing، کار پردازش این اشیاء پایان یافته و SQL خام نهایی آن در اختیار ما است.


نحوه‌ی استفاده از YeKeInterceptor

در آغاز برنامه، سطر زیر را فراخوانی کنید:
 DbInterception.Add(new YeKeInterceptor());

یک مثال کامل برای دریافت
Sample32.cs

‫صفحه بندی پویا در Entity Framework

$
0
0
در اکثر برنامه‌ها ما نیازمند این موضوع هستیم که بتوانیم اطلاعاتی را به کاربر نشان دهیم. در بعضی از موارد این اطلاعات بسیار زیاد هستند و نیاز است در این حالت از صفحه بندی اطلاعات یا Data Paging استفاده کنیم. در ASP.NET برای ارائه اطلاعات به کاربر معمولا از کنترلهای Gridview ، ListView و امثالهم استفاده می‌شود. مشکل اساسی این کنترل‌ها این است که آنها اطلاعات را به صورت کامل از سرور دریافت کرده، سپس اقدام به نمایش صفحه بندی شده آن می‌نمایند که این موضوع باعث استفاده بی مورد از حافظه سرور شده و هزینه زیادی برای برنامه ما خواهد داشت.
صفحه بندی در سطح پایگاه داده بهترین روش برای استفاده بهینه از منابع است. برای رسیدن به این مقصود ما نیاز به یک کوئری خواهیم داشت که فقط همان صفحه مورد نیاز را به کنترلر تحویل دهد.
با استفاده از متد توسعه یافته زیر می‌توان به این مقصود دست یافت:
/// <summary>
/// صفحه بندی کوئری
/// </summary>
/// <param name="query">کوئری مورد نظر شما</param>
/// <param name="pageNum">شماره صفحه</param>
/// <param name="pageSize">سایز صفحه</param>
/// <param name="orderByProperty">ترتیب خواص</param>
/// <param name="isAscendingOrder">اگر برابر با <c>true</c> باشد صعودی است</param>
/// <param name="rowsCount">تعداد کل ردیف ها</param>
/// <returns></returns>
private static IQueryable<T> PagedResult<T, TResult>(IQueryable<T> query, int pageNum, int pageSize,
                Expression<Func<T, TResult>> orderByProperty, bool isAscendingOrder, out int rowsCount)
{
    if (pageSize <= 0) pageSize = 20;
    //مجموع ردیف‌های به دست آمده
    rowsCount = query.Count();

// اگر شماره صفحه کوچکتر از 0 بود صفحه اول نشان داده شود
    if (rowsCount <= pageSize || pageNum <= 0) pageNum = 1;
// محاسبه ردیف هایی که نسبت به سایز صفحه باید از آنها گذشت
    int excludedRows = (pageNum - 1) * pageSize;

    query = isAscendingOrder ? query.OrderBy(orderByProperty) : query.OrderByDescending(orderByProperty);
    
// ردشدن از ردیف‌های اضافی و  دریافت ردیف‌های مورد نظر برای صفحه مربوطه
    return query.Skip(excludedRows).Take(pageSize);
}

نحوه استفاده : 
فرض کنید که کوئری مورد نظر قرار است تا یکسری از مطالب را از جدول Articles نمایش دهد. برای دریافت 20 ردیف اول جهت استفاده در صفحه اول، از کد زیر استفاده می‌کنیم :
var articles = (from article in Articles
                where article.Author == "Abc"
                select article);

int totalArticles;    

var firstPageData =  PagedResult(articles, 1, 20, article => article.PublishedDate, false, out totalArticles);
یا به صورت ساده‌تر و قابل اجرا به صورت کلی‌تر :
var context = new AtricleEntityModel(); 
var query = context.ArticlesPagedResult(articles, <pageNumber>, 20, article => article.PublishedDate, false, out totalArticles);

‫مقاومت اتصال و اتصالات بهبودپذیر در Entity framework 6

$
0
0
Timeouts، Deadlocks و قطعی‌های احتمالی و موقت اتصال به بانک اطلاعاتی در شبکه، جزئی از ساختار دنیای واقعی هستند. در EF 6 برای پیاده سازی سعی مجدد در اتصال و انجام مجدد عملیات، ویژگی خاصی تحت عنوان connection resiliencyاضافه شده‌است که در ادامه مثالی از آن‌را بررسی خواهیم کرد.

پیاده سازی‌های پیش فرض موجود

برای پیاده سازی منطق سعی مجدد در اتصال، باید اینترفیس IDbExecutionStrategy پیاده سازی شود. در EF 6 حداقل 4 نوع پیاده سازی پیش فرض از آن به صورت توکار ارائه شده‌است:
الف) DefaultExecutionStrategy : حالت پیش فرض است و در صورت بروز مشکل، سعی مجددی را در اتصال، به عمل نخواهد آورد.
ب) DefaultSqlExecutionStrategy : برای کارهای درونی EF از آن استفاده می‌شود. سعی مجددی در اتصال قطع شده نخواهد کرد؛ اما جزئیات خطاهای بهتری را در اختیار مصرف کننده قرار می‌دهد.
ج) DbExecutionStrategy : هدف از آن تهیه یک کلاس پایه است برای نوشتن استراتژی‌های سعی مجدد سفارشی.
د) SqlAzureExecutionStrategy : یک نمونه DbExecutionStrategy سفارشی تهیه شده برای ویندوز اژور است. برای فعال سازی و تعریف آن نیز باید به نحو ذیل عمل کرد:
public class MyConfiguration : DbConfiguration 
{     public MyConfiguration()     {         SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());     } 
}


تهیه یک DbExecutionStrategy سفارشی برای SQL Server

همانطور که عنوان شد، هدف از کلاس DbExecutionStrategy، تهیه یک کلاس پایه، جهت نوشتن منطق سعی مجدد در اتصال به بانک اطلاعاتی است و این مورد از دیتابیسی به دیتابیس دیگر می‌تواند متفاوت باشد؛ زیرا خطاهایی را که ارائه می‌دهند، یکسان و یک دست نیستند. در ادامه یک پیاده سازی سفارشی را از DbExecutionStrategy، جهت SQL Server مرور خواهیم کرد:
    public class SqlServerExecutionStrategy : DbExecutionStrategy
    {
        public SqlServerExecutionStrategy()
        { }

        public SqlServerExecutionStrategy(int maxRetryCount, TimeSpan maxDelay)
            : base(maxRetryCount, maxDelay)
        { }

        protected override bool ShouldRetryOn(Exception ex)
        {
            var sqlException = ex as SqlException;
            if (sqlException == null)
                return false; // don't retry

            foreach (var error in sqlException.Errors.Cast<SqlError>())
            {
                switch (error.Number)
                {
                    case 1205: // Deadlock
                    case -1: // Timeout
                    case -2: // Timeout
                        return true; // retry
                }
            }

            return false;
        }
    }
در اینجا کار با بازنویسی متد ShouldRetryOn شروع می‌شود. این متد اگر پس از بررسی استثنای دریافتی، مقدار true را برگرداند، به معنای نیاز به سعی مجدد در اتصال است و برعکس. سازنده پیش فرض این کلاس طوری تنظیم شده‌است که 5 بار سعی مجدد کند؛ با فواصل زمانی 7 ثانیه. اگر می‌خواهید این زمان را صریحا تعیین کنید باید متد GetNextDelay کلاس پایه را نیز بازنویسی کرد:
   protected override TimeSpan? GetNextDelay(Exception lastException)
  {
        return base.GetNextDelay(lastException);
  }
در ادامه برای استفاده از آن خواهیم داشت:
    public class MyDbConfiguration : DbConfiguration
    {
        public MyDbConfiguration()
        {
            SetExecutionStrategy("System.Data.SqlClient", () => new SqlServerExecutionStrategy());
        }
    }
این کلاس به صورت خودکار توسط EF از اسمبلی جاری استخراج شده و استفاده خواهد شد. بنابراین نیازی نیست جایی معرفی شود. فقط باید در کدها حضور داشته باشد. همچنین ذکر System.Data.SqlClient نیز ضروری است؛ از این جهت که خطاهای بازگشت داده شده مانند 1205 و امثال آن، در بانک‌های اطلاعاتی مختلف، می‌توانند کاملا متفاوت باشند.

‫Repository ها روی UnitOfWork ایده خوبی نیستند

$
0
0
در دنیای دات نت گرایشیبرای تجزیه (abstract) کردن EF پشت الگوی Repositoryوجود دارد. این تمایل اساسا بد است و در ادامه سعی می‌کنم چرای آن را توضیح دهم.


پایه و اساس

عموما این باور وجود دارد که با استفاده از الگوی Repository می‌توانید (در مجموع) دسترسی به داده‌ها را از لایه دامنه (Domain) تفکیک کنید و "داده‌ها را بصورت سازگار و استوار عرضه کنید".

اگر به هر کدام از پیاده سازی‌های الگوی Repository در کنار (UnitOfWork (EFدقت کنید خواهید دید که تفکیک (decoupling) قابل ملاحظه ای وجود ندارد.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        public Student GetStudentByID(int id)
        {
            return context.Students.Find(id);
        }

        //<snip>
        public void Save()
        {
            context.SaveChanges();
        }
    }
}

این کلاس بدون SchoolContext نمی‌تواند وجود داشته باشد، پس دقیقا چه چیزی را در اینجا decouple کردیم؟ هیچ چیز را!

در این قطعه کد - از MSDN - چیزی که داریم یک پیاده سازی مجدد از LINQ است که مشکل کلاسیک Repository API‌های بی انتها را بدست می‌دهد. منظور از Repository API‌های بی انتها، متدهای جالبی مانند GetStudentById, GetStudentByBirthday, GetStudentByOrderNumber و غیره است.

اما این مشکل اساسی نیست. مشکل اصلی روتین ()Save است. این متد یک دانش آموز (Student) را ذخیره می‌کند .. اینطور بنظر می‌رسد. دیگر چه چیزی را ذخیره می‌کند؟ آیا می‌توانید حدس بزنید؟ من که نمی‌توانم .. بیشتر در ادامه.


UnitOfWork تراکنشی است

یک UnitOfWork همانطور که از نامش بر می‌آید برای انجام کاریوجود دارد. این کار می‌تواند به سادگی واکشی اطلاعات و نمایش آنها، و یا به پیچیدگی پردازش یک سفارش جدید باشد. هنگامی که شما از EntityFramework استفاده می‌کنید و یک DbContext را وهله سازی می‌کنید، در واقع یک UnitOfWork می‌سازید.

در EF می‌توانید با فراخوانی ()SubmitChanges تمام تغییرات را فلاش کرده و بازنشانی کنید (flush and reset). این کار بیت‌های مقایسه change tracker را تغییر می‌دهد. افزودن رکوردهای جدید، بروز رسانی و حذف آنها. هر چیزی که تعیین کرده باشید. و تمام این دستورات در یک تراکنش یا Transaction انجام می‌شوند.


یک Repository مطلقا یک UnitOfWork نیست
هر متد در یک Repository قرار است فرمانی اتمی (Atomic) باشد - چه واکشی اطلاعات و چه ذخیره آنها. مثلا می‌توانید یک Repository داشته باشید با نام SalesRepository که اطلاعات کاتالوگ شما را واکشی می‌کند، و یا یک سفارش جدید را ثبت می‌کند. منظور از فرمان‌های اتمیک این است، که هر متد تنها یک دستور را باید اجرا کند. تراکنشی وجود ندارد و امکاناتی مانند ردیابی تغییرات و غیره هم جایی ندارند.

یکی دیگر از مشکلات استفاده از Repository‌ها این است که بزودی و به آسانی از کنترل خارج می‌شوند و نیاز به ارجاع دیگر مخازن پیدا می‌کنند. به دلیل اینکه مثلا نمی‌دانستید که SalesRepository نیاز به ارجاع ReportRepository داشته است (یا چیزی مانند این).

این مشکل به سرعت مشکل ساز می‌شود، و نیز به همین دلیل است که به UnitOfWork تمایل پیدا می‌کنیم.


بدترین کاری که می‌توانید انجام دهید: <Repository<T

این الگو دیوانه وار است. این کار عملا انتزاعی از یک انتزاع دیگر است (abstraction of an abstraction). به قطعه کد زیر دقت کنید، که به دلیلی نامشخص بسیار هم محبوب است.

public class CustomerRepository : Repository < Customer > {
  public CustomerRepository(DbContext context){
    //a property on the base class
    this.DB = context;
  }

  //base class has Add/Save/Remove/Get/Fetch
}

در نگاه اول شاید بگویید مشکل این کلاس چیست؟ همه چیز را کپسوله می‌کند و کلاس پایه Repository هم به کانتکست دسترسی دارد. پس مشکل کجاست؟

مشکلات عدیده اند .. بگذارید نگاهی بیاندازیم.

آیا می‌دانید این DbContext از کجا آمده است؟
خیر، نمی‌دانید. این آبجکت به کلاس تزریق (Inject) می‌شود، و نمی‌دانید که چه متدی آن را باز کرده و به چه دلیلی. ایده اصلی پشت الگوی Repository استفاده مجدد از کد است. بدین منظور که مثلا برای عملیات CRUD از کلاسی پایه استفاده کنید تا برای هر موجودیت و فرمی نیاز به کدنویسی مجدد نباشد. برگ برنده این الگو نیز دقیقا همین است. مثلا اگر بخواهید از کدی در چند فرم مختلف استفاده کنید از این الگو استفاده میشد.

الگوی UnitOfWork همه چیز در نامش مشخص است. اگر قرار باشد آنرا بدین شکل تزریق کنید، نمی‌توانید بدانید که از کجا آمده است.


شناسه مشتری جدید را نیاز داشتم
کد بالا در CustomerRepository را در نظر بگیرید - که یک مشتری جدید را به دیتابیس اضافه می‌کند. اما CustomerID جدید چه می‌شود؟ مثلا به این شناسه نیاز دارید تا یک log بسازید. چه می‌کنید؟ گزینه‌های شما اینها هستند:

  • متد ()SubmitChanges را صدا بزنید تا تغییرات ثبت شوند و بتوانید به CustomerID جدید دسترسی پیدا کنید
  • CustomerRepository خود را باز کنید و متد پایه Add را بازنویسی (override) کنید. بدین منظور که پیش از بازگشت دادن، متد ()SubmitChanges را فراخوانی کند. این راه حلی است که MSDN به آن تشویق می‌کند، و بمبی ساعتی است که در انتظار انفجار است
  • تصمیم بگیرید که تمام متدهای Add/Remove/Save در مخازن شما باید ()SubmitChanges را فراخوانی کنند

مشکل را می‌بینید؟ مشکل در خود پیاده سازی است. در نظر بگیرید که چرا New Customer ID را نیاز دارید؟ احتمالا برای استفاده از آن در ثبت یک سفارش جدید، و یا ثبت یک ActivityLog.

اگر بخواهیم از StudentRepository بالا برای ایجاد دانش آموزان جدید پس از خرید آنها از فروشگاه کتاب مان استفاده کنیم چه؟ اگر DbContext خود را به مخزن تزریق کنید و دانش آموز جدید را ذخیره کنید .. اوه .. تمام تراکنش شما فلاش شده و از بین رفته!

حالا گزینه‌های شما اینها هستند: 1) از StudentRepository استفاده نکنید (از OrderRepository یا چیز دیگری استفاده کنید). و یا 2) فراخوانی ()SubmitChanges را حذف کنید و به باگ‌های متعددی اجازه ورود به کد تان را بدهید.

اگر تصمیم بگیرید که از StudentRepository استفاده نکنید، حالا کدهای تکراری (duplicate) خواهید داشت.

شاید بگویید که برای دستیابی به شناسه رکورد جدید نیازی به ()SubmitChanges نیست، چرا که خود EF این عملیات را در قالب یک تراکنش انجام می‌دهد!

دقیقا درست است، و نکته من نیز همین است. در ادامه به این قسمت باز خواهیم گشت.

متدهای Repositories قرار است اتمیک باشند

به هر حال تئوری اش که چنین است. چیزی که در Repository‌ها داریم حتی اصلا Repository هم نیست. بلکه یک abstraction برای عملیات CRUD است که هیچ کاری مربوط به منطق تجاری اپلیکیشن را هم انجام نمی‌دهد. مخازن قرار است روی دستورات مشخصی تمرکز کنند (مثلا ثبت یک رکورد یا واکشی لیستی از اطلاعات)، اما این مثال‌ها چنین نیستند.

همانطور که گفته شده استفاده از چنین رویکردهایی به سرعت مشکل ساز می‌شوند و با رشد اپلیکیشن شما نیز مشکلات عدیده ای برایتان بوجود می‌آروند.

خوب، راه حل چیست؟

برای جلوگیری از این abstraction‌های غیر منطقی دو راه وجود دارد. اولین راه استفاده از Command/Query Separation است که ممکن است در ابتدا کمی عجیب و بنظر برسند اما لازم نیست کاملا CQRS را دنبال کنید. تنها از سادگی انجام کاری که مورد نیاز است لذت ببرید، و نه بیشتر.

آبجکت‌های Command/Query

Jimmy Bogard مطلب خوبی در اینباره نوشته است و با تغییراتی جزئی برای بکارگیری Properties کدی مانند لیست زیر خواهیم داشت. مثلا برای مطالعه بیشتر درباره آبجکت‌های Command/Query به این لینکسری بزنید.

public class TransactOrderCommand {
  public Customer NewCustomer {get;set;}
  public Customer ExistingCustomer {get;set;}
  public List<Product> Cart {get;set;}
  //all the parameters we need, as properties...
  //...

  //our UnitOfWork
  StoreContext _context;
  public TransactOrderCommand(StoreContext context){
    //allow it to be injected - though that's only for testing
    _context = context;
  }

  public Order Execute(){
    //allow for mocking and passing in... otherwise new it up
    _context = _context ?? new StoreContext();

    //add products to a new order, assign the customer, etc
    //then...
    _context.SubmitChanges();

    return newOrder;
  }
}
همین کار را با یک آبجکت Query نیز می‌توانید انجام دهید. می‌توانید پست Jimmy را بیشتر مطالعه کنید، اما ایده اصلی این است که آبجکت‌های Query و Command برای دلیل مشخصی وجود دارند. می‌توانید آبجکت‌ها را در صورت نیاز تغییر دهید و یا mock کنید.


DataContext خود را در آغوش بگیرید

ایده ای که در ادامه خواهید دید را شخصا بسیار می‌پسندم (که توسط Ayendeمعرفی شد). چیزهایی که به آنها نیاز دارید را در قالب یک فیلتر wrap کنید و یا از یک کلاس کنترلر پایه استفاده کنید (با این فرض که از اپلیکیشن‌های وب استفاده می‌کنید).

using System;
using System.Web.Mvc;

namespace Web.Controllers
{
  public class DataController : Controller
  {
    protected StoreContext _context;

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      //make sure your DB context is globally accessible
      MyApp.StoreDB = new StoreDB();
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
      MyApp.StoreDB.SubmitChanges();
    }
  }
}

این کار به شما اجازه می‌دهد که از DataContext خود در خلال یک درخواست واحد (request) استفاده کنید. تنها کاری که باید بکنید این است که از این کلاس پایه ارث بری کنید. این بدین معنا است که هر درخواست به اپلیکیشن شما یک UnitOfWork خواهد بود. که بسیار هم منطقی و قابل قبول است. در برخی موارد هم شاید این فرض درست یا کارآمد نباشد، که در این هنگام می‌توانید از آبجکت‌های Command/Query استفاده کنید.


ایده‌های بعدی: چه چیزی بدست آوردیم؟

چیزهای متعددی بدست آوردیم.

  • تراکنش‌های روشن و صریح: دقیقا می‌دانیم که DbContext ما از کجا آمده و در هر مرحله روی چه UnitOfWork ای کار می‌کنیم. این امر هم الان، و هم در آینده بسیار مفید خواهد بود
  • انتزاع کمتر == شفافیت بیشتر: ما Repository‌ها را از دست دادیم، که دلیلی برای وجود داشتن نداشتند. به جز اینکه یک abstraction از abstraction دیگر باشند. رویکرد آبجکت‌های Command/Query تمیز‌تر است و دلیل وجود هرکدام و مسئولیت آنها نیز روشن‌تر است
  • شانس کمتر برای باگ ها: رویکردهای مبتنی بر Repository باعث می‌شوند که با تراکنش‌های ناموفق یا پاره ای (partially-executed) مواجه شویم که نهایتا به یکپارچگی و صحت داده‌ها صدمه می‌زند. لازم به ذکر نیست که خطایابی و رفع چنین مشکلاتی شدیدا زمان بر و دردسر ساز است

    برای مطالعه بیشتر 

    ایجاد Repositories بر روی UnitOfWork
    به الگوی Repository در لایه DAL خود نه بگویید!
    پیاده سازی generic repository یک ضد الگو است 
    نگاهی به generic repositories
    بدون معکوس سازی وابستگی‌ها، طراحی چند لایه شما ایراد دارد 

      ‫تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد

      $
      0
      0
      در زمان ساخت مدل از بانک اطلاعاتی در روش Database First به صورت پیش فرض تنظیمات مربوط به اتصال (Connection String) مدل به بانک اطلاعاتی در فایل config برنامه ذخیره می‌شود. مشکل این روش آن است که در سیستم‌های مختلف، بسته به بستری که نرم افزار قرار است بر روی آن اجرا شود، باید تنظیمات مربوط به بانک اطلاعاتی صورت گیرد.
      مثلا فرض کنید شما در زمان توسعه نرم افزار، SQL Server را به صورت Local بر روی سیستم خود نصب کرده اید و Connection String ساخته شده توسط ویزارد Entity Framework بر همین اساس ساخته و ذخیره شده‌است. حال بعد از انتشار برنامه، شخصی تصمیم دارد برنامه را بر روی سیستمی نصب کند که بانک اطلاعاتی Local نداشته و تصمیم به اتصال به یک بانک اطلاعاتی بر روی سرور دیگر یا با مشخصات (Login و Password و ...) دیگر را دارد. برای این مواقع نیاز به پیاده سازی روشی است تا کاربر نهایی بتواند تنظیمات مربوط به اتصال به بانک اطلاعاتی را تغییر دهد.
      روش‌های مختلفی مثل تغییر فایل app.config به صورت Runtime یا ... در سایت‌های مختلف ارائه شده که اکثرا روش‌های غیر اصولی و زمانبری جهت پیاده سازی هستند.
      ساده‌ترین روش جهت انجام این کار، اعمال تغییری کوچک در Constructor کلاس مدل مشتق شده از DBContext می‌باشد. فرض کنید مدلی از بانک اطلاعاتی Personnely با نام PersonallyEntities ساخته اید که حاصل آن کلاس زیر خواهد بود:
          public partial class PersonallyEntities : DbContext    {        public PersonallyEntities()            : base("name=PersonallyEntities")        {        }    }
      همانطور که مشاهده می‌کنید، در Constructor این کلاس، نام Connection String مورد استفاده جهت اتصال به بانک اطلاعاتی به صورت زیر آورده شده که به Connection String ذخیره شده در فایل Config اشاره می‌کند:
      "name=PersonallyEntities"
      اگر به Connection String ذخیره شده در فایل Config دقت کنید متوجه می‌شوید که Connection String ذخیره شده، دارای فرمتی خاص و متفاوتی نسبت به Connection String معمولی ADO.NET است. متن ذخیره شده شامل تنظیمات و Metadata مدل ساخته شده جهت ارتباط با بانک اطلاعاتی نیز می‌باشد:
       metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=Personally;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
      جهت تولید پویای Connection String، بسته به تنظیمات کاربر، نیاز است تا در آخر Connection String ی با فرمت بالا در اختیار Entity Framework قرار دهیم تا امکان اتصال به بانک فراهم شود. جهت تبدیل Connection String معمول ADO.NET به Connection String قابل فهم EF میتوان از کلاس EntityConnectionStringBuilder به صورت زیر استفاده کرد:
              public static string BuildEntityConnection(string connectionString)
              {
                  var entityConnection = new EntityConnectionStringBuilder
                  {
                      Provider = "System.Data.SqlClient",
                      ProviderConnectionString = connectionString,
                      Metadata = "res://*"
                  };
      
                  return entityConnection.ToString();
              }
      همانطور که مشاهده می‌کنید، متد بالا با دریافت یک connectionString که همان ADO.NET ConnectionString ما می‌باشد، تنظیمات و Metadata مورد نیاز Entity Framework را به آن اضافه کرده و یک EF ConnectionString برمی‌گرداند.
      برای اینکه بتوان EF ConnectionString تولید شده را در هنگام اجرای برنامه به صورت Runtime اعمال کرد، نیاز است تا تغییر کوچکی در Constructor کلاس مدل تولید شده توسط Entity Framework ایجاد کرد. کلاس PersonnelyEntities به صورت زیر تغییر پیدا می‌کند:

          public partial class PersonallyEntities : DbContext
          {
              public PersonallyEntities(string connectionString)
                  : base(connectionString)
              {
      
              }
          }
      با اضافه شدن پارامتر connectionString به سازنده کلاس PersonnelyEntities برای ساخت یک نمونه از مدل ساخته شده در کد نیاز است تا Connection String مورد نظر جهت برقراری ارتباط با بانک را به عنوان پارامتر، به متد سازنده پاس دهیم. سپس مقدار این پارامتر به کلاس والد ( DbContext ) جهت برقراری ارتباط با بانک اطلاعاتی ارجاع داده شده: 
      : base(connectionString)
      در آخر به صورت زیر میتوان توسط EF به بانک اطلاعاتی مورد نظر متصل شد :
      var entityConnectionString = BuildeEntityConnection("Data Source=localhost;Initial Catalog=Personally; Integrated Security=True");
      var PersonallyDb = new PersonallyEntities(entityConnectionString);
      با این روش میتوان ADO Connection String مربوط به اتصال بانک اطلاعاتی را به راحتی به صورت داینامیک به وسیله اطلاعات وارد شده توسط کاربر و کلاس‌های تولید Connection String نظیر SQLConnectionStringBuilder تولید کرد و بدون تغییر در کد‌های برنامه، به بانک‌های مختلفی متصل شد. همچنین با داینامیک کردن متد Provider کلاس EntityConnectionStringBuilder که در کد بالا با "System.Data.SqlClient" مقدار دهی شده، می‌توان وابستگی برنامه بانک اطلاعی خاص را از بین برد و بسته به تنظیمات مورد نظر کاربر، به موتورهای مختلف بانک اطلاعاتی متصل شد که البته لازمه این کار رعایت یکسری نکات فنی در پیاده سازی پروژه است که از حوصله این مقاله خارج است.
      موفق باشید

      ‫Soft Delete در Entity Framework 6

      $
      0
      0
      برای حذف نمودن یک رکورد از دیتابیس 2 راه وجود دارد : 1- حذف به صورت فیزیکی 2- حذف به صورت منطقی ( مورد بحث این مطلب )
      در حذف رکورد به صورت منطقی، طراحان دیتابیس، فیلدی را با نام‌های متفاوتی همچون Flag , IsDeleted , IsActive , و غیره، در جداول ایجاد می‌نمایند. خوب، این روش مزایا و معایب خاص خودش را دارد. مثلا شما در هر پرس و جویی که ایجاد می‌نمایید، بایستی این مورد را چک نموده و رکوردهایی را فراخوانی نمایید که فیلد IsDeleted آن برابر با false باشد. و همچنین در زمان حذف رکورد، برنامه نویس بایستی از متد Update به جای حذف فیزیکی استفاده نماید که تمام این موارد حاکی از مشکلات خاص این روش است. 
      در این مقاله سعی داریم که مشکلات ذکر شده در بالا را با ایجاد SoftDelete در EF 6 برطرف نماییم .*یکی از پیش نیاز‌های این پست مطالعه (سری آموزشی EF CodeFirst) در سایت جاری می‌باشد.
      برای شروع، ما نیاز به داشتن یک Attribute برای مشخص ساختن موجودیت هایی داریم که بایستی بر روی آنها SoftDelete فعال گردد. پس برای اینکار کلاسی را به شکل زیر طراحی مینماییم:
      using System.Data.Entity.Core.Metadata.Edm;
      public class SoftDeleteAttribute : Attribute
          {
              public string ColumnName { get; set; }
              public SoftDeleteAttribute(string column)
              {
                  ColumnName = column;
              }
              public static string GetSoftDeleteColumnName(EdmType type)
              {
                  MetadataProperty column = type.MetadataProperties.Where(x => x.Name.EndsWith("customannotation:SoftDeleteColumnName")).SingleOrDefault();
                  return column == null ? null : (string)column.Value;
              }
          }
      توضیحات کد بالا:در متد سازنده، نام فیلدی را که قرار است بر روی آن SoftDelete به صورت اتوماتیک ایجاد شود، دریافت می‌نماییم و متد GetSoftDeleteColumnName در واقع با استفاده از متادیتاهایی که بر روی فیلد‌ها وجود دارد، فیلدی که انتهای نام آن متادیتای "customannotation:SoftDeleteColumnName" را دارد، انتخاب نموده و برگشت می‌دهد.
      سؤال:متادیتای  "customannotation:SoftDeleteColumnName" از کجا آمد؟ برای پاسخ به این سوال کافیست ادامه‌ی مطلب را کامل مطالعه نمایید.
      حال این Attribute برای استفاده در موجودیت‌های ما آمده است. برای استفاده کافیست به روش زیر عمل نمایید .
          [SoftDelete("IsDeleted")]
          public class TblUser 
          {        
              [Key]
              public int TblUserID { get; set; }
      
              [MaxLength(30)]
              public string Name { get; set; }
      
              public bool IsDeleted { get; set; }
          }
      برای معرفی این قابلیت جدید به EF 6 کافیست در DbContext برنامه در متد OnModelCreating به نحو زیر عمل نماییم.
       protected override void OnModelCreating(DbModelBuilder modelBuilder)
              {
                  var Conv = new AttributeToTableAnnotationConvention<SoftDeleteAttribute, string>(
                      "SoftDeleteColumnName",
                      (type, attribute) => attribute.Single().ColumnName);
                  modelBuilder.Conventions.Add(Conv);
      
              }
      در واقع ما در اینجا به Ef می‌گوییم که یک Annotation جدید، با نام SoftDeleteColumnName به Entity که توسط این Attribute مزین شده است، اضافه نماید و همچنین مقدار این Annotation را نام فیلدی که در متد سازنده SoftDeleteAttribute معرفی گردیده است قرار دهد.
      برای اطمینان حاصل کردن از اینکه آیا Annotation جدید به مدل برنامه اضافه شده است یا نه کافیست بر روی فایل cs کانتکست DbContext، کلیک راست نموده و در منوی نمایش داده شده گزینه‌ی EntityFramework و سپس گزینه View Entity Data Model را انتخاب نمایید . مانند تصویر زیر:

      در پنجره باز شده به قسمت سوم یعنی <StorageModels> مراجعه نمایید و بایستی گزینه زیر را مشاهده نمایید .

      <EntityType Name="TblUser" customannotation:SoftDeleteColumnName="IsDeleted">

      تا اینجای کار ما توانستیم یک Annotation جدید را به Ef اضافه نماییم .

      در مرحله بعد بایستی به Ef دستور دهیم که در تولید Query بر روی این Entity، این مورد را نیز لحاظ کند.

      برای این کار کلاسی را ایجاد می‌نماییم که از اینترفیس IDbCommandTreeInterceptor ارث بری می‌نماید. مانند کد زیر :

      public class SoftDeleteInterceptor : IDbCommandTreeInterceptor
          {
              public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
              {
                  if (interceptionContext.OriginalResult.DataSpace == System.Data.Entity.Core.Metadata.Edm.DataSpace.SSpace)
                  {
                      var QueryCommand = interceptionContext.Result as DbQueryCommandTree;
                      if (QueryCommand != null)
                      {
                          var newQuery = QueryCommand.Query.Accept(new SoftDeleteQueryVisitor());
                          interceptionContext.Result = new DbQueryCommandTree(QueryCommand.MetadataWorkspace, QueryCommand.DataSpace, newQuery);
                      }
                  }
             }
      }

      در ابتدا تشخیص داده می‌شود که نوع خروجی Query آیا از نوع Storage Model است . ( برای توضیحات بیشتر ) سپس پرس و جوی تولید شده را با استفاده از الگوی visitor تغییر داده و Query جدید را تولید نموده و در انتها Query جدیدی را به جای Query قبلی جایگزین می‌نماییم.

      در اینجا ما نیاز به داشتن کلاس  SoftDeleteQueryVisitor  برای تغییر دادن Query و اضافه نمودن IsDeleted <>1 به Query می‌باشیم.

      یک کلاس دیگری با نام  SoftDeleteQueryVisitor  به شکل زیر  به برنامه اضافه می‌نماییم.

        public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
          {
              public override DbExpression Visit(DbScanExpression expression)
              {
                  var column = SoftDeleteAttribute.GetSoftDeleteColumnName(expression.Target.ElementType);
                  if (column!=null)
                  {
                      var Binding = DbExpressionBuilder.Bind(expression);
                      return DbExpressionBuilder.Filter(Binding, DbExpressionBuilder.NotEqual(DbExpressionBuilder.Property(DbExpressionBuilder.Variable(Binding.VariableType, Binding.VariableName), column), DbExpression.FromBoolean(true)));
                  }
                  else
                  {
                      return base.Visit(expression);
                  }
              }
          }
      در متد Visit تشخیص داده می‌شود که آیا Query ساخته شده دارای customannotation:SoftDeleteColumnName است؟ چنانچه این Annotation را دارا باشد، نام فیلدی را که بالای Entity ذکر شده است، بازگشت می‌دهد و در خط بعدی، نام این فیلد را با مقدار مخالف True به Query تولید شده اضافه می‌نماید.

      در نهایت برای اینکه EF تشخیص دهد که یک‌چنین Interceptor ایی وجود دارد، بایستی در کلاس DbContextConfig، کلاس SoftDeleteInterceptor را اضافه نماییم؛ همانند کد زیر:

       public class DbContextConfig : DbConfiguration
          {
              public DbContextConfig()
              {
                   AddInterceptor(new SoftDeleteInterceptor());
              }
          }

      تا اینجا در تمام Query‌های تولید شده بر روی Entity که با خاصیت SoftDelete مزین شده است، مقدار IsDeleted <> 1 را به صورت اتوماتیک اعمال می‌نماید. حتی به صورت هوشمند چنانچه این موجودیت در یک Join استفاده شده باشد این شرط را قبل از Join به Query تولید شده اضافه می‌نماید.

      در مقاله بعدی در مورد تغییر کد Remove به کد Update توضیح داده خواهد شد.


      برای مطالعه بیشتر

      Entity Framework: Building Applications with Entity Framework 6

      ‫نگاهی به هویت سنجی کاربران در ASP.NET MVC 5

      $
      0
      0
      در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
      در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
       در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

      دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
       Microsoft.AspNet.Identity.Core
      Microsoft.AspNet.Identity.EntityFrameWork
      عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
       اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
       دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
      اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

       فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
       بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

      دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
      namespace MyShop.Models
      {
          // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
          public class ApplicationUser : IdentityUser
          {
          }
      
          public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
          {
              public ApplicationDbContext()
                  : base("DefaultConnection")
              {
              }
      }
      کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
      namespace MyShop.Models
      {
          // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
          public class ApplicationUser : IdentityUser
          {
              [Display(Name = "نام انگلیسی")]
              public string EnglishName { get; set; }
      
              [Display(Name = "نام سیستمی")]
              public string NameInSystem { get; set; }
      
              [Display(Name = "نام فارسی")]
              public string PersianName { get; set; }
      
              [Required]
              [DataType(DataType.EmailAddress)]
              [Display(Name = "آدرس ایمیل")]
              public string Email { get; set; }
           }
      }
      کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
       در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
      namespace MyShop.Models
      {
          public class AuthorProduct
          {
              [Key]
              public int AuthorProductId { get; set; }
             /* public int UserId { get; set; }*/
      
              [Display(Name = "User")]
              public string ApplicationUserId { get; set; }
      
              public int ProductID { get; set; }
      
              public virtual Product Product { get; set; }
          
              public virtual ApplicationUser ApplicationUser { get; set; }
          }
      }
      همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
      namespace MyShop.Models
      {
          [DisplayName("محصول")]
          [DisplayPluralName("محصولات")]
          public class Product
          {
              [Key]
              public int ProductID { get; set; }
      
              [Display(Name = "گروه محصول")]
              [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
              public int ProductGroupID { get; set; }
      
              [Display(Name = "مدت زمان")]
              public string Duration { get; set; }
      
         
              [Display(Name = "نام تهیه کننده")]
              public string Producer { get; set; }
      
              [Display(Name = "عنوان محصول")]
              [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
              public string ProductTitle { get; set; }
      
              [StringLength(200)]
              [Display(Name = "کلید واژه")]
              public string MetaKeyword { get; set; }
      
              [StringLength(200)]
              [Display(Name = "توضیح")]
              public string MetaDescription { get; set; }
      
              [Display(Name = "شرح محصول")]
              [UIHint("RichText")]
              [AllowHtml]
              public string ProductDescription { get; set; }
      
              [Display(Name = "قیمت محصول")]
              [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
              [UIHint("Integer")]
              [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
              public int ProductPrice { get; set; }
              [Display(Name = "تاریخ ثبت محصول")]
      
              public DateTime? RegisterDate { get; set; }
      
          }
      }
      به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
      برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
      protected override void Seed(MyShop.Models.ApplicationDbContext context)
              {
                  context.Users.AddOrUpdate(u => u.Id,
                            new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                                  new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                                  new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                                  new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                           
                              );
      
      
                  context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
                    new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
                    new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
                    new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }
      
                );
       می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
                  if (!context.Users.Where(u => u.UserName == "Admin").Any())
                  {
                      var roleStore = new RoleStore<IdentityRole>(context);
                      var rolemanager = new RoleManager<IdentityRole>(roleStore);
      
                      var userstore = new UserStore<ApplicationUser>(context);
                      var usermanager = new UserManager<ApplicationUser>(userstore);
                      var user = new ApplicationUser {UserName = "Admin"};
                      usermanager.Create(user, "121212");
                      rolemanager.Create(new IdentityRole {Name = "admin"});
                      usermanager.AddToRole(user.Id, "admin");
                  }
         در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
      برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
       بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 

      ‫پردازش‌های Async در Entity framework 6

      $
      0
      0
      اجرای Async اعمال نسبتا طولانی، در برنامه‌های مبتنی بر داده، عموما این مزایا را به همراه دارد:

      الف) مقیاس پذیری سمت سرور

      در اعمال سمت سرور متداول، تردهای متعددی جهت پردازش درخواست‌های کلاینت‌ها تدارک دیده می‌شوند. هر زمانیکه یکی از این تردها، یک عملیات blocking را انجام می‌دهد (مانند دسترسی به شبکه یا اعمال I/O)، ترد مرتبط با آن تا پایان کار این عملیات معطل خواهد شد. با بالا رفتن تعداد کاربران یک برنامه و در نتیجه بیشتر شدن تعداد درخواست‌هایی که سرور باید پردازش کند، تعداد تردهای معطل مانده نیز به همین ترتیب بیشتر خواهند شد. مشکل اصلی اینجا است که نمونه سازی تردها بسیار هزینه بر است (با اختصاص 1MB of virtual memory space) و منابع سرور محدود. با زیاد شدن تعداد تردهای معطل اعمال I/O یا شبکه، سرور مجبور خواهد شد بجای استفاده مجدد از تردهای موجود، تردهای جدیدی را ایجاد کند. همین مساله سبب بالا رفتن بیش از حد مصرف منابع و حافظه برنامه می‌گردد. یکی از روش‌های رفع این مشکل بدون نیاز به بهبودهای سخت افزاری، تبدیل اعمال blocking نامبرده شده به نمونه‌های non-blocking است. به این ترتیب ترد پردازش کننده‌ی این اعمال Async بلافاصله آزاد شده و سرور می‌تواند از آن جهت پردازش درخواست دیگری استفاده کند؛ بجای اینکه ترد جدیدی را وهله سازی نماید.

      ب) بالا بردن پاسخ دهی کلاینت‌ها

      کلاینت‌ها نیز اگر مدام درخواست‌های blocking را به سرور جهت دریافت پاسخی ارسال کنند، به زودی به یک رابط کاربری غیرپاسخگو خواهند رسید. برای رفع این مشکل نیز می‌توان ازتوانمندی‌های Async دات نت 4.5جهت آزاد سازی ترد اصلی برنامه یا همان ترد UI استفاده کرد.

      و ... تمام این‌ها یک شرط را دارند. نیاز است یک چنین API خاصی که اعمال Async واقعی را پشتیبانی می‌کنند، فراهم شده باشد. بنابراین صرفا وجود متد Task.Run، به معنای اجرای واقعی Async یک متد خاص نیست. برای این منظور ADO.NET 4.5 به همراه متدهای Async ویژه کار با بانک‌های اطلاعاتی است و پس از آن Entity framework 6 از این زیر ساخت استفاده کرده‌است که در ادامه جزئیات آن‌را بررسی خواهیم کرد.


      پیشنیازها

      برای کار با امکانات جدید Async موجود در EF 6 نیاز است از VS 2012 به بعد که به همراه کامپایلری است که واژه‌های کلیدی async و await را پشتیبانی می‌کند و همچنین دات نت 4.5 استفاده کرد. چون ADO.NET 4.5 اعمال async واقعی را پشتیبانی می‌کند، دات نت 4 در اینجا قابل استفاده نخواهد بود.


      متدهای الحاقی جدید Async در EF 6.x

      جهت متدهای الحاقی متداول EF مانند ToList، Max، Min و غیره، نمونه‌های Async آن‌ها نیز اضافه شده‌اند:
       QueryableExtensions:
      AllAsync
      AnyAsync
      AverageAsync
      ContainsAsync
      CountAsync
      FirstAsync
      FirstOrDefaultAsync
      ForEachAsync
      LoadAsync
      LongCountAsync
      MaxAsync
      MinAsync
      SingleAsync
      SingleOrDefaultAsync
      SumAsync
      ToArrayAsync
      ToDictionaryAsync
      ToListAsync
      
      DbSet:
      FindAsync
      
      DbContext:
      SaveChangesAsync
      
      Database:
      ExecuteSqlCommandAsync
      بنابراین اولین قدم تبدیل کدهای قدیمی به Async، استفاده از متدهای الحاقی فوق است.


      چند مثال


      فرض کنید، مدل‌های برنامه، رابطه‌ی one-to-many ذیل را بین یک کاربر و مقالات او دارند:
          public class User
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public virtual ICollection<BlogPost> BlogPosts { get; set; }
          }
      
          public class BlogPost
          {
              public int Id { get; set; }
              public string Title { get; set; }
              public string Content { get; set; }
      
              [ForeignKey("UserId")]
              public virtual User User { get; set; }
              public int UserId { get; set; }
          }
      همچنین Context برنامه نیز جهت در معرض دید قرار دادن این کلاس‌ها، به نحو ذیل تشکیل شده‌است:
          public class MyContext : DbContext
          {
              public DbSet<User> Users { get; set; }
              public DbSet<BlogPost> BlogPosts { get; set; }
      
              public MyContext()
                  : base("Connection1")
              {
                  this.Database.Log = sql => Console.Write(sql);
              }
          }
      بر این اساس مثالی که دو رکورد را در جداول کاربران و مقالات به صورت async ثبت می‌کند، به نحو ذیل خواهد بود:
              private async Task<User> addUserAsync(CancellationToken cancellationToken = default(CancellationToken))
              {
                  using (var context = new MyContext())
                  {
                      var user = context.Users.Add(new User
                      {
                          Name = "Vahid"
                      });
                      context.BlogPosts.Add(new BlogPost
                      {
                          Content = "Test",
                          Title = "Test",
                          User = user
                      });
                      await context.SaveChangesAsync(cancellationToken);
                      return user;
                  }
              }

      چند نکته جهت یادآوری مباحث Async

      - به امضای متد واژه‌ی کلیدی async اضافه شده‌است، زیرا در بدنه‌ی آن از کلمه‌ی کلیدی await استفاده کرده‌ایم (لازم و ملزوم هستند).
      - به انتهای نام متد، کلمه‌ی Async اضافه شده‌است. این مورد ضروری نیست؛ اما به یک استاندارد و قرارداد تبدیل شده‌است.
      - مدل Async دات نت 4.5 مبتنی بر Taskهااست. به همین جهت اینبار خروجی‌های توابع نیاز است از نوع Task باشند و آرگومان جنریک آن‌ها، بیانگر نوع مقداری که باز می‌گردانند.
      - تمام متدهای الحاقی جدیدی که نامبرده شدند، دارای پارامتر اختیاریلغو عملیاتنیز هستند. این مورد را با مقدار دهی cancellationToken در کدهای فوق ملاحظه می‌کنید.
      نمونه‌ای از نحوه‌ی مقدار دهی این پارامتر در ASP.NET MVCبه صورت زیر می‌تواند باشد:
       [AsyncTimeout(8000)]
      public async Task<ActionResult> Index(CancellationToken cancellationToken)
      در اینجا به امضای اکشن متد جاری، async اضافه شده‌است و خروجی آن نیز به نوع Task تغییر یافته است. همچنین یک پارامتر cancellationToken نیز تعریف شده‌است. این پارامتر به صورت خودکار توسط ASP.NET MVC پس از زمانیکه توسط ویژگی AsyncTimeout تعیین شده‌است، تنظیم خواهد شد. به این ترتیب، اعمال async در حال اجرا به صورت خودکار لغو می‌شوند.
      - برای اجرا و دریافت نتیجه‌ی متدهای Async دار EF، نیاز است از واژه‌ی کلیدی await استفاده گردد.

      استفاده کننده نیز می‌تواند متد addUserAsync را به صورت زیر فراخوانی کند:
       var user = await addUserAsync();
      Console.WriteLine("user id: {0}", user.Id);

      شبیه به همین اعمال را نیز جهت به روز رسانی و یا حذف اطلاعات خواهیم داشت:
              private async Task<User> updateAsync(CancellationToken cancellationToken = default(CancellationToken))
              {
                  using (var context = new MyContext())
                  {
                      var user1 = await context.Users.FindAsync(cancellationToken, 1);
                      if (user1 != null)
                          user1.Name = "Vahid N.";
      
                      await context.SaveChangesAsync(cancellationToken);
                      return user1;
                  }
              }
      
              private async Task<int> deleteAsync(CancellationToken cancellationToken = default(CancellationToken))
              {
                  using (var context = new MyContext())
                  {
                      var user1 = await context.Users.FindAsync(cancellationToken, 1);
                      if (user1 != null)
                          context.Users.Remove(user1);
      
                      return await context.SaveChangesAsync(cancellationToken);
                  }
              }

      کدهای Async تقلبی!

      به قطعه کد ذیل دقت کنید:
               public async Task<List<TEntity>> GetAllAsync()
         {
          return await Task.Run(() => _tEntities.ToList());
         }
      این متد از یکی از Generic repositoryهای فله‌ای رها شده در اینترنت انتخاب شده‌است.
      به این نوع متدها که از Task.Run برای فراخوانی متدهای همزمان قدیمی مانند ToList جهت Async جلوه دادن آن‌ها استفاده می‌شود، کدهای Async تقلبیمی‌گویند! این عملیات هر چند در یک ترد دیگر انجام می‌شود اما هم سربار ایجاد یک ترد جدید را به همراه دارد و هم عملیات ToList آن کاملا blocking است.
      معادل صحیح Async واقعی این عملیات را در ذیل مشاهده می‌کنید:
              private async Task<List<User>> getUsersAsync(CancellationToken cancellationToken = default(CancellationToken))
              {
                  using (var context = new MyContext())
                  {
                      return await context.Users.ToListAsync(cancellationToken);
                  }
              }
      متد ToListAsync یک متد Async واقعی است و نه شبیه سازی شده توسط Task.Run. متدهای Async واقعی کار با شبکه و اعمال I/O، از ترد استفاده نمی‌کنندو توسط سیستم عامل به نحو بسیار بهینه‌ای اجرا می‌گردند.
      برای مثال پشت صحنه‌ی متد الحاقی SaveChangesAsync به یک چنین متدی ختم می‌شود:
       internal override async Task<long> ExecuteAsync(
      //...
      rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
      //...
      متد ExecuteNonQueryAsync جزو متدهای ADO.NET 4.5 است و برای اجرا نیاز به هیچ ترد جدیدی ندارد.
      و یا برای شبیه سازی ToListAsync با ADO.NET 4.5 و استفاده از متدهای Async واقعی آن، به یک چنین کدهایی نیاز است: 
          var connectionString = "........";
          var sql = @"......"";
          var users = new List<User>(); 
          using (var cnx = new SqlConnection(connectionString))
          { using (var cmd = new SqlCommand(sql, cnx)) {
             await cnx.OpenAsync(); 
             using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
             {
              while (await reader.ReadAsync())
              { var user = new User {
                 Id = reader.GetInt32(0), 
                 Name = reader.GetString(1),  };
               users.Add(user);
              }
             } }
          }


      محدودیت پردازش موازی اعمال در EF

      در متد ذیل، دو Task غیرهمزمان تعریف شده‌اند و سپس با await Task.WhenAll درخواست اجرای همزمان و موازی آن‌ها را کرده‌ایم:
              // multiple operations
              private static async Task loadAllAsync(CancellationToken cancellationToken = default(CancellationToken))
              {
                  using (var context = new MyContext())
                  {
                      var task1 = context.Users.ToListAsync(cancellationToken);
                      var task2 = context.BlogPosts.ToListAsync(cancellationToken);
      
                      await Task.WhenAll(task1, task2);
                      // use task1.Result
                  }
              }
      این متد ممکن است اجرا شود؛ یا در بعضی از مواقع با استثنای ذیل خاتمه یابد:
        An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
       Additional information: A second operation started on this context before a previous asynchronous operation completed.
      Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context.
      Any instance members are not guaranteed to be thread safe.
      متن استثنای ارائه شده بسیار مفید است و توضیحات کامل را به همراه دارد. در EF در طی یک Context اگر عملیات Async شروع شده‌ای خاتمه نیافته باشد، مجاز به شروع یک عملیات Async دیگر، به موازت آن نخواهیم بود. برای رفع این مشکل یا باید از چندین Context استفاده کرد و یا await Task.WhenAll را حذف کرده و بجای آن واژه‌ی کلیدی await را همانند معمول، جهت صبر کردن برای دریافت نتیجه‌ی یک عملیات غیرهمزمان استفاده کنیم.

      ‫پردازش داده‌های جغرافیایی به کمک SQL Server و Entity framework

      $
      0
      0
      پشتیبانی SQL Server از Spatial data

      از SQL Server 2008 به بعد، نوع داده جدیدی به نام geography به نوع‌های قابل تعریف ستون‌ها اضافه شده‌است. در این نوع ستون‌ها می‌توان طول و عرض جغرافیایی یک نقطه را ذخیره کرد و سپس به کمک توابع توکاری از آن‌ها کوئری گرفت.


      در اینجا نمونه‌ای از نحوه‌ی تعریف و همچنین مقدار دهی این نوع ستون‌ها را مشاهده می‌کنید:
       CREATE TABLE [Geo](
      [id] [int] IDENTITY(1,1) NOT NULL,
      [Location] [geography] NULL
      )
      
       insert into Geo( Location , long, lat ) values
      ( geography::STGeomFromText ('POINT(-121.527200 45.712113)', 4326))
      متد geography::STGeoFromText یک SQL CLR function است. این متد در مثال فوق، مختصات یک نقطه را دریافت کرده‌است. همچنین نیاز دارد بداند که این نقطه توسط چه نوع سیستم مختصاتی ارائه می‌شود. عدد 4326 در اینجا یک SRIDیا Spatial Reference System Identifier استاندارد است. برای نمونه اطلاعات ارائه شده توسط Google و یا Bing توسط این استاندارد ارائه می‌شوند.
      در اینجا متدهای توکار دیگری مانند geography::STDistance برای یافتن فاصله مستقیم بین نقاط نیز ارائه شد‌ه‌اند. خروجی آن بر حسب متر است.


      پشتیبانی از Spatial Data در Entity framework

      پشتیبانی از نوع مخصوص geography، در EF 5 توسط نوع داده‌ای DbGeography ارائه شد. این نوع داده‌ای immutable است. به این معنا که پس از نمونه سازی، دیگر مقدار آن قابل تغییر نیست.
      در اینجا برای نمونه مدلی را مشاهده می‌کنید که از نوع داده‌ای DbGeography استفاده می‌کند:
      using System.Data.Entity.Spatial;
      
      namespace EFGeoTests.Models
      {
          public class GeoLocation
          {
              public int Id { get; set; }
              public DbGeography Location { get; set; }
              public string Name { get; set; }
              public string Type { get; set; }
      
              public override string ToString()
              {
                  return string.Format("Name:{0}, Location:{1}", Name, Location);
              }
          }
      }
      به همراه یک Context، تا کلاس GeoLocation در معرض دید EF قرار گیرد:
      using System;
      using System.Data.Entity;
      using EFGeoTests.Models;
      
      namespace EFGeoTests.Config
      {
          public class MyContext : DbContext
          {
              public DbSet<GeoLocation> GeoLocations { get; set; }
      
              public MyContext()
                  : base("Connection1")
              {
                  this.Database.Log = sql => Console.Write(sql);
              }
          }
      }
      برای مقدار دهی خاصیت Location از نوع DbGeography می‌توان از متد ذیل استفاده کرد که بسیار شبیه به متد geography::STGeoFromText عمل می‌کند:
         private static DbGeography createPoint(double longitude, double latitude,  int coordinateSystemId = 4326)
        {
             var text = string.Format(CultureInfo.InvariantCulture.NumberFormat,"POINT({0} {1})", longitude, latitude);
             return DbGeography.PointFromText(text, coordinateSystemId);
        }


      تهیه منبع داده‌ی جغرافیایی

      برای تدارک یک مثال واقعی جغرافیایی، نیاز به اطلاعاتی دقیق داریم. این نوع اطلاعات عموما توسط یک سری فایل مخصوص به نام Shapefilesکه حاوی اطلاعات برداری جغرافیایی هستند ارائه می‌شوند. برای نمونه اطلاعات جغرافیایی به روز ایران را از آدرس ذیل می‌توانید دریافت کنید:
      http://download.geofabrik.de/asia/iran.html
      http://download.geofabrik.de/asia/iran-latest.shp.zip

      پس از دریافت این فایل، به تعدادی فایل با پسوندهای shp، shx و dbf خواهیم رسید.
      فایل‌های shp بیانگر فرمت اشکال ذخیره شده هستند. فایل‌های shx یک سری ایندکس بوده و فایل‌های dbf از نوع بانک اطلاعاتی dBase IV می‌باشند.
      همچنین اگر فایل‌های prj را باز کنید، یک چنین اطلاعاتی در آن موجودند:
      GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
      نکته‌ی مهمی که در اینجا باید مدنظر داشت، استاندارد GCS_WGS_1984آن است. این استاندارد معادل است با استاندارد EPSG 4326. عدد 4326 آن جهت ثبت این اطلاعات در یک بانک اطلاعاتی SQL Server حائز اهمیت است (پارامتر coordinateSystemId در متد createPoint) و ممکن است از هر فایلی به فایل دیگر متفاوت باشد.



      خواند‌ن فایل‌های shp در دات نت

      پس از دریافت فایل‌های shp و بانک‌های اطلاعاتی مرتبط با اطلاعات جغرافیایی ایران، اکنون نوبت به پردازش این فایل‌های مخصوص با فرمت بانک اطلاعاتی فاکس پرو مانند، رسیده‌است. برای این منظور می‌توان از پروژه‌ی سورس باز ذیل استفاده کرد:

      این پروژه در خواندن فایل‌های shp بدون نقص عمل می‌کند اما توانایی خواندن نام‌های فارسی وارد شده در این نوع بانک‌های اطلاعاتی را ندارد. برای رفع این مشکل، سورس آن را از Codeplex دریافت کنید. سپس فایل Shapefile.cs را گشوده و ابتدای خاصیت Current آن‌را به نحو ذیل تغییر دهید:
              /// <summary>
              /// Gets the current shape in the collection
              /// </summary>
              public Shape Current
              {
                  get 
                  {
                      if (_disposed) throw new ObjectDisposedException("Shapefile");
                      if (!_opened) throw new InvalidOperationException("Shapefile not open.");
                      // get the metadata
                      StringDictionary metadata = null;
                      if (!RawMetadataOnly)
                      {
                          metadata = new StringDictionary();
                          for (int i = 0; i < _dbReader.FieldCount; i++)
                          {
                              string value = _dbReader.GetValue(i).ToString();
                              if (_dbReader.GetDataTypeName(i) == "DBTYPE_WVARCHAR")
                              {
                                  // برای نمایش متون فارسی نیاز است
                                  value = Encoding.UTF8.GetString(Encoding.GetEncoding(720).GetBytes(value));
                              }
                              metadata.Add(_dbReader.GetName(i),
                                  value);
                          }
                      }
      در اینجا فقط سطر استفاده از Encoding خاصی با شماره 720 و تبدیل آن به UTF8 اضافه شده‌است. پس از آن بدون مشکل می‌توان برچسب‌های فارسی را از فایل‌های dBase IV این نوع بانک‌های اطلاعاتی استخراج کرد (اصلاح شده‌ی آن در فایل پیوست مطلب موجود است).
      using System.Collections.Generic;
      using System.Linq;
      using Catfood.Shapefile;
      
      namespace EFGeoTests
      {
          public class MapPoint
          {
              public Dictionary<string, string> Metadata { set; get; }
              public double X { set; get; }
              public double Y { set; get; }
          }
      
          public static class ShapeReader
          {
              public static IList<MapPoint> ReadShapeFile(string path)
              {
                  var results = new List<MapPoint>();
      
                  using (var shapefile = new Shapefile(path))
                  {
                      foreach (var shape in shapefile)
                      {
                          if (shape.Type != ShapeType.Point)
                              continue;
      
                          var shapePoint = shape as ShapePoint;
                          if (shapePoint == null)
                              continue;
      
      
                           var metadataNames = shape.GetMetadataNames();
                          if(!metadataNames.Any())
                              continue;
      
                          var metadata = new Dictionary<string, string>();
                          foreach (var metadataName in metadataNames)
                          {
                              metadata.Add(metadataName,shape.GetMetadata(metadataName));
                          }
      
                          results.Add(new MapPoint
                          {
                              Metadata = metadata,
                              X = shapePoint.Point.X,
                              Y = shapePoint.Point.Y
                          });
                      }
                  }
      
                  return results;
              }
          }
      }
      در کدهای فوق به کمک کتابخانه‌ی C# Esri Shapefile Reader، اطلاعات نقاط بانک اطلاعاتی shape files را خوانده و به صورت لیست‌هایی از MapPoint بازگشت می‌دهیم. نکته‌ی مهم آن، Metadata است که از هر فایلی به فایل دیگر می‌توان متفاوت باشد. به همین جهت این اطلاعات را به شکل ویژگی‌های key/value در این نوع بانک‌های اطلاعاتی ذخیره می‌کنند.


      افزودن اطلاعات جغرافیایی به بانک اطلاعاتی SQL Server به کمک Entity framework

      فایل places.shp را در مجموعه فایل‌هایی که در ابتدای بحث عنوان شدند، می‌توانید مشاهده کنید. قصد داریم اطلاعات نقاط آن‌را به مدل GeoLocation انتساب داده و سپس ذخیره کنیم:
                  var points = ShapeReader.ReadShapeFile("IranShapeFiles\\places.shp");
                  using (var context = new MyContext())
                  {
                      context.Configuration.AutoDetectChangesEnabled = false;
                      context.Configuration.ProxyCreationEnabled = false;
                      context.Configuration.ValidateOnSaveEnabled = false;
      
                      if (context.GeoLocations.Any())
                          return;
      
                      foreach (var point in points)
                      {
                          context.GeoLocations.Add(new GeoLocation
                          {
                              Name = point.Metadata["name"],
                              Type = point.Metadata["type"],
                              Location = createPoint(point.X, point.Y)
                          });
                      }
      
                      context.SaveChanges();
                  }
      تعریف متد createPoint را که بر اساس X و Y نقاط، معادل قابل پذیرش آن‌را جهت SQL Server تهیه می‌کند، در ابتدای بحث مشاهده کردید.
      در فایل‌های مرتبط با places.shp، متادیتا name، معادل نام شهرهای ایران است و type آن بیانگر شهر، روستا و امثال آن می‌باشد.
      پس از اینکه اطلاعات مکان‌های ایران، در SQL Server ذخیره شدند، نمایش بصری آن‌ها را در management studio نیز می‌توان مشاهده کرد:



      کوئری گرفتن از اطلاعات جغرافیایی

      فرض کنید می‌خواهیم مکان‌هایی را با فاصله کمتر از 5 کیلومتر از تهران پیدا کنیم:
                  var tehran = createPoint(51.4179604, 35.6884243);
      
                  using (var context = new MyContext())
                  {
                      // find any locations within 5 kilometers ordered by distance
                      var locations = context.GeoLocations
                          .Where(loc => loc.Location.Distance(tehran) < 5000)
                          .OrderBy(loc => loc.Location.Distance(tehran))
                          .ToList();
      
                      foreach (var location in locations)
                      {
                          Console.WriteLine(location.Name);
                      }
                  }
      همانطور که پیشتر نیز عنوان شد، متد Distance بر اساس متر کار می‌کند. به همین جهت برای تعریف 5 کیلومتر به نحو فوق عمل شده‌است. همچنین نحوه‌ی مرتب سازی اطلاعات نیز بر اساس فاصله از یک مکان مشخص صورت گرفته‌است.
      و یا اگر بخواهیم دقیقا بر اساس مختصات یک نقطه، مکانی را بیابیم، می‌توان از متد SpatialEquals استفاده کرد:
                  var tehran = createPoint(51.4179604, 35.6884243);
                  using (var context = new MyContext())
                  {
                      // find any locations within 5 kilometers ordered by distance
                      var tehranLocation = context.GeoLocations.FirstOrDefault(loc => loc.Location.SpatialEquals(tehran));
                      if (tehranLocation != null)
                      {
                          Console.WriteLine(tehranLocation.Type);
                      }
                  }

      کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
      EFGeoTests.zip
       

      ‫بررسی خطاهای متداول عملیات Migration در حین به روز رسانی پروژه‌های EF Code First

      $
      0
      0

      1. شاید یکی از آزاردهنده‌ترین مشکلات، برخورد با پیغام‌های خطا، هنگام عملیات migrationباشد. یکی از ده‌ها نوع خطا، زمانی رخ می‌دهد که متد seedدر حال اجراست. در این حالت هیچ نوع break-point ایی به کمک ما نخواهد آمد.

      سوال ایجاست که آیا می‌توان این بخش را دیباگ نمود؟ بهترین راه حل، اجرای آپدیت از طریق متدها(یا اکشن ها) است.

      فراخوانی migration بسیار سادهاست. باید یک نمونه از کلاس Configurationرا ساختهو در جایی از پروژه قرار دهیم و صد البته مطمئن باشیم که migration  فعال است. 

      var configuration = new Configuration();
      var migrator = new DbMigrator(configuration);
      migrator.Update();

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

      var configuration = new Configuration();
      configuration.TargetDatabase = new DbConnectionInfo(
          "Server=MyServer;Database=MyDatabase;Trusted_Connection=True;", 
          "System.Data.SqlClient");
      var migrator = new DbMigrator(configuration);
      migrator.Update();
      می‌توانید این کد را در ابتدای اکشن indexدر کنترلر Homeقرار دهید و با قرار دادن Break-Pointدر بخش‌های مختلف متد Seed، آن را بررسی کنید. 

      2. خطای :

      Duplicate type name within an assembly.

       معمولا بخاطر وجود break-point این مشکل رخ میدهد. یا break-pointهایدرون seed را حذف کنید یا جای آنها را تغییر دهید. [اینجا]

      3.خطای : 

      Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

      این مشکل میتواند دلایل مختلفی داشته باشد.کد درون متد seed را داخلtry/catch قرار دهید و علت آن را بررسی کنید:

                      try
                      {
                          var user = new ApplicationUser { UserName = "Admin", Phone = "09120000000", Email = "m@gmail.com" };
                          usermanager.Create(user, "09120000000");                                
                          usermanager.AddToRole(user.Id, "admin");
                      }
                      catch (DbEntityValidationException dbEx)
                      {
                          foreach (var validationErrors in dbEx.EntityValidationErrors)
                          {
                              foreach (var validationError in validationErrors.ValidationErrors)
                              {
                                  Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                              }
                          }
                      }

      فراموش نکنید، seed را به کمک حالتی که در شماره 1 گفته شد اجرا کنید تا بتوانید از BreakPoint استفاده کنید.

      4.خطای : 

      More than one context type was found in the assembly 'Ex1_CodeFirst'

      این خطا وقتی ظاهر میشود که چندین Context برای پروژه جاری داشته باشیم و ویژوال استودیو نتواند Context مورد نظر ما را تشخیص دهد. بهتر است هنگام اجرای migration نام context مورد نظر را بنویسیم (مثلا MyConfiguration نام کانتکست شماست) :

      Update-Database -ConfigurationTypeName MyConfiguration
      5.خطای : 

      There is already an object named 'UserProfile' in the database.

      یعنی مدل شما پس از اینکه جدولی با همین نام (در این جا به عنوان مثال UserProfile) از پیش موجود بوده، تغییر کرده است؛ پس migration آپدیت شدنی نیست. ابتدا این دستور را می‌نویسیم (پیش از آنکه تغییراتمان را روی کلاس مربوط به جدول UserProfileاعمال کنیم): 

      Add-Migration Initial –IgnoreChanges
      اینکار باعث می‌شود یک فایل خالی به نام InitialMigrationایجاد شود. حالا تنظیمات مورد نظر، اعم از تغییر نام یا اضافه کردن ستون خاص را به کلاس UserProfileاعمال کرده و دیتابیس را آپدیت میکنیم :
      update-database –verbose

      گاهی این مشکل زمانی پیش می‌آید که واقعا تغییری در جدول نامبرده انجام نداده‌ایم. در این حالت روش پله‌ای زیر را به کار می‌بریم: 
            • پاک کردن یا ریست کردن migration (در شماره 9 همین مقاله این کار در چند مرحله توضیح داده شده است. در مرحله سوم حتما از Add-Migration Initial –IgnoreChangesاستفاده کنید)  
            • بعد از آپدیت دیتابیس باید فایل زیر دارای محتویات باشد  

      محتویات این فایل دقیقا شرایط فعلی جدول شماست.
            • اگر این فایل ایجاد نشده است مراحل را تکرار کنید تا محتویات درون آن را ببینید.
            • حالا تغییری را در یکی از مدل‌های خود انجام دهید. احتمالا با مشکل آپدیت شدن مواجه میشوید و پیغام زیر را دوباره خواهید دید: 
      There is already an object named 'UserProfile' in the database
            • جدولی را که پیغام خطا به آن اشاره کرده، در فایل فوق بیابید و محدوده createآن را کامنت کنید تا ساخته نشود. 
            • دیتابیس را آپدیت کنید، احتمالا پیغام خطای فوق برای جدول دیگری نمایش داده می‌شود. آن را هم کامنت کنید و دیتابیس را آپدیت کنید و اگر باز هم خطا بود مکانیزم بالا را تا جایی تکرار کنید که خطایی نبینید .
            • حالا جدولی را که تغییراتی در آن داده بودید، در دیتابیس چک کنید که تغییرات اعمال شده باشد.
            • هر آنچه را در فایل initialکامنت کرده بودید، از کامنت خارج کنید و دیتابیس را آپدیت کنید .
            • برای آزمایش، یک آیتم به یکی از مدل‌ها اضافه کنید و ببینید که migration درست کار می‌کند یا خیر.
      6.  خطای : 

      Unable to update database to match the current model because there are pending changes 
      and automatic migration is disabled. Either write the pending model changes to a code-based 
      migration or enable automatic migration. 
      Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.
      You can use the Add-Migration command to write the pending model changes to a code-based migration.
      همانطور که از متن این پیغام پیداست، یعنی شما AutomaticMigrationرا true نکرده‌اید و اینکار را باید در فایل  Configuration.cs  در فولدر Migration انجام داد. 

      7.خطای : 
      Automatic migration was not applied because it would result in data loss.
      این خط را در سازنده‌ی کلاس Configuration اضافه میکنیم :

         AutomaticMigrationDataLossAllowed = true;
      8.خطای : 

      Introducing FOREIGN KEY constraint 'FK_dbo.ProductProductGroups_dbo.ProductGroups_ProductGroupId' on table 
      'ProductProductGroups' may cause cycles or multiple cascade paths. 
      Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
      Could not create constraint or index. See previous errors.
      خطای فوق وقتی رخ میدهد که دو جدول از یک طرف به هم وصل باشند و از طرف دیگر مشخصات ذکر نشده باشد.
      9. راه حل برای خطاهای عجیبی که شاید نیاز به صرف زمان بیشتر برای کشف و برطرف کردن داشته باشند : 
      بهتر نیست مایگریشن خود را از نو بسازید؟
      روش به روز رسانی و بازسازی migration [اینجا ]:
      - پاک کردن فولدر Migrations در پروژه
      - پاک کردن جدولی به نام MigrationHistory_  در دیتابیس (ممکن است زیر مجموعه جدول‌های system باشد)
      - اجرای دستور زیر کنسول پکیچ منیجر ویژوال استودیو :
       Enable-Migrations -EnableAutomaticMigrations -Force
      - اجرای دستور زیر :
       Add-Migration Initial


      ‫روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework

      $
      0
      0
      در این مطلب تعدادی از شایع‌ترین مشکلات حین کار با Entity framework که نهایتا به تولید برنامه‌هایی کند منجر می‌شوند، بررسی خواهند شد.

      مدل مورد بررسی

          public class User
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public virtual ICollection<BlogPost> BlogPosts { get; set; }
          }
      
          public class BlogPost
          {
              public int Id { get; set; }
              public string Title { get; set; }
              public string Content { get; set; }
      
              [ForeignKey("UserId")]
              public virtual User User { get; set; }
              public int UserId { get; set; }
          }
      کوئری‌هایی که در ادامه بررسی خواهند شد، بر روی رابطه‌ی one-to-many فوق تعریف شده‌اند؛ یک کاربر به همراه تعدادی مطلب منتشر شده.


      مشکل 1: بارگذاری تعداد زیادی ردیف
       var data = context.BlogPosts.ToList();
      در بسیاری از اوقات، در برنامه‌های خود تنها نیاز به مشاهده‌ی قسمت خاصی از یک سری از اطلاعات، وجود دارند. به همین جهت بکارگیری متد ToList بدون محدود سازی تعداد ردیف‌های بازگشت داده شده، سبب بالا رفتن مصرف حافظه‌ی سرور و همچنین بالا رفتن میزان داده‌ای که هر بار باید بین سرور و کلاینت منتقل شوند، خواهد شد. یک چنین برنامه‌هایی بسیار مستعد به استثناهایی از نوع out of memory هستند.
      راه حل:  با استفاده از Skip و Take، مباحث صفحه‌ی بندیرا اعمال کنید.


      مشکل 2: بازگرداندن تعداد زیادی ستون
       var data = context.BlogPosts.ToList();
      فرض کنید View برنامه، در حال نمایش عناوین مطالب ارسالی است. کوئری فوق، علاوه بر عناوین، شامل تمام خواص تعریف شده‌ی دیگر نیز هست. یک چنین کوئری‌هایی نیز هربار سبب هدر رفتن منابع سرور می‌شوند.
      راه حل:اگر تنها نیاز به خاصیت Content است، از Select و سپس ToList استفاده کنید؛ البته به همراه نکته 1.
       var list = context.BlogPosts.Select(x => x.Content).Skip(15).Take(15).ToList();


      مشکل 3: گزارشگیری‌هایی که بی‌شباهت به حمله‌ی به دیتابیس نیستند
       foreach (var post in context.BlogPosts)
      {
           Console.WriteLine(post.User.Name);
      }
      فرض کنید قرار است رکوردهای مطالب را نمایش دهید. در حین نمایش این مطالب، در قسمتی از آن باید نام نویسنده نیز درج شود. با توجه به رابطه‌ی تعریف شده، نوشتن post.User.Name به ازای هر مطلب، بسیار ساده به نظر می‌رسد و بدون مشکل هم کار می‌کند. اما ... اگر خروجی SQL این گزارش را مشاهده کنیم، به ازای هر ردیف نمایش داده شده، یکبار رفت و برگشت به بانک اطلاعاتی، جهت دریافت نام نویسنده یک مطلب وجود دارد.
      این مورد به lazy loadingمشهور است و در مواردی که قرار است با یک مطلب و یک نویسنده کار شود، شاید اهمیتی نداشته باشد. اما در حین نمایش لیستی از اطلاعات، بی‌شباهت به یک حمله‌ی شدید به بانک اطلاعاتی نیست.
      راه حل:در گزارشگیری‌ها اگر نیاز به نمایش اطلاعات روابط یک موجودیت وجود دارد، از متد Include استفاده کنید تا Lazy loading لغو شود.
       foreach (var post in context.BlogPosts.Include(x=>x.User))


      مشکل 4:  فعال بودن بی‌جهت مباحث ردیابی اطلاعات
       var data = context.BlogPosts.ToList();
      در اینجا ما فقط قصد داریم که لیستی از اطلاعات را دریافت و سپس نمایش دهیم. در این بین، هدف، ویرایش یا حذف اطلاعات این لیست نیست. یک چنین کوئری‌هایی مساوی هستند با تشکیل dynamic proxies مخصوص EF جهت ردیابی تغییرات اطلاعات (مباحث AOPتوکار). EF توسط این dynamic proxies، محصور کننده‌هایی را برای تک تک آیتم‌های بازگشت داده شده از لیست تهیه می‌کند. در این حالت اگر خاصیتی را تغییر دهید، ابتدا وارد این محصور کننده (غشاء نامرئی) می‌شود، در سیستم ردیابی EF ذخیره شده و سپس به شیء اصلی اعمال می‌گردد. به عبارتی شیء در حال استفاده، هر چند به ظاهر post.User است اما در واقعیت یک User دارای روکشی نامرئی از جنس dynamic proxy‌های EF است. تهیه این روکش‌ها، هزینه‌بر هستند؛ چه از لحاظ میزان مصرف حافظه و چه از نظر سرعت کار.
      راه حل:در گزاشگیری‌ها، dynamic proxies را توسط متد AsNoTrackingغیرفعال کنید:
       var data = context.BlogPosts.AsNoTracking().Skip(15).Take(15).ToList();


      مشکل 5: باز کردن  تعداد اتصالات زیاد به بانک اطلاعاتی در طول یک درخواست

      هر Context دارای اتصال منحصربفرد خود به بانک اطلاعاتی است. اگر در طول یک درخواست، بیش از یک Context مورد استفاده قرار گیرد، بدیهی است به همین تعداد اتصال باز شده به بانک اطلاعاتی، خواهیم داشت. نتیجه‌ی آن فشار بیشتر بر بانک اطلاعاتی و همچنین کاهش سرعت برنامه است؛ از این لحاظ که اتصالات TCP برقرار شده، هزینه‌ی بالایی را به همراه دارند.
      روش تشخیص:
              private void problem5MoreThan1ConnectionPerRequest() 
              {
                  using (var context = new MyContext())
                  {
                      var count = context.BlogPosts.ToList();
                  }
              }
      داشتن متدهایی که در آن‌ها کار وهله سازی و dispose زمینه‌ی EF انجام می‌شود (متدهایی که در آن‌ها new Context وجود دارد).
      راه حل:برای حل این مساله باید از روش‌های تزریق وابستگی‌هااستفاده کرد. یک Context وهله سازی شده‌ی در طول عمر یک درخواست، باید بین وهله‌های مختلف اشیایی که نیاز به Context دارند، زنده نگه داشته شده و به اشتراک گذاشته شود.


      مشکل 6: فرق است بین IList و IEnumerable
      DataContext = from user in context.Users
                            where user.Id>10
                            select user;
      خروجی کوئری LINQ نوشته شده از نوع IEnumerable است. در EF، هربار مراجعه‌ی مجدد به یک کوئری که خروجی IEnumerable دارد، مساوی است با ارزیابی مجدد آن کوئری. به عبارتی، یکبار دیگر این کوئری بر روی بانک اطلاعاتی اجرا خواهد شد و رفت و برگشت مجددی صورت می‌گیرد.
      زمانیکه در حال تهیه‌ی گزارشی هستید، ابزارهای گزارشگیر ممکن است چندین بار از نتیجه‌ی کوئری شما در حین تهیه‌ی گزارش استفاده کنند. بنابراین برخلاف تصور، data binding انجام شده، تنها یکبار سبب اجرای این کوئری نمی‌شود؛ بسته به ساز و کار درونی گزارشگیر، چندین بار ممکن است این کوئری فراخوانی شود.
      راه حل:یک ToList را به انتهای این کوئری اضافه کنید. به این ترتیب از نتیجه‌ی کوئری، بجای اصل کوئری استفاده خواهد شد و در این حالت تنها یکبار رفت و برگشت به بانک اطلاعاتی را شاهد خواهید بود.


      مشکل 7: فرق است بین IQueryable و IEnumerable

      خروجی IEnumerable، یعنی این عبارت را محاسبه کن. خروجی IQueryable یعنی این عبارت را درنظر داشته باش. اگر نیاز است نتایج کوئری‌ها با هم ترکیب شوند، مثلا بر اساس رابط کاربری برنامه، کاربر بتواند شرط‌های مختلف را با هم ترکیب کند، باید از ترکیب IQueryableهااستفاده کرد تا سبب رفت و برگشت اضافی به بانک اطلاعاتی نشویم.


      مشکل 8: استفاده از کوئری‌های Like دار
       var list = context.BlogPosts.Where(x => x.Content.Contains("test"))
      این نوع کوئری‌ها که در نهایت به Like در SQL ترجمه می‌شوند، سبب full table scan خواهند شد که کارآیی بسیار پایینی دارند. در این نوع موارد توصیه شده‌است که از روش‌های full text searchاستفاده کنید.


      مشکل 9: استفاده از Count بجای Any

      اگر نیاز است بررسی کنید مجموعه‌ای دارای مقداری است یا خیر، از Count>0 استفاده نکنید. کارآیی Any و کوئری SQL ایی که تولید می‌کند، به مراتب بیشتر و بهینه‌تر استاز Count>0.


      مشکل 10: سرعت insert پایین است

      ردیابی تغییرات را خاموش کردهو از متد جدید AddRange استفاده کنید. همچنین افزونه‌هایی برای Bulk insertنیز موجود هستند.


      مشکل 11: شروع برنامه کند است

      می‌توان تمام مباحث نگاشت‌های پویای کلاس‌های برنامه به جداول و روابط بانک اطلاعاتی را به صورت کامپایل شده در برنامه ذخیره کرد. این مورد سبب بالا رفتن سرعت شروع برنامه خصوصا در حالتیکه تعداد جداول بالا است می‌شود.

      ‫ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 10

      $
      0
      0
      بهره‌گیری از یک تابع پویا برای افزودن، ویرایش
      در مثال‌های گذشتهدیدید که برای هر کدام از عمل‌های درج، ویرایش و حذف، تابع‌های مختلفی نوشته بودیم که این‌کار هنگامی‌که یک پروژه‌ی بزرگ در دست داریم زمان‌بر خواهد بود. چه بسا یک جدول بزرگ داشته باشیم و بخواهیم در هر فرمی، ستون یا ستون‌های خاص به‌روزرسانی شوند. برای رفع این نگرانی افزودن تابع زیر به سرویس‌مان گره‌گشا خواهد بود.
              public bool AddOrUpdateOrDelete<TEntity>(TEntity newItem, bool updateIsNull) where TEntity : class
              {
                  try
                  {
                      var dbMyNews = new dbMyNewsEntities();
                      if (updateIsNull)
                          dbMyNews.Set<TEntity>().AddOrUpdate(newItem);
                      else
                      {
                          dbMyNews.Set<TEntity>().Attach(newItem);
                          var entry = dbMyNews.Entry(newItem);
                          foreach (
                              var pri in newItem.GetType().GetProperties()
                                  .Where(pri => (pri.GetGetMethod(false).ReturnParameter.ParameterType.IsSerializable &&
                                                 pri.GetValue(newItem, null) != null)))
                          {
                              entry.Property(pri.Name).IsModified = true;
                          }
                      }
                      dbMyNews.SaveChanges();
                      return true;
                  }
                  catch (Exception)
                  {
                      return false;
                  }
              }
      این تابع دو پارامتر ورودی  newItem و updateIsNull دارد که نخستین، همان نمونه‌ای از Entity است که قصد افزودن، ویرایش یا حذف آن‌را داریم و با دومی مشخص می‌کنیم که آیا ستون‌هایی که دارای مقدار null هستند نیز در موجودیت اصلی به‌هنگام شوند یا خیر. این پارامتر جهت رفع این مشکل گذاشته شده است که هنگامی‌که قصد به‌هنگام‌کردن یک یا چند ستون خاص را داشتیم و تابع update را به گونه‌ی زیر صدا می‌زدیم، بقیه‌ی ستون‌ها مقدار null می‌گرفت.
      var news = new tblNews();
      news.tblCategoryId = 2;
      news.tblNewsId = 1;
      MyNews.EditNews(news);
      توسط تکه کد بالا، ستون tblCategoryId از جدول  tblNews با شرط این‌که شناسه‌ی جدول آن برابر با 1 باشد، مقدار 2 خواهد گرفت. ولی بقیه‌ی ستون‌های آن به علت این‌که مقداری برای آن مشخص نکرده ایم، مقدار null خواهد گرفت.
      راهی که برای حل آن استفاده می‌کردیم، به این صورت بود:
       var news = MyNews.GetNews(1);
       news.tblCategoryId = 2;
      MyNews.EditNews(news)  
      در این روش یک رفت و برگشت بی‌هوده به WCF انجام خواهد شد در حالتی که ما اصلاً نیازی به مقدار ستون‌های دیگر نداریم و اساساً کاری روی آن نمی‌خواهیم انجام دهیم.
      در تابع AddOrUpdateOrDelete نخست بررسی می‌کنیم که آیا این‌که ستون‌هایی که مقدار ندارند، در جدول اصلی هم مقدار null بگیرند برای ما مهم است یا نه. برای نمونه هنگامی‌که می‌خواهیم سطری به جدول بیفزاییم یا این‌که واقعاً بخواهیم مقدار دیگر ستون‌ها برابر با null شود. در این صورت همان متد  AddOrUpdate از Entity Framework اجرا خواهد شد.
      حالت دیگر که در حذف و ویرایش از آن بهره می‌بریم با یک دستور foreach همه‌ی پروپرتی‌هایی که Serializable باشد (که در این صورت پروپرتی‌های virtual حذف خواهد شد) و مقدار آن نامساوی با null باشد، در حالت ویرایش خواهند گرفت و در نتیجه دیگر ستون‌ها ویرایش نخواهد شد. این دستور دیدگاه جزء‌نگر دستور زیر است که کل موجودیت را در وضعیت ویرایش قرار می‌داد:
      dbMyNews.Entry(news).State = EntityState.Modified;
      با آن‌چه گفته شد، می‌توانید به جای سه تابع زیر:
         public int AddNews(tblNews News)
              {
                  dbMyNews.tblNews.Add(News);
                  dbMyNews.SaveChanges();
                  return News.tblNewsId;
              }
      
              public bool EditNews(tblNews News)
              {
                  try
                  {
                      dbMyNews.Entry(News).State = EntityState.Modified;
                      dbMyNews.SaveChanges();
                      return true;
                  }
                  catch (Exception exp)
                  {
                      return false;
                  }
              }
      
              public bool DeleteNews(int tblNewsId)
              {
                  try
                  {
                      tblNews News = dbMyNews.tblNews.FirstOrDefault(p => p.tblNewsId == tblNewsId);
                      News.IsDeleted = true;
                      dbMyNews.SaveChanges();
                  return true;
                  }
                  catch (Exception exp)
                  {
                      return false;
                  }
              }
      تابع زیر را بنویسید:
             public bool AddOrEditNews(tblNews News)
              {
                  return AddOrUpdateOrDelete(News, News.tblNewsId == 0);
              }
      به همین سادگی. من در این‌جا شرط کردم فقط در حالت درج، از قسمت نخست تابع بهره گرفته شود.
      در سمت برنامه از این تابع برای عمل درج، ویرایش و حذف به سادگی و بدون نگرانی استفاده می‌کنید. برای نمونه جهت حذف در یک خط به این صورت می‌نویسید:
      MyNews.AddOrEditNews (new tblNews { tblNewsId = 1, IsDeleted =true });
      در بخش پسین آموزش، پیرامون ایجاد امنیت در WCF خواهم نوشت.

      ‫بهبود عملکرد SQL Server Locks در سیستم‌های با تعداد تراکنش بالا در Entity Framework

      $
      0
      0
      بر اساس رفتار پیش فرضدر دیتابیس SQL Server، در زمان انجام دادن یک دستور که منجر به ایجاد تغییرات در اطلاعات موجود در جدول می‌شود (برای مثال دستور Update)، جدول مربوطه به صورت کامل Lock می‌شود، ولو آن دستور Update، فقط با یکی از رکوردهای آن جدول کار داشته باشد.

      در سیستم‌های با تعداد تراکنش بالا و دارای تعداد زیاد کلاینت، این رفتار پیش فرض موجب ایجاد صفی از تراکنش‌های در حال انتظار بر روی جداولی می‌شود که ویرایش‌های زیادی بر روی آنها رخ می‌دهد.
      اگر چه که بنظر این مشکل راه حل‌های زیادی دارد، لکن آن راه حلی که همیشه موثر عمل می‌کند استفاده از SQL Server Table Hints است.
      SQL Server Table Hints به تمامی آن دستوراتی گفته می‌شود که هنگام اجرای دستور اصلی (برای مثال Select و یا Update) رفتار پیش فرض SQL Server را بر اساس Hint ارائه شده تغییر می‌دهند.
      لیست کامل این Hint‌ها را می‌توانید در اینجا مشاهده کنید.
      Hint ای که در اینجا برای ما مفید است، آن است که به SQL Server بگوییم هنگام اجرای دستور Update، به جای Lock کردن کل جدول، فقط رکورد در حال ویرایش را Lock کند، و این باعث می‌شود تا باقی تراکنش ها، که ای بسا با سایر رکوردهای آن جدول کار داشته باشند متوقف نشوند، که البته این مسئله کمی به افزایش مصرف حافظه می‌انجامد، لکن مقدار افزایش بسیار ناچیز است.
      این Hint که rowlock نام دارد در تراکنش‌های با Isolation Level تنظیم شده بر روی Snapshot باید با یک Table Hint دیگر با نام updlock ترکیب شود.
      توضیحات مفصل‌تر این دو Hint در لینک مربوطه آمده است.
      بنابر این، بجای دستور
      update products
      set Name = "Test"
      Where Id = 1
      داریم
      update products with (nolock,updlock)
      set Name = "Test"
      where Id = 1
      تا اینجا مشکل خاصی وجود ندارد، آنچه که از اینجا به بعد اهمیت دارد این است که در هنگام کار با Entity Framework، اساسا ما نویسنده دستورات Update نیستیم که به آنها Hint اضافه کنیم یا نه، بلکه دستورات SQL بوسیله Entity Framework ایجاد می‌شوند.
      در Entity Framework، مکانیزمی تعبیه شده است با نام Db Command Interceptor که به شما اجازه می‌دهد دستورات SQL ساخته شده را Log کنیدو یا قبل از اجرا تغییر دهید، که برای اضافه نمودن Table Hint‌ها ما از این روش استفاده می‌کنیم، برای انجام این کار داریم: (توضیحات در ادامه)
          public class UpdateRowLockHintDbCommandInterceptor : IDbCommandInterceptor    {        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<Int32> interceptionContext)        {            if (command.CommandType != CommandType.Text) return; // (1)            if (!(command is SqlCommand)) return; // (2)            SqlCommand sqlCommand = (SqlCommand)command;            String commandText = sqlCommand.CommandText;            String updateCommandRegularExpression = "(update) ";            Boolean isUpdateCommand = Regex.IsMatch(commandText, updateCommandRegularExpression, RegexOptions.IgnoreCase | RegexOptions.Multiline); // You may use better regular expression pattern here.            if (isUpdateCommand)            {                Boolean isSnapshotIsolationTransaction = sqlCommand.Transaction != null && sqlCommand.Transaction.IsolationLevel == IsolationLevel.Snapshot;                String tableHintToAdd = isSnapshotIsolationTransaction ? " with (rowlock , updlock) set " : " with (rowlock) set ";                commandText = Regex.Replace(commandText, "^(set) ", (match) =>                {                    return tableHintToAdd;                }, RegexOptions.IgnoreCase | RegexOptions.Multiline);                command.CommandText = commandText;            }        }
      این کد در قسمت (1) ابتدا تشخیص می‌دهد که آیا این یک Command دارای Command Text است یا خیر، برای مثال اگر فراخوانی یک Stored Procedure است، ما با آن کاری نداریم.
      در قسمت دوم تشخیص می‌دهیم که آیا با SQL Server در حال تعامل هستیم، یا برای مثال با Oracle و ...، که ما برای Table Hint‌ها فقط با SQL Server کار داریم.
      سپس باید تشخیص دهیم که آیا این یک دستور update است یا خیر ؟ برای این منظور از Regular Expression‌ها استفاده کرده ایم، که خیلی به بحث آموزش این پست مربوط نیست، به صورت کلی از Regular Expression‌ها برای یافتن و بررسی و جایگزینی عبارات با قاعده در هنگام کار با رشته‌ها استفاده می‌شود.
      ممکن است Regular Expression ای که شما می‌نویسید بسیار بهتر از این نمونه باشد، که در این صورت خوشحال می‌شوم در قسمت نظرات آنرا قرار دهید.
      در نهایت با بررسی Transaction Isolation Level مربوطه که Snapshot است یا خیر، به درج یک یا هر دو Table Hint مربوطه اقدام می‌نماییم.

      ‫استفاده از Full text search توسط Entity Framework

      $
      0
      0
      پیشنیاز مطلب:
      پشتیبانی از Full Text Search در SQL Server

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

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

      کلاس FullTextSearchExtensions :
      public static class FullTextSearchExtensions
          {
      
              /// <summary>
              /// 
              /// </summary>
              /// <typeparam name="TEntity"></typeparam>
              /// <param name="source"></param>
              /// <param name="expression"></param>
              /// <param name="searchTerm"></param>
              /// <returns></returns>
              public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
              {
                  return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm));
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <typeparam name="TEntity"></typeparam>
              /// <param name="source"></param>
              /// <param name="expression"></param>
              /// <param name="searchTerm"></param>
              /// <returns></returns>
              public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
              {
                  return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm));
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <typeparam name="TEntity"></typeparam>
              /// <param name="source"></param>
              /// <param name="expression"></param>
              /// <param name="searchTerm"></param>
              /// <returns></returns>
              private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm)
              {
                  if (String.IsNullOrEmpty(searchTerm))
                  {
                      return source;
                  }
      
                  // The below represents the following lamda:
                  // source.Where(x => x.[property].Contains(searchTerm))
      
                  //Create expression to represent x.[property].Contains(searchTerm)
                  //var searchTermExpression = Expression.Constant(searchTerm);
                  var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value");
                  var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
      
                  //Join not null and contains expressions
      
                  var methodCallExpression = Expression.Call(typeof(Queryable),
                                                             "Where",
                                                             new[] { source.ElementType },
                                                             source.Expression,
                                                             Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters));
      
                  return source.Provider.CreateQuery<TEntity>(methodCallExpression);
              }

      در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
      متد FreeTextSearch جهت استفاده از دستور FREETEXT  استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
      متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو. 
      متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
       
      کلاس FtsInterceptor :
       public class FtsInterceptor : IDbCommandInterceptor
          {
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
              {
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
              {
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
              {
                  RewriteFullTextQuery(command);
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
              {
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
              {
                  RewriteFullTextQuery(command);
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="command"></param>
              /// <param name="interceptionContext"></param>
              public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
              {
              }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="cmd"></param>
              public static void RewriteFullTextQuery(DbCommand cmd)
              {
                  var text = cmd.CommandText;
                  for (var i = 0; i < cmd.Parameters.Count; i++)
                  {
                      var parameter = cmd.Parameters[i];
                      if (
                          !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength,
                              DbType.AnsiStringFixedLength)) continue;
                      if (parameter.Value == DBNull.Value)
                          continue;
                      var value = (string)parameter.Value;
                      if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0)
                      {
                          parameter.Size = 4096;
                          parameter.DbType = DbType.AnsiStringFixedLength;
                          value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query
                          value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                          parameter.Value = value;
                          cmd.CommandText = Regex.Replace(text,
                              string.Format(
                                  @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                              string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName));
                          if (text == cmd.CommandText)
                              throw new Exception("FTS was not replaced on: " + text);
                          text = cmd.CommandText;
                      }
                      else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0)
                      {
                          parameter.Size = 4096;
                          parameter.DbType = DbType.AnsiStringFixedLength;
                          value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query
                          value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                          parameter.Value = value;
                          cmd.CommandText = Regex.Replace(text,
                              string.Format(
                                  @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                              string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName));
                          if (text == cmd.CommandText)
                              throw new Exception("FTS was not replaced on: " + text);
                          text = cmd.CommandText;
                      }
                  }
              }
      
          }

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

      در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
      public class Note
          {
              /// <summary>
              /// 
              /// </summary>
              public int Id { get; set; }
      
              /// <summary>
              /// 
              /// </summary>
              public string NoteText { get; set; }
          }
      
        public class FtsSampleContext : DbContext
          {
              /// <summary>
              /// 
              /// </summary>
              public DbSet<Note> Notes { get; set; }
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="modelBuilder"></param>
              protected override void OnModelCreating(DbModelBuilder modelBuilder)
              {
                  modelBuilder.Configurations.Add(new NoteMap());
              }
          }
      
      /// <summary>
          /// 
          /// </summary>
          class Program
          {
              /// <summary>
              /// 
              /// </summary>
              /// <param name="args"></param>
              static void Main(string[] args)
              {
                  DbInterception.Add(new FtsInterceptor());
                  const string searchTerm = "john";
                  using (var db = new FtsSampleContext())
                  {
                      var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList();
                      //SQL Server Profiler result ===>>>
                      //exec sp_executesql N'SELECT 
                      //    [Extent1].[Id] AS [Id], 
                      //    [Extent1].[NoteText] AS [NoteText]
                      //    FROM [dbo].[Notes] AS [Extent1]
                      //    WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                      //char(4096)',@p__linq__0='(john)'
                      var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList();
                      //SQL Server Profiler result ===>>>
                      //exec sp_executesql N'SELECT 
                      //    [Extent1].[Id] AS [Id], 
                      //    [Extent1].[NoteText] AS [NoteText]
                      //    FROM [dbo].[Notes] AS [Extent1]
                      //    WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                      //char(4096)',@p__linq__0='(john)'
                  }
                  Console.ReadKey();
              }
          }

      ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
      تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.comقرار گرفته است.
      منبع:
      Full Text Search in Entity Framework 6

      ‫ایجاد ایندکس منحصربفرد بر روی چند فیلد با هم در EF Code first

      $
      0
      0
      در EF 6 امکان تعریف ساده‌تر ایندکس‌ها توسط data annotationsمیسر شده‌است. برای مثال:
      public abstract class BaseEntity
      {
          public int Id { get; set; }
      }
      
      public class User : BaseEntity
      {
         [Index(IsUnique = true)]
         public string EmailAddress { get; set; }
      }
      در اینجا توسط ویژگی Index، خاصیت آدرس ایمیل به صورت منحصربفرد تعریف شده‌است.

      سؤال:چگونه می‌توان شبیه به composite keys، اما نه دقیقا composite keys، بر روی چند فیلد با هم ایندکس منحصربفرد تعریف کرد؟
          public class UserRating : BaseEntity
          {
              public VoteSectionType SectionType { set; get; }
      
              public double RatingValue { get; set; }
      
              public int SectionId { get; set; }
      
              [ForeignKey("UserId")]
              public virtual User User { set; get; }
              public int UserId { set; get; }
          }
      در اینجا جدول رای‌های ثبت شده‌ی یک سیستم را مشاهده می‌کنید. می‌خواهیم یک کاربر نتواند بیش از یک رای به یک مطلب خاص بدهد. به عبارتی نیاز است بر روی SectionType (مطلب، اشتراک‌ها، دوره‌ها و ...)، SectionId (شماره مطلب) و UserId (شماره کاربر) یک کلید منحصربفرد ترکیبی تعریف کرد. ترکیب این سه مورد باید در کل جدول منحصربفرد باشند (Multiple column indexes).
      همچنین نمی‌خواهیم Composite key هم تعریف کنیم. می‌خواهیم Id و Primary key این جدول مانند قبل برقرار باشد.
      انجام چنین کاری در EF 6.1 به نحو ذیل میسر شده‌است:
          public class UserRating : BaseEntity
          {
              [Index("IX_Single_UserRating", IsUnique = true, Order = 1)] //کلید منحصربفرد ترکیبی روی سه ستون
              public VoteSectionType SectionType { set; get; }
      
              public double RatingValue { get; set; }
      
              [Index("IX_Single_UserRating", IsUnique = true, Order = 2)]
              public int SectionId { get; set; }
      
              [ForeignKey("UserId")]
              public virtual User User { set; get; }
      
              [Index("IX_Single_UserRating", IsUnique = true, Order = 3)]
              public int? UserId { set; get; }
          }
      نکته‌ی انجام اینکار، تعریف Indexها با یک نام یکسان صریحا مشخص شده‌است. در اینجا سه ایندکس تعریف شده‌اند؛ اما نام آن‌ها یکی است و مساوی IX_Single_UserRating قرار داده شده‌است. هر سه مورد نیز IsUnique تعریف شده‌اند و Order آن‌ها نیز باید مشخص گردد.
      خروجی SQL چنین تنظیمی به صورت زیر است:
       CREATE UNIQUE INDEX [IX_Single_UserRating]
      ON [UserRatings] ([SectionType] ASC,[SectionId] ASC,[UserId] ASC);
       

      ‫به روز رسانی ساده‌تر اجزاء ارتباطات در EF Code first به کمک GraphDiff

      $
      0
      0
      دو نوع حالت کلی کارکردن با EF وجود دارند: متصل و منقطع.
      در حالت متصل مانند برنامه‌های متداول دسکتاپ، Context مورد استفاده در طول عمر صفحه‌ی جاری زنده نگه داشته می‌شود. در این حالت اگر شیءایی اضافه شود، حذف شود یا تغییر کند، توسط EF ردیابی شده و تنها با فراخوانی متد SaveChanges، تمام این تغییرات به صورت یکجا به بانک اطلاعاتی اعمال می‌شوند.
      در حالت غیرمتصل مانند برنامه‌های وب، طول عمر Context در حد طول عمر یک درخواست است. پس از آن از بین خواهد رفت و دیگر فرصت ردیابی تغییرات سمت کاربر را نخواهد یافت. در این حالت به روز رسانی کلیه تغییرات انجام شده در خواص و همچنین ارتباطات اشیاء موجود، کاری مشکل و زمانبر خواهد بود.
      برای حل این مشکل، کتابخانه‌ای به نام GraphDiffطراحی شده‌است که صرفا با فراخوانی متد UpdateGraph آن، به صورت خودکار، محاسبات تغییرات صورت گرفته در اشیاء منقطع و اعمال آن‌ها به بانک اطلاعاتی صورت خواهد گرفت. البته ذکر متد SaveChanges پس از آن نباید فراموش شود.


      اصطلاحات بکار رفته در GraphDiff

      برای کار با GraphDiff نیاز است با یک سری اصطلاح آشنا بود:

      Aggregate root
      گرافی است از اشیاء به هم وابسته که مرجع تغییرات داده‌ها به شمار می‌رود. برای مثال یک سفارش و آیتم‌های آن‌را درنظر بگیرید. بارگذاری آیتم‌های سفارش، بدون سفارش معنایی ندارند. بنابراین در اینجا سفارش aggregate root است.

      AssociatedCollection/AssociatedEntity
      حالت‌های Associated به GraphDiff اعلام می‌کنند که اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root نباید به روز رسانی شوند. در این حالت تنها ارجاعات به روز رسانی خواهند شد.
      اگر خاصیت راهبری از نوع ICollection است، حالت AssociatedCollection و اگر صرفا یک شیء ساده است، از AssociatedEntity استفاده خواهد شد.

      OwnedCollection/OwnedEntity
      حالت‌های Owned به GraphDiff اعلام می‌کنند که جزئیات و همچنین ارجاعات اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root باید به روز رسانی شوند.


      دریافت و نصب GraphDiff

      برای نصب خودکار کتابخانه‌ی GraphDiff می‌توان از دستور نیوگت ذیل استفاده کرد:
       PM> Install-Package RefactorThis.GraphDiff


      بررسی GraphDiff در طی یک مثال

      مدل‌های برنامه آزمایشی، از سه کلاس ذیل که روابط many-to-many و one-to-many با یکدیگر دارند، تشکیل شده‌است:
      using System.Collections.Generic;
      using System.ComponentModel.DataAnnotations.Schema;
      
      namespace GraphDiffTests.Models
      {
          public class BlogPost
          {
              public int Id { get; set; }
              public string Title { get; set; }
              public string Content { get; set; }
      
              public virtual ICollection<Tag> Tags { set; get; } // many-to-many
      
              [ForeignKey("UserId")]
              public virtual User User { get; set; }
              public int UserId { get; set; }
      
              public BlogPost()
              {
                  Tags = new List<Tag>();
              }
          }
      
          public class Tag
          {
              public int Id { set; get; }
      
              [StringLength(maximumLength: 450), Required]
              public string Name { set; get; }
      
              public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many
      
              public Tag()
              {
                  BlogPosts = new List<BlogPost>();
              }
          }
      
          public class User
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public virtual ICollection<BlogPost> BlogPosts { get; set; } // one-to-many
          }
      }
      - یک مطلب می‌تواند چندین برچسب داشته باشد و هر برچسب می‌تواند به چندین مطلب انتساب داده شود.
      - هر کاربر می‌تواند چندین مطلب ارسال کند.

      در این حالت، Context برنامه چنین شکلی را خواهد یافت:
      using System;
      using System.Data.Entity;
      using GraphDiffTests.Models;
      
      namespace GraphDiffTests.Config
      {
          public class MyContext : DbContext
          {
              public DbSet<User> Users { get; set; }
              public DbSet<BlogPost> BlogPosts { get; set; }
              public DbSet<Tag> Tags { get; set; }
      
      
              public MyContext()
                  : base("Connection1")
              {
                  this.Database.Log = sql => Console.Write(sql);
              }
          }
      }
      به همراه تنظیمات به روز رسانی ساختار بانک اطلاعاتی به صورت خودکار:
      using System.Data.Entity.Migrations;
      using System.Linq;
      using GraphDiffTests.Models;
      
      namespace GraphDiffTests.Config
      {
          public class Configuration : DbMigrationsConfiguration<MyContext>
          {
              public Configuration()
              {
                  AutomaticMigrationsEnabled = true;
                  AutomaticMigrationDataLossAllowed = true;
              }
      
              protected override void Seed(MyContext context)
              {
                  if(context.Users.Any())
                      return;
      
                  var user1 = new User {Name = "User 1"};
                  context.Users.Add(user1);
      
                  var tag1 = new Tag { Name = "Tag1" };
                  context.Tags.Add(tag1);
      
                  var post1 = new BlogPost { Title = "Title...1", Content = "Content...1", User = user1};
                  context.BlogPosts.Add(post1);
      
                  post1.Tags.Add(tag1);
      
                  base.Seed(context);
              }
          }
      }
      در متد Seed آن یک سری اطلاعات ابتدایی ثبت شده‌اند؛ یک کاربر، یک برچسب و یک مطلب.




      در این تصاویر به Id هر کدام از رکوردها دقت کنید. از آن‌ها در ادامه استفاده خواهیم کرد.
      در اینجا نمونه‌ای از نحوه‌ی استفاده از GraphDiff را جهت به روز رسانی یک Aggregate root ملاحظه می‌کنید:
                  using (var context = new MyContext())
                  {
                      var user1 = new User { Id = 1, Name = "User 1_1_1" };
                      var post1 = new BlogPost { Id = 1, Title = "Title...1_1", Content = "Body...1_1",
                          User = user1, UserId = user1.Id };
                      var tags = new List<Tag>
                      {
                          new Tag {Id = 1, Name = "Tag1_1"},
                          new Tag {Id=12, Name = "Tag2_1"},
                          new Tag {Name = "Tag3"},
                          new Tag {Name = "Tag4"},
                      };
                      tags.ForEach(tag => post1.Tags.Add(tag));
      
                      context.UpdateGraph(post1, map => map
                          .OwnedEntity(p => p.User)
                          .OwnedCollection(p => p.Tags)
                          );
      
                      context.SaveChanges();
                  }
      پارامتر اول UpdateGraph، گرافی از اشیاء است که قرار است به روز رسانی شوند.
      پارامتر دوم آن، همان مباحث Owned و Associated بحث شده در ابتدای مطلب را مشخص می‌کنند. در اینجا چون می‌خواهیم هم برچسب‌ها و هم اطلاعات کاربر مطلب اول به روز شوند، نوع رابطه را Owned تعریف کرده‌ایم.
      در حین کار با متد UpdateGraph، ذکر Idهای اشیاء منقطع از Context بسیار مهم هستند. اگر دستورات فوق را اجرا کنیم به خروجی ذیل خواهیم رسید:




      - همانطور که مشخص است، چون id کاربر ذکر شده و همچنین این Id در post1 نیز درج گردیده است، صرفا نام او ویرایش گردیده است. اگر یکی از موارد ذکر شده رعایت نشوند، ابتدا کاربر جدیدی ثبت شده و سپس رابطه‌ی مطلب و کاربر به روز رسانی خواهد شد (userId آن به userId آخرین کاربر ثبت شده تنظیم می‌شود).
      - در حین ثبت برچسب‌ها، چون Id=1 از پیش در بانک اطلاعاتی موجود بوده، تنها نام آن ویرایش شده‌است. در سایر موارد، برچسب‌های تعریف شده صرفا اضافه شده‌اند (چون Id مشخصی ندارند یا Id=12 در بانک اطلاعاتی وجود خارجی ندارد).
      - چون Id مطلب مشخص شده‌است، فیلدهای عنوان و محتوای آن نیز به صورت خودکار ویرایش شده‌اند.

      و ... تمام این کارها صرفا با فراخوانی متدهای UpdateGraph و سپس SaveChanges رخ داده‌است.


      کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
      GraphDiffTests.zip

      ‫تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET

      $
      0
      0
      پس از بررسی مقدماتیامکانات کتابخانه‌ی JSON.NET، در ادامه به تعدادی از تنظیمات کاربردی آن با ذکر مثال‌هایی خواهیم پرداخت.


      گرفتن خروجی CamelCase از JSON.NET

      یک سری از کتابخانه‌های جاوا اسکریپتی سمت کلاینت، به نام‌های خواص CamelCaseنیاز دارند و حالت پیش فرض اصول نامگذاری خواص در دات نت عکس آن است. برای مثال بجای UserName به userName نیاز دارند تا بتوانند صحیح کار کنند.
      روش اول حل این مشکل، استفاده از ویژگی JsonProperty بر روی تک تک خواص و مشخص کردن نام‌های مورد نیاز کتابخانه‌ی جاوا اسکریپتی به صورت صریح است.
      روش دوم، استفاده از تنظیمات ContractResolver می‌باشد که با تنظیم آن به CamelCasePropertyNamesContractResolver به صورت خودکار به تمامی خواص به صورت یکسانی اعمال می‌گردد:
      var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings
      {
         ContractResolver = new CamelCasePropertyNamesContractResolver()
      });


      درج نام‌های المان‌های یک Enum در خروجی JSON

      اگر یکی از عناصر در حال تبدیل به JSON، از نوع enum باشد، به صورت پیش فرض مقدار عددی آن در JSON نهایی درج می‌گردد:
      using Newtonsoft.Json;
      
      namespace JsonNetTests
      {
          public enum Color
          {
              Red,
              Green,
              Blue,
              White
          }
      
          public class Item
          {
              public string Name { set; get; }
              public Color Color { set; get; }
          }
      
          public class EnumTests
          {
              public string GetJson()
              {
                  var item = new Item
                  {
                      Name = "Item 1",
                      Color = Color.Blue 
                  };
      
                  return JsonConvert.SerializeObject(item, Formatting.Indented);
              }
          }
      }
      با این خروجی:
      {
        "Name": "Item 1",
        "Color": 2
      }
      اگر علاقمند هستید که بجای عدد 2، دقیقا مقدار Blue در خروجی JSON درج گردد، می‌توان به یکی از دو روش ذیل عمل کرد:
      الف) مزین کردن خاصیت از نوع enum به ویژگی JsonConverter از نوع StringEnumConverter:
        [JsonConverter(typeof(StringEnumConverter))]
        public Color Color { set; get; }
      ب) و یا اگر می‌خواهید این تنظیم به تمام خواص از نوع enum به صورت یکسانی اعمال شود، می‌توان نوشت:
      return JsonConvert.SerializeObject(item, new JsonSerializerSettings
      {
         Formatting = Formatting.Indented,
         Converters = { new StringEnumConverter() }
      });


      تهیه خروجی JSON از مدل‌های مرتبط، بدون Stack overflow

      دو کلاس گروه‌های محصولات و محصولات ذیل را درنظر بگیرید:
         public class Category
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public virtual ICollection<Product> Products { get; set; }
      
              public Category()
              {
                  Products = new List<Product>();
              }
          }
      
          public class Product
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public virtual Category Category { get; set; }
          }
      این نوع طراحی در Entity framework بسیار مرسوم است. در اینجا طرف‌های دیگر یک رابطه، توسط خاصیتی virtual معرفی می‌شوند که به آن‌ها خواص راهبری یا navigation properties هم می‌گویند.
      با توجه به این دو کلاس، سعی کنید مثال ذیل را اجرا کرده و از آن، خروجی JSON تهیه کنید:
      using System.Collections.Generic;
      using Newtonsoft.Json;
      using Newtonsoft.Json.Converters;
      
      namespace JsonNetTests
      {
          public class SelfReferencingLoops
          {
              public string GetJson()
              {
                  var category = new Category
                  {
                      Id = 1,
                      Name = "Category 1"
                  };
                  var product = new Product
                  {
                      Id = 1,
                      Name = "Product 1"
                  };
      
                  category.Products.Add(product);
                  product.Category = category;
      
                  return JsonConvert.SerializeObject(category, new JsonSerializerSettings
                  {
                      Formatting = Formatting.Indented,
                      Converters = { new StringEnumConverter() }
                  });
              }
          }
      }
      برنامه با این استثناء متوقف می‌شود:
       An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
      Additional information: Self referencing loop detected for property 'Category' with type 'JsonNetTests.Category'. Path 'Products[0]'.
      اصل خطای معروف فوق «Self referencing loop detected» است. در اینجا کلاس‌هایی که به یکدیگر ارجاع می‌دهند، در حین عملیات Serialization سبب بروز یک حلقه‌ی بازگشتی بی‌نهایت شده و در آخر، برنامه با خطای stack overflow خاتمه می‌یابد.

      راه حل اول:
      به تنظیمات JSON.NET، مقدار ReferenceLoopHandling = ReferenceLoopHandling.Ignore را اضافه کنید تا از حلقه‌ی بازگشتی بی‌پایان جلوگیری شود:
      return JsonConvert.SerializeObject(category, new JsonSerializerSettings
      {
         Formatting = Formatting.Indented,
         ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
         Converters = { new StringEnumConverter() }
      });
      راه حل دوم:
      به تنظیمات JSON.NET، مقدار PreserveReferencesHandling = PreserveReferencesHandling.Objects را اضافه کنید تا مدیریت ارجاعات اشیاء توسط خود JSON.NET انجام شود:
      return JsonConvert.SerializeObject(category, new JsonSerializerSettings
      {
         Formatting = Formatting.Indented,
         PreserveReferencesHandling = PreserveReferencesHandling.Objects,
         Converters = { new StringEnumConverter() }
      });
      خروجی حالت دوم به این شکل است:
      {
        "$id": "1",
        "Id": 1,
        "Name": "Category 1",
        "Products": [
          {
            "$id": "2",
            "Id": 1,
            "Name": "Product 1",
            "Category": {
              "$ref": "1"
            }
          }
        ]
      }
      همانطور که ملاحظه می‌کنید، دو خاصیت $id و $ref توسط JSON.NET به خروجی JSON اضافه شده‌است تا توسط آن بتواند ارجاعات و نمونه‌های اشیاء را تشخیص دهد.

      ‫کدام سلسله متدها، متد جاری را فراخوانی کرده‌اند؟

      $
      0
      0
      یکی از نیازهای نوشتن یک برنامه‌ی پروفایلر، نمایش اطلاعات متدهایی است که سبب لاگ شدن اطلاعاتی شده‌اند. برای مثال در طراحی interceptorهای EF 6 به یک چنین متدهایی می‌رسیم:
              public void ScalarExecuted(DbCommand command,
                                         DbCommandInterceptionContext<object> interceptionContext)
              {
              }

      سؤال: در زمان اجرای ScalarExecuted دقیقا در کجا قرار داریم؟ چه متدی در برنامه، در کدام کلاس، سبب رسیدن به این نقطه شده‌است؟
      تمام این اطلاعات را در زمان اجرا توسط کلاس StackTrace می‌توان بدست آورد:
              public static string GetCallingMethodInfo()
              {
                  var stackTrace = new StackTrace(true);
                  var frameCount = stackTrace.FrameCount;
      
                  var info = new StringBuilder();
                  var prefix = "-- ";
                  for (var i = frameCount - 1; i >= 0; i--)
                  {
                      var frame = stackTrace.GetFrame(i);
                      var methodInfo = getStackFrameInfo(frame);
                      if (string.IsNullOrWhiteSpace(methodInfo))
                          continue;
      
                      info.AppendLine(prefix + methodInfo);
                      prefix = "-" + prefix;
                  }
      
                  return info.ToString();
              }
      ایجاد یک نمونه جدید از کلاس StackTrace با پارامتر true به این معنا است که می‌خواهیم اطلاعات فایل‌های متناظر را نیز در صورت وجود دریافت کنیم.
      خاصیت stackTrace.FrameCount مشخص می‌کند که در زمان فراخوانی متد GetCallingMethodInfo که اکنون برای مثال درون متد ScalarExecuted قرار گرفته‌است، از چند سطح بالاتر این فراخوانی صورت گرفته‌است. سپس با استفاده از متد stackTrace.GetFrame می‌توان به اطلاعات هر سطح دسترسی یافت.
      در هر StackFrame دریافتی، با فراخوانی stackFrame.GetMethod می‌توان نام متد فراخوان را بدست آورد. متد stackFrame.GetFileLineNumber دقیقا شماره سطری را که فراخوانی از آن صورت گرفته، بازگشت می‌دهد و stackFrame.GetFileName نیز نام فایل مرتبط را مشخص می‌کند.

      یک نکته:
      شرط عمل کردن متدهای stackFrame.GetFileName و stackFrame.GetFileLineNumber در زمان اجرا، وجود فایل PDB اسمبلی در حال بررسی است. بدون آن اطلاعات محل قرارگیری فایل سورس مرتبط و شماره سطر فراخوان، قابل دریافت نخواهند بود.


      اکنون بر اساس این اطلاعات، متد getStackFrameInfo چنین پیاده سازی را خواهد داشت:
              private static string getStackFrameInfo(StackFrame stackFrame)
              {
                  if (stackFrame == null)
                      return string.Empty;
      
                  var method = stackFrame.GetMethod();
                  if (method == null)
                      return string.Empty;
      
                  if (isFromCurrentAsm(method) || isMicrosoftType(method))
                  {
                      return string.Empty;
                  }
      
                  var methodSignature = method.ToString();
                  var lineNumber = stackFrame.GetFileLineNumber();
                  var filePath = stackFrame.GetFileName();
      
                  var fileLine = string.Empty;
                  if (!string.IsNullOrEmpty(filePath))
                  {
                      var fileName = Path.GetFileName(filePath);
                      fileLine = string.Format("[File={0}, Line={1}]", fileName, lineNumber);
                  }
      
                  var methodSignatureFull = string.Format("{0} {1}", methodSignature, fileLine);
                  return methodSignatureFull;
              }
      و خروجی آن برای مثال چنین شکلی را خواهد داشت:
       Void Main(System.String[]) [File=Program.cs, Line=28]
      که وجود file و line آن تنها به دلیل وجود فایل PDB اسمبلی مورد بررسی است.

      در اینجا خروجی نهایی متد GetCallingMethodInfo به شکل زیر است که در آن چند سطح فراخوانی را می‌توان مشاهده کرد:
       -- Void Main(System.String[]) [File=Program.cs, Line=28]
      --- Void disposedContext() [File=Program.cs, Line=76]
      ---- Void Opened(System.Data.Common.DbConnection, System.Data.Entity.Infrastructure.Interception.DbConnectionInterceptionContext) [File=DatabaseInterceptor.cs,Line=157]

      جهت تعدیل خروجی متد GetCallingMethodInfo، عموما نیاز است مثلا از کلاس یا اسمبلی جاری صرفنظر کرد یا اسمبلی‌های مایکروسافت نیز در این بین شاید اهمیتی نداشته باشند و بیشتر هدف بررسی سورس‌های موجود است تا فراخوانی‌های داخلی یک اسمبلی ثالث:
              private static bool isFromCurrentAsm(MethodBase method)
              {
                  return method.ReflectedType == typeof(CallingMethod);
              }
      
              private static bool isMicrosoftType(MethodBase method)
              {
                  if (method.ReflectedType == null)
                      return false;
      
                  return method.ReflectedType.FullName.StartsWith("System.") ||
                         method.ReflectedType.FullName.StartsWith("Microsoft.");
              }


      کد کامل CallingMethod.cs را از اینجا می‌توانید دریافت کنید:
      CallingMethod.cs

      ‫یافتن Contextهای Dispose نشده در Entity framework

      $
      0
      0
      این دو متد را در نظر بگیرید:
              private static void disposedContext()
              {
                  using (var context = new MyContext())
                  {
                      Debug.WriteLine("Posts count: " + context.BlogPosts.Count());
                  }
              }
      
              private static void nonDisposedContext()
              {
                  var context = new MyContext();
                  Debug.WriteLine("Posts count: " + context.BlogPosts.Count());
              }
      در اولی با استفاده از using، شیء context به صورت خودکار dispose خواهد شد؛ اما در دومی از using استفاده نشده‌است.

      سؤال: در یک برنامه‌ی بزرگ چطور می‌توان لیست Contextهای Dispose نشده را یافت؟

      در EF 6 با تعریف یک IDbConnectionInterceptor سفارشی می‌توان به متدهای باز، بسته و dispose شدن یک Connection دسترسی یافت. اگر Context ایی dispose نشده باشد، اتصال آن نیز dispose نخواهد شد.
      using System.Data;
      using System.Data.Common;
      using System.Data.Entity.Infrastructure.Interception;
      
      namespace EFNonDisposedContext.Core
      {
          public class DatabaseInterceptor : IDbConnectionInterceptor
          {
              public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
              {
                  Connections.AddOrUpdate(connection, ConnectionStatus.Closed);
              }
      
              public void Disposed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
              {
                  Connections.AddOrUpdate(connection, ConnectionStatus.Disposed);
              }
      
              public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
              {
                  Connections.AddOrUpdate(connection, ConnectionStatus.Opened);
              }
            
              // the rest of the IDbConnectionInterceptor methods ...
      
          }
      }
      همانطور که ملاحظه می‌کنید، با پیاده سازی IDbConnectionInterceptor، به سه متد Closed، Opened و Disposed یک DbConnection می‌توان دسترسی یافت.

      مشکل مهم! در زمان فراخوانی متد Disposed، دقیقا کدام DbConnection باز شده، رها شده‌است؟
      پاسخ به این سؤال را در مطلب «ایجاد خواص الحاقی» می‌توانید مطالعه کنید. با استفاده از یک ConditionalWeakTable به هر کدام از اشیاء DbConnection یک Id را انتساب خواهیم داد و پس از آن به سادگی می‌توان وضعیت این Id را ردگیری کرد.
      برای این منظور، لیستی از ConnectionInfo را تشکیل خواهیم داد:
          public enum ConnectionStatus
          {
              None,
              Opened,
              Closed,
              Disposed
          }
      
          public class ConnectionInfo
          {
              public string ConnectionId { set; get; }
              public string StackTrace { set; get; }
              public ConnectionStatus Status { set; get; }
      
              public override string ToString()
              {
                  return string.Format("{0}:{1} [{2}]",ConnectionId, Status, StackTrace);
              }
          }
      در اینجا ConnectionId را به کمک ConditionalWeakTable محاسبه می‌کنیم.
      StackTrace توسط نکته‌ی مطلب «کدام سلسله متدها، متد جاری را فراخوانی کرده‌اند؟» تهیه می‌شود.
      Status نیز وضعیت جاری اتصال است که بر اساس متدهای فراخوانی شده در پیاده سازی IDbConnectionInterceptor مشخص می‌گردد.

      در پایان کار برنامه فقط باید یک گزارش تهیه کنیم از لیست ConnectionInfoهایی که Status آن‌ها مساوی Disposed نیست. این موارد با توجه به مشخص بودن Stack trace هر کدام، دقیقا محل متدی را که در آن context مورد استفاده dispose نشده‌است، مشخص می‌کنند.


      کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
      EFNonDisposedContext.zip
       

      ‫فعال‌سازی Multiple Active Result Sets

      $
      0
      0
      (Multiple Active Result Sets (MARS یکی از قابلیتهای SQL SERVER است. این قابلیت در واقع این امکان را برای ما فراهم می‌کند تا بر روی یک Connection همزمان چندین کوئری را به صورت موازی ارسال کنیم. در این حالت برای هر کوئری یک سشن مجزا در نظر گرفته می‌شود. 
      مدل:
      namespace EnablingMARS.Models
      {
          public class Product
          {
              public int Id { get; set; }
              public string Title { get; set; }
              public string Desc { get; set; }
              public float Price { get; set; }
              public Category Category { get; set; }
      
          }
      
          public enum Category
          {
              Cate1,
              Cate2,
              Cate3
          }
      }
      کلاس Context:
      namespace EnablingMARS.Models
      {
          public class ProductDbContext : DbContext
          {
              public ProductDbContext() : base("EnablingMARS") {}
              public DbSet<Product> Products { get; set; }
      
          }
      }
      ابتدا یک سطر جدید را توسط کد زیر به دیتابیس اضافه می‌کنیم:
      MyContext.Products.Add(new Product()
       {
                      Title = "title1",
                      Desc = "desc",
                      Price = 4500f,
                      Category = Category.Cate1
         });
      MyContext.SaveChanges();
      اکنون می‌خواهیم قیمت محصولاتی را که در دسته‌بندی Cate1 قرار دارند، تغییر دهیم:
      foreach (var product in _dvContext.Products.Where(category => category.Category == Category.Cate1))
      {
           product.Price = 50000;
           MyContext.SaveChanges();
      }
      خوب؛ اکنون اگر برنامه را اجرا کنیم با خطای زیر مواجه می‌شویم:
      There is already an open DataReader associated with this Command which must be closed first.
      این استثناء زمانی اتفاق می‌افتد که بر روی نتایج حاصل از یک کوئری، یک کوئری دیگر را ارسال کنیم. البته استثنای صادر شده بستگی به کوئری دوم شما دارد ولی در حالت کلی و با مشاهده Stack Trace، پیام فوق نمایش داده می‌شود. همانطور که در کد بالا ملاحظه می‌کنید درون حلقه‌ی forach ما به پراپرتی Price دسترسی پیدا کرده‌ایم، در حالیکه کوئری اصلی ما هنوز فعال (Active) است. MARS در اینجا به ما کمک می‌کند که بر روی یک Connection، بیشتر از یک کوئری فعال داشته باشیم. در حالت عادی Entity Framework Code First این ویژگی را به صورت پیش‌فرض برای ما فعال نمی‌کند. اما اگر خودمان کانکشن‌استرینگ را اصلاح کنیم، این ویژگی SQL SERVER فعال می‌گردد. برای حل این مشکل کافی است به کانکشن‌استرینگ، MultipleActiveResultSets=true را اضافه کنیم:
      "Data Source=(LocalDB)\v11.0;Initial Catalog=EnablingMARS; MultipleActiveResultSets=true"
      لازم به ذکر است که این قابلیت از نسخه SQL SERVER 2005 به بالا در دسترس می‌باشد. همچنین در هنگام استفاده از این قابلیت می‌بایستی موارد زیر را در نظر داشته باشید:
      • وقتی کانکشنی در حالت MARS برقرار می‌شود، یک سشن نیز همراه با یکسری اطلاعات اضافی برای آن ایجاد شده که باعث ایجاد Overhead خواهد شد.
      • دستورات مارس thread-safe نیستند.
      Viewing all 112 articles
      Browse latest View live




      Latest Images