مطالب
یکسان سازی "ی" و "ک" دریافتی در حین استفاده از Entity framework

در مورد یکسان سازی ی و ک در حین استفاده از WCF RIA Services پیشتر مطلبی را در این سایت خوانده بودید. جهت تکمیل این بحث، بسط این روش به Entity framework به صورت زیر خواهد بود:

using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Reflection;

namespace EfExt
{
public static class CorrectYeKe
{
public static void ApplyCorrectYeKe(this ObjectContext ctx)
{
if (ctx == null)
return;

//پیدا کردن موجودیت‌های تغییر کرده
var changedEntities = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified
);

foreach (var entity in changedEntities)
{
if (entity.Entity == null) continue;

//یافتن خواص قابل تنظیم و رشته‌ای این موجودیت‌ها
var propertyInfos = entity.Entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(string));

var pr = new PropertyReflector();

//اعمال یکپارچگی نهایی
foreach (var propertyInfo in propertyInfos)
{
var propName = propertyInfo.Name;
var val = pr.GetValue(entity.Entity, propName);
if (val != null)
{
pr.SetValue(
entity.Entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}
}

ابتدا موجودیت‌های تغییر کرده یافت خواهند شد (اگر از self tracking entities استفاده می‌کنید استفاده از Context.DetectChanges پیش از فراخوانی این متد ضروری خواهد بود)، سپس در این لیست در مورد تک تک اشیاء، خواص رشته‌ای که readonly نیستند یافت شده و ی و ک آن‌ها یک دست می‌شوند.
محل اعمال آن هم باید پیش از فراخوانی Context.SaveChanges باشد.

سورس این کتابخانه را از اینجا می‌توانید دریافت کنید.

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

1) دریافت کتابخانه‌های لازم
نیاز به کتابخانه‌های Lucene.NET و همچنین Lucene.Net Contrib است که هر دو مورد را به سادگی توسط NuGet می‌توانید دریافت و نصب کنید.
Highlighter استفاده شده، در کتابخانه Lucene.Net Contrib قرار دارد. به همین جهت این مورد را نیز باید جداگانه دریافت کرد.


2) تهیه منبع داده
در اینجا جهت سادگی کار فرض کنید که لیستی از مطالب را به فرمت زیر دراختیار داریم:
public class Post
{
    public int Id { set; get; }
    public string Title { set; get; }
    public string Body { set; get; }
}
تفاوتی نمی‌کند که از چه منبع داده‌ای استفاده می‌کنید. آیا قرار است یک سری فایل متنی ساده موجود در یک پوشه را ایندکس کنید یا تعدادی رکورد بانک اطلاعاتی؛ از NHibernate استفاده می‌کنید یا از Entity framework و یا از ADO.NET. کتابخانه Lucene مستقل است از منبع داده مورد استفاده و تنها اطلاعاتی با فرمت شیء Document معرفی شده به آن‌را می‌شناسد.


3) تبدیل اطلاعات به فرمت Lucene.NET
همانطور که عنوان شد نیاز است هر رکورد از اطلاعات خود را به شیء Document نگاشت کنیم. نمونه‌ای از اینکار را در متد ذیل مشاهده می‌نمائید:
static Document MapPostToDocument(Post post)
{
    var postDocument = new Document();
    postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
    postDocument.Add(new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
    postDocument.Add(new Field("Body", post.Body, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
    return postDocument;
}
این متد وهله‌ای از شیء Post را دریافت کرده و آن‌را تبدیل به یک سند Lucene می‌کند.
کار با ایجاد یک وهله از شیء Document شروع شده و سپس اطلاعات به صوت فیلدهایی به این سند اضافه می‌شوند.

توضیحات آرگومان‌های مختلف سازنده کلاس Field:
- در ابتدا نام فیلد مورد نظر ذکر می‌گردد.
- سپس مقدار متناظر با آن فیلد، به صورت رشته باید معرفی شود.
- آرگومان سوم آن مشخص می‌کند که اصل اطلاعات نیز علاوه بر ایندکس شدن باید در فایل‌های Lucene ذخیره شوند یا خیر. توسط Field.Store.YES مشخص می‌کنیم که بله؛ علاقمندیم تا اصل اطلاعات نیز از طریق Lucene قابل بازیابی باشند. این مورد جهت نمایش سریع نتایج جستجوها می‌تواند مفید باشد. اگر قرار نیست اطلاعاتی را از این فیلد خاص به کاربر نمایش دهید می‌توانید از گزینه Field.Store.NO استفاده کنید. همچنین امکان فشرده سازی اطلاعات ذخیره شده با انتخاب گزینه Field.Store.COMPRESS نیز میسر است.
- توسط آرگومان چهارم آن تعیین خواهیم کرد که اطلاعات فیلد مورد نظر ایندکس شوند یا خیر. مقدار Field.Index.NOT_ANALYZED سبب عدم ایندکس شدن فیلد Id می‌شوند (چون قرار نیست روی id در قسمت جستجوی عمومی سایت، جستجویی صورت گیرد). به کمک مقدار Field.Index.ANALYZED، مقدار معرفی شده، ایندکس خواهد شد.
- پارامتر پنجم آن‌را جهت سرعت عمل در نمایان سازی/برجسته کردن و highlighting عبارات جستجو شده در متن‌های یافت شده معرفی کرده‌ایم. الگوریتم‌های متناظر با این روش در فایل‌های Lucene.Net Contrib قرار دارند.


یک نکته
اگر اطلاعاتی را که قرار است ایندکس کنید از نوع HTML می‌باشند، بهتر است تمام تگ‌های آن‌را پیش از افزودن به لوسین حذف کنید. به این ترتیب نتایج جستجوی دقیق‌تری را می‌توان شاهد بود. برای این منظور می‌توان از متد ذیل کمک گرفت:
public static string RemoveHtmlTags(string text)
{
    return string.IsNullOrEmpty(text) ? string.Empty : Regex.Replace(text, @"<(.|\n)*?>", string.Empty);
}


4) تهیه Full text index به کمک Lucene.NET
تا اینجا توانستیم اطلاعات خود را به فرمت اسناد لوسین تبدیل کنیم. اکنون ثبت و تبدیل آن‌ها به فایل‌های Full text search لوسین به سادگی زیر است:
static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_29;
public static void CreateIdx(IEnumerable<Post> dataList)
{
    var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
    var analyzer = new StandardAnalyzer(_version);
    using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
    {
         foreach (var post in dataList)
        {
            writer.AddDocument(MapPostToDocument(post));
        }

        writer.Optimize();
        writer.Commit();
        writer.Close();
        directory.Close();
    }
}
ابتدا محل ذخیره سازی فایل‌های full text search مشخص می‌شوند. سپس آنالیز کننده اطلاعات باید معرفی شود. در ادامه به کمک این اطلاعات، شیء IndexWriter ایجاد و مستندات لوسین به آن اضافه می‌شوند. در آخر، این اطلاعات بهینه سازی شده و ثبت نهایی صورت خواهد گرفت.
ذکر version در اینجا ضروری است؛ از این جهت که اگر ایندکسی با فرمت مثلا LUCENE_29 تهیه شود ممکن است با نگارش بعدی این کتابخانه سازگار نباشد و در صورت ارتقاء، نتایج جستجوی انجام شده، کاملا بی‌ربط نمایش داده شوند. با ذکر صریح نگارش، دیگر این اتفاق رخ نخواهد داد.


نکته
StandardAnalyzer توکار لوسین، امکان دریافت لیستی از واژه‌هایی که نباید ایندکس شوند را نیز دارا است. اطلاعات بیشتر در اینجا.


5) به روز رسانی ایندکس‌ها
به کمک سه متد ذیل می‌توان اطلاعات ایندکس‌های موجود را به روز یا حذف کرد:
public static void UpdateIndex(Post post)
{
        var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
        var analyzer = new StandardAnalyzer(_version);
        using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
        {
            var newDoc = MapPostToDocument(post);

             indexWriter.UpdateDocument(new Term("Id", post.Id.ToString()), newDoc);
             indexWriter.Commit();
             indexWriter.Close();
             directory.Close();
         }
}

public static void DeleteIndex(Post post)
{
         var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
         var analyzer = new StandardAnalyzer(_version);
         using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
         {
             indexWriter.DeleteDocuments(new Term("Id", post.Id.ToString()));
             indexWriter.Commit();
             indexWriter.Close();
             directory.Close();
          }
}

public static void AddIndex(Post post)
{
      var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
      var analyzer = new StandardAnalyzer(_version, getStopWords());
      using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
      {
           var searchQuery = new TermQuery(new Term("Id", post.Id.ToString()));
           indexWriter.DeleteDocuments(searchQuery);

            var newDoc = MapPostToDocument(post);
            indexWriter.AddDocument(newDoc);
            indexWriter.Commit();
            indexWriter.Close();
            directory.Close();
        }
}
تنها نکته مهم این متدها، استفاده از متد IndexWriter با پارامتر create مساوی false است. به این ترتیب فایل‌های موجود بجای از نو ساخته شدن، به روز خواهند شد.
محل فراخوانی این متدها هم می‌تواند در کنار متدهای به روز رسانی اطلاعات اصلی در بانک اطلاعاتی برنامه باشند. اگر رکوردی اضافه یا حذف شده، ایندکس متناظر نیز باید به روز شود.


6) جستجو در اطلاعات ایندکس شده و نمایش آن‌ها به همراه نمایان/برجسته سازی عبارات جستجو شده
قسمت نهایی کار با لوسین و اطلاعات ایندکس‌های تهیه شده، کوئری گرفتن از آن‌ها است. متدهای کامل مورد نیاز را در ذیل مشاهده می‌کنید:

public static void Query(string term)
{
     var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
     using (var searcher = new IndexSearcher(directory, readOnly: true))
     {
          var analyzer = new StandardAnalyzer(_version);
          var parser = new MultiFieldQueryParser(_version, new[] { "Body", "Title" }, analyzer);
          var query = parseQuery(term, parser);
          var hits = searcher.Search(query, 10).ScoreDocs;

          if (hits.Length == 0)
          {
               term = searchByPartialWords(term);
               query = parseQuery(term, parser);
               hits = searcher.Search(query, 10).ScoreDocs;
           }

           FastVectorHighlighter fvHighlighter = new FastVectorHighlighter(true, true);
           foreach (var scoreDoc in hits)
           {
               var doc = searcher.Doc(scoreDoc.doc);
               string bestfragment = fvHighlighter.GetBestFragment(
                                fvHighlighter.GetFieldQuery(query),
                                searcher.GetIndexReader(),
                                docId: scoreDoc.doc,
                                fieldName: "Body",
                                fragCharSize: 400);
                var id = doc.Get("Id");
                var title = doc.Get("Title");
                var score = scoreDoc.score;
                Console.WriteLine(bestfragment);
            }

            searcher.Close();
            directory.Close();
      }
   }

   private static Query parseQuery(string searchQuery, QueryParser parser)
   {
       Query query;
        try
        {
            query = parser.Parse(searchQuery.Trim());
        }
        catch (ParseException)
        {
            query = parser.Parse(QueryParser.Escape(searchQuery.Trim()));
        }
        return query;
   }

   private static string searchByPartialWords(string bodyTerm)
   {
       bodyTerm = bodyTerm.Replace("*", "").Replace("?", "");
       var terms = bodyTerm.Trim().Replace("-", " ").Split(' ')
                                .Where(x => !string.IsNullOrEmpty(x))
                                .Select(x => x.Trim() + "*");
       bodyTerm = string.Join(" ", terms);
       return bodyTerm;
   }
توضیحات:
اکثر سایت‌ها را که بررسی کنید، جستجوی بر روی یک فیلد را توضیح داده‌اند. در اینجا نحوه جستجو بر روی چند فیلد را به کمک MultiFieldQueryParser ملاحظه می‌کنید.
نکته‌ی مهمی را هم که در اینجا باید به آن دقت داشت، حساس بودن لوسین به کوچکی و بزرگی نام فیلدهای معرفی شده است و در صورت عدم رعایت این مساله، جستجوی شما نتیجه‌ای را دربر نخواهد داشت.
در ادامه برای parse اطلاعات، از متد کمکی parseQuery استفاده شده است. ممکن است به ParseException بخاطر یک سری حروف خاص بکارگرفته شده در عبارات مورد جستجو برسیم. در اینجا می‌توان توسط متد QueryParser.Escape، اطلاعات دریافتی را اصلاح کرد.
سپس نحوه استفاده از کوئری تهیه شده و متد Search را ملاحظه می‌کنید. در اینجا بهتر است تعداد رکوردهای بازگشت داده شده را تعیین کرد (به کمک آرگومان دوم متد جستجو) تا بی‌جهت سرعت عملیات را پایین نیاورده و همچنین مصرف حافظه سیستم را نیز بالا نبریم.
ممکن است تعداد hits یا نتایج حاصل صفر باشد؛ بنابراین بد نیست خودمان دست به کار شده و به کمک متد searchByPartialWords، ورودی کاربر را بر اساس زبان جستجوی ویژه لوسین اندکی بهینه کنیم تا بتوان به نتایج بهتری دست یافت.
در آخر نحوه کار با  ScoreDocs یافت شده را ملاحظه می‌کنید. اگر محتوای فیلد را در حین ایندکس سازی ذخیره کرده باشیم، به کمک متد doc.Get می‌توان به اطلاعات کامل آن نیز دست یافت.
همچنین نکته دیگری را که در اینجا می‌توان ملاحظه کرد استفاده از FastVectorHighlighter می‌باشد. به کمک این Highlighter ویژه می‌توان نتایج جستجو را شبیه به نتایج نمایش داده شده توسط موتور جستجوی گوگل درآورد. برای مثال اگر شخصی ef code first را جستجو کرد، توسط متد GetBestFragment، بهترین جزئی که شامل بیشترین تعداد حروف جستجو شده است، یافت گردیده و همچنین به کمک تگ‌های B، ضخیم نمایش داده خواهند شد.

 
مطالب
مروری بر کدهای کلاس SqlHelper

قسمتی از یک پروژه به همراه کلاس SqlHelper آن در کامنت‌های مطلب «اهمیت Code review» توسط یکی از خوانندگان بلاگ جهت Code review مطرح شده که بهتر است در یک مطلب جدید و مجزا به آن پرداخته شود. قسمت مهم آن کلاس SqlHelper است و مابقی در اینجا ندید گرفته می‌شوند:

//It's only for code review purpose!  
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;


public sealed class SqlHelper
{
private SqlHelper() { }


// Send Connection String
//---------------------------------------------------------------------------------------
public static string GetCntString()
{
return WebConfigurationManager.ConnectionStrings["db_ConnectionString"].ConnectionString;
}


// Connect to Data Base SqlServer
//---------------------------------------------------------------------------------------
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)
{
try
{
if (sqlCnt == null) sqlCnt = new SqlConnection();
sqlCnt.ConnectionString = cntString;
if (sqlCnt.State != ConnectionState.Open) sqlCnt.Open();
return sqlCnt;
}
catch (SqlException)
{
return null;
}
}


// Run ExecuteScalar Command
//---------------------------------------------------------------------------------------
public static string RunExecuteScalarCmd(ref SqlConnection sqlCnt, string strCmd, bool blnClose)
{
Connect2Db(ref sqlCnt, GetCntString());
using (sqlCnt)
{
using(SqlCommand sqlCmd = sqlCnt.CreateCommand())
{
sqlCmd.CommandText = strCmd;
object objResult = sqlCmd.ExecuteScalar();
if (blnClose) CloseCnt(ref sqlCnt, true);
return (objResult == null) ? string.Empty : objResult.ToString();
}
}
}

// Close SqlServer Connection
//---------------------------------------------------------------------------------------
public static bool CloseCnt(ref SqlConnection sqlCnt, bool nullSqlCnt)
{
try
{
if (sqlCnt == null) return true;
if (sqlCnt.State == ConnectionState.Open)
{
sqlCnt.Close();
sqlCnt.Dispose();
}
if (nullSqlCnt) sqlCnt = null;
return true;
}
catch (SqlException)
{
return false;
}
}
}


مثالی از نحوه استفاده ارائه شده:

protected void BtnTest_Click(object sender, EventArgs e)
{
SqlConnection sqlCnt = new SqlConnection();
string strQuery = "SELECT COUNT(UnitPrice) AS PriceCount FROM [Order Details]";


// در این مرحله پارامتر سوم یعنی کانکشن باز نگه داشته شود
string strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, false);



strQuery = "SELECT LastName + N'-' + FirstName AS FullName FROM Employees WHERE (EmployeeID = 9)";
// در این مرحله پارامتر سوم یعنی کانکشن بسته شود
strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, true);
}


مروری بر این کد:

1) نحوه کامنت نوشتن
بین سی شارپ و زبان سی++ تفاوت وجود دارد. این نحوه کامنت نویسی بیشتر در سی++ متداول است. اگر از ویژوال استودیو استفاده می‌کنید، مکان نما را به سطر قبل از یک متد منتقل کرده و سه بار پشت سر هم forward slash را تایپ کنید. به صورت خودکار ساختار خالی زیر تشکیل خواهد شد:
/// <summary>
///
/// </summary>
/// <param name="sqlCnt"></param>
/// <param name="cntString"></param>
/// <returns></returns>
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)

این روش مرسوم کامنت نویسی کدهای سی شارپ است. خصوصا اینکه ابزارهایی وجود دارند که به صورت خودکار از این نوع کامنت‌ها، فایل CHM‌ درست می‌کنند.

2) وجود سازنده private
احتمالا هدف این بوده که نه شخصی و نه حتی کامپایلر، وهله‌ای از این کلاس را ایجاد نکند. بنابراین بهتر است کلاسی را که تمام متدهای آن static است (که به این هم خواهیم رسید!) ، راسا static معرفی کنید. به این ترتیب نیازی به سازنده private نخواهد بود.

3) وجود try/catch
یک اصل کلی وجود دارد: اگر در حال طراحی یک کتابخانه پایه‌ای هستید، try/catch را در هیچ متدی از آن لحاظ نکنید. بله؛ درست خوندید! لطفا try/catch ننویسید! کرش کردن برنامه خوب است! لا‌یه‌های بالاتر برنامه که در حال استفاده از کدهای شما هستند متوجه خواهند شد که مشکلی رخ داده و این مشکل توسط کتابخانه مورد استفاده «خفه» نشده. برای مثال اگر هم اکنون SQL Server در دسترس نیست، لایه‌های بالاتر برنامه باید این مشکل را متوجه شوند. Exception اصلا چیز بدی نیست! کرش برنامه اصلا بد نیست!
فرض کنید که دچار بیماری شده‌اید. اگر مثلا تبی رخ ندهد، از کجا باید متوجه شد که نیاز به مراقبت پزشکی وجود دارد؟ اگر هیچ علامتی بروز داده نشود که تا الان نسل بشر منقرض شده بود!

4) وجود ref و out
دوستان گرامی! این ref و out فقط جهت سازگاری با زبان C در سی شارپ وجود دارد. لطفا تا حد ممکن از آن استفاده نکنید! مثلا استفاده از توابع API‌ ویندوز که با C نوشته شده‌اند.
یکی از مهم‌ترین کاربردهای pointers در زبان سی، دریافت بیش از یک خروجی از یک تابع است. برای مثال یک متد API ویندوز را فراخوانی می‌کنید؛ خروجی آن یک ساختار است که به کمک pointers به عنوان یکی از پارامترهای همان متد معرفی شده. این روش به وفور در طراحی ویندوز بکار رفته. ولی خوب در سی شارپ که از این نوع مشکلات وجود ندارد. یک کلاس ساده را طراحی کنید که چندین خاصیت دارد. هر کدام از این خاصیت‌ها می‌توانند نمایانگر یک خروجی باشند. خروجی متد را از نوع این کلاس تعریف کنید. یا برای مثال در دات نت 4، امکان دیگری به نام Tuples معرفی شده برای کسانی که سریع می‌خواهند چند خروجی از یک تابع دریافت کنند و نمی‌خواهند برای اینکار یک کلاس بنویسند.
ضمن اینکه برای مثال در متد Connect2Db، هم کانکشن یکبار به صورت ref معرفی شده و یکبار به صورت خروجی متد. اصلا نیازی به استفاده از ref در اینجا نبوده. حتی نیازی به خروجی کانکشن هم در این متد وجود نداشته. کلیه تغییرات شما در شیء کانکشنی که به عنوان پارامتر ارسال شده، در خارج از آن متد هم منعکس می‌شود (شبیه به همان بحث pointers در زبان سی). بنابراین وجود ref غیرضروری است؛ وجود خروجی متد هم به همین صورت.

5) استفاده از using در متد RunExecuteScalarCmd
استفاده از using خیلی خوب است؛ همیشه اینکار را انجام دهید!
اما اگر اینکار را انجام دادید، بدانید که شیء sqlCnt در پایان بدنه using ، توسط GC نابوده شده است. بنابراین اینجا bool blnClose دیگر چه کاربردی دارد؟! تصمیم شما دیگر اهمیتی نخواهد داشت؛ چون کار تخریبی پیشتر انجام شده.

6) متد CloseCnt
این متد زاید است؛ به دلیلی که در قسمت (5) عنوان شد. using های استفاده شده، کار را تمام کرده‌اند. بنابراین بستن اشیاء dispose شده معنا نخواهد داشت.

7) در مورد نحوه استفاده
اگر SqlHelper را در اینجا مثلا یک DAL ساده فرض کنیم (data access layer)، جای قسمت BLL (business logic layer) در اینجا خالی است. عموما هم چون توضیحات این موارد را خیلی بد ارائه داده‌اند، افراد از شنیدن اسم آن‌ها هم وحشت می‌کنند. BLL یعنی کمی دست به Refactoring بزنید و این پیاده سازی منطق تجاری ارائه شده در متد BtnTest_Click را به یک کلاس مجزا خارج از code behind پروژه منتقل کنید. Code behind فقط محل استفاده نهایی از آن باشد. همین! فعلا با همین مختصر شروع کنید.
مورد دیگری که در اینجا باز هم مشهود است، عدم استفاده از پارامتر در کوئری‌ها است. چون از پارامتر استفاده نکرده‌اید، SQL Server مجبور است برای حالت EmployeeID = 9 یکبار execution plan را محاسبه کند، برای کوئری بعدی مثلا EmployeeID = 19، اینکار را تکرار کند و الی آخر. این یعنی مصرف حافظه بالا و همچنین سرعت پایین انجام کوئری‌ها. بنابراین اینقدر در قید و بند باز نگه داشتن یک کانکشن نباشید؛ مشکل اصلی جای دیگری است!

8) برنامه وب و اطلاعات استاتیک!
این پروژه، یک پروژه ASP.NET است. دیدن تعاریف استاتیک در این نوع پروژه‌ها یک علامت خطر است! در این مورد قبلا مطلب نوشتم:
متغیرهای استاتیک و برنامه‌های ASP.NET


یک درخواست عمومی!
لطف کنید در پروژ‌های «جدید» خودتون این نوع کلاس‌های SqlHelper رو «دور بریزید». یاد گرفتن کار با یک ORM جدید اصلا سخت نیست. مثلا طراحی Entity framework مایکروسافت به حدی ساده است که هر شخصی با داشتن بهره هوشی در حد یک عنکبوت آبی یا حتی جلبک دریایی هم می‌تونه با اون کار کنه! فقط NHibernate هست که کمی مرد افکن است و گرنه مابقی به عمد ساده طراحی شده‌اند.
مزایای کار کردن با ORM ها این است:
- کوئری‌های حاصل از آن‌ها «پارامتری» است؛ که این دو مزیت عمده را به همراه دارد:
امنیت: مقاومت در برابر SQL Injection
سرعت و همچنین مصرف حافظه کمتر: با کوئری‌های پارامتری در SQL Server همانند رویه‌های ذخیره شده رفتار می‌شود.
- عدم نیاز به نوشتن DAL شخصی پر از باگ. چون ORM یعنی همان DAL که توسط یک سری حرفه‌ای طراحی شده.
- یک دست شدن کدها در یک تیم. چون همه بر اساس یک اینترفیس مشخص کار خواهند کرد.
- امکان استفاده از امکانات جدید زبان‌های دات نتی مانند LINQ و نوشتن کوئری‌های strongly typed تحت کنترل کامپایلر.
- پایین آوردن هزینه‌های آموزشی افراد در یک تیم. مثلا EF را می‌شود به عنوان یک پیشنیاز در نظر گرفت؛ عمومی است و همه گیر. کسی هم از شنیدن نام آن تعجب نخواهد کرد. کتاب(های) آموزشی هم در مورد آن زیاد هست.
و ...


نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
این مورد را در قسمت اول ذیل «اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند »، توضیح دادم.

شما در ASP.NET Core امکان کار با هر دو فریم ورک یاد شده را دارید و این دو به هم وابستگی ندارند. به عبارتی چندین target را دراینجا می‌توانید معرفی و استفاده کنید. اگر دات نت 4.6 را هم استفاده کردید، برنامه فقط قابلیت چندسکویی خودش را از دست خواهد داد. برای مثال شما هم اکنون می‌توانید EF 6.x را با ASP.NET Core 1.0 استفاده کنید (اگر نمی‌خواهید تا زمان تکمیل نهایی EF Core صبر کنید). فقط در این حالت باید دقت داشته باشید که کدهای شما بر روی لینوکس اجرا نخواهند شد (چون EF 6.x مبتنی بر دات نت 4x است).
مطالب
بهبود عملکرد SQL Server Locks در سیستم‌های با تعداد تراکنش بالا در Entity Framework
بر اساس رفتار پیش فرض در دیتابیس 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 مربوطه اقدام می‌نماییم.
اشتراک‌ها
پیاده سازی یک پروژه با ASP.NET Core Web API

Ticketier project | ASP.NET Core Web API CRUD and Search | .NET 7 API | Full Course

Full Course Ticketier project with ASP.NET Core Web API (.NET 7 API ) and  Entity Framework Core covering CRUD and Search step by step
In this video, we will create an ASP.NET Core Web API (.NET 7) project called Ticketier and implement full CRUD and Search functionality into it.
The focus of this project is to show you how you can build new ASP.NET Core Web API (.NET 7) project from 0 to 100 and implement CRUD and Search in it.

we will learn these topics together:
Entities
Dtos
Context
ORM
Http Methods
Swagger
AutoMapper
IQueryable
Where clause 

پیاده سازی یک پروژه با ASP.NET Core Web API
اشتراک‌ها
مخزن آموزشی Net. Core با مثالهای پیاده سازی شده

در این مخزن روش‌های بهینه و توصیه شده جهت ساخت برنامه‌های تحت وب با استفاده از Net Core. در قالب 12 پروژه پیاده سازی شده است که منبع خوبی جهت الگو برداری است.

Boilerplate for ASP.NET Core reference application with Entity Framework Core, demonstrating a layered application architecture with DDD best practices. Implements NLayer Hexagonal architecture (Core, Application, Infrastructure and Presentation Layers) and Domain Driven Design (Entities, Repositories, Domain/Application Services, DTO's...) and aimed to be a Clean Architecture, with applying SOLID principles in order to use for a project template. Also implements best practices like loosely-coupled, dependency-inverted architecture and using design patterns such as Dependency Injection, logging, validation, exception handling, localization and so on. 

مخزن آموزشی Net. Core با مثالهای پیاده سازی شده
اشتراک‌ها
نسخه RTM برای 1.0 ASP.NET Core ارایه شد

We are excited to announce the release of .NET Core 1.0, ASP.NET Core 1.0 and Entity Framework 1.0, available on Windows, OS X and Linux! .NET Core is a cross-platform, open source, and modular .NET platform for creating modern web apps, microservices, libraries and console applications.

This release includes the .NET Core runtime, libraries and tools and the ASP.NET Core libraries. We are also releasing Visual Studio and Visual Studio Code extensions that enable you to create .NET Core projects. You can get started at https://dot.net/core. Read the release notes for detailed release information. 

نسخه RTM برای 1.0 ASP.NET Core ارایه شد
اشتراک‌ها
لاگ زدن تغییرات انجام شده در DbContext با Entity Framework 4.1
Many applications have a need to keep audit information on changes made to objects in the database. Traditionally, this would be done either through log events, stored procedures that implement the logging, or the use of archive/tombstone tables to store the old values before the modification (hopefully enforced through stored procedures). With all of these, there is always a chance that a developer could forget to do those things in a specific section of code, and that changes could be made through the application without logging the change correctly. With Entity Framework 4.1’s DbContext API, it is fairly easy to implement more robust audit logging in your application  
لاگ زدن تغییرات انجام شده در DbContext با Entity Framework 4.1