مطالب
بازسازی کد: Encapsulate collection - ساده و خیلی مهم
زمانیکه متدی یک لیست (collection) را بر می‌گرداند، بهتر است این لیست، نسخه‌ای فقط خواندنی باشد. برای توضیح بیشتر این موضوع می‌توان به کلاس زیر و نحوه‌ی بازسازی آن رجوع کرد. 
در کلاس Person فرضی، متدی مسئولیت دریافت لیست درس‌های آن شخص را بر عهده دارد. متد دیگری نیز مسئولیت ثبت کردن آنها را بر عهده دارد. توجه کنید متد دریافت لیست درس ها، لیستی قابل ویرایش (افزودن یا حذف کردن) را بر می‌گرداند. 

در پیاده سازی ابتدایی، متدهای getter و setter عادی را مشاهده می‌کنیم که خیلی معمول هستند (با پیاده سازی‌های مختلف در نگارش‌های مختلف #C) .

اما چرا بهتر است لیستی فقط خواندنی را از طریق متد GetCourses برگردانیم؟  

  • زیرا برگرداندن لیست قابل ویرایش، به استفاده کننده کلاس، امکان ویرایش لیست را بدون اطلاع کلاس مالک می‌دهد. 
  • زیرا اطلاعات بیش از حدی در مورد نحوه‌ی ذخیره سازی اطلاعات در کلاس مالک را به استفاده کننده می‌دهد.
با برگرداندن نسخه‌ای فقط خواندنی از لیست خود می‌توانیم اشکالات مطرح شده را تا حدی کاهش دهیم. این بازسازی کد را می‌توان با حذف setter برای کل لیست و افزودن دو متد برای افزودن و حذف تکی آیتم‌های آن کامل کرد. با این تغییر می‌توان از عدم تغییرات خارج از اطلاع و کنترل کلاس مالک، به طور کامل اطمینان حاصل کرد. 
به طور مثال اگر در #C از کلاس List استفاده می‌کنید، می‌توان به صورت زیر این کار را انجام داد؛ نسخه عمومی‌تری مانند IReadOnlyCollection نیز وجود دارد. همچنین نسخه‌ای از دیکشنری فقط خواندنی نیز در دسترس است (IReadOnlyDictionary):
public class Refactoring 
{ 
    private List<object> objects; 
    public Refactoring() 
    { 
        objects = new List<object>(); 
    } 
    public IReadOnlyList<object> Objects { get { return objects.AsReadOnly(); } } 
}
اشتراک‌ها
حذف رکورد های تکراری قبل از رسیدن به کنترلر

در این ویدیو با استفاده از HashSet رکورد های تکراری را قبل از رسیدن به کنترلر حذف میکنیم و دیگر نیازی به استفاده از Distinct یا DistinctBy وجود ندارد.

ابتدا این کار را با تایپ های پیشفرض سی شارپ مانند int و string انجام میدهیم و بعد از آن عملیات حذف رکورد های تکراری را برای کلاس هایی که خودمان ایجاد کرده ایم انجام میدهیم و به صورت کامل متد های GetHashCode و Equals را مورد بررسی قرار میدهیم و یاد میگیریم چرا با وجود اینکه همیشه از متد GetHashCode به دلیل سرعت بالاتر نسبت به Equals استفاده میکنیم ولی به متد Equals هم نیاز داریم.

حذف رکورد های تکراری قبل از رسیدن به کنترلر
نظرات مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
با سلام بنده  داخل پروژه ام از t4mvc  استفاده کردم امروز صبح یک partialview اضافه کردم وقتی روی T4MVC.tt راست کلیک کردم و run custom tool را زدم با خطای :


Error1Running transformation: System.ArgumentException: The file 'T4MVC.tt' is not open.
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)
   at Microsoft.VisualStudio.NativeMethods.ThrowOnFailure(Int32 hr, Int32[] expectedHRFailure)
   at Microsoft.VisualStudio.Shell.RunningDocumentTable.FindDocument(String moniker, IVsHierarchy& hierarchy, UInt32& itemid, UInt32& docCookie)
   at Microsoft.XmlEditor.XmlHelper.GetCanonicalNameWithoutView(IServiceProvider site, String fname)
   at Microsoft.XmlEditor.XmlFileInfoService.RenameOrRemoveCacheFileInfo(String fname)
   at Microsoft.XmlEditor.Package.GetOrCreateFileInfo(String fname, String text)
   at Microsoft.XmlEditor.Package.GetOrCreateFileInfo(String fname, IVsTextLines buffer)
   at Microsoft.XmlEditor.FactoryBase.GetDesignerFactory(String monikerTheFileOnDisk, String monikerToUse, IVsTextLines existingBuffer, Guid logicalView)
   at Microsoft.XmlEditor.Chooser.ChooseEditorFactory(String pszMkDocument, IVsHierarchy pHier, UInt32 itemid, IntPtr punkDocDataExisting, Guid& rguidLogicalView, Guid& pguidEditorTypeActual, Guid& pguidLogicalViewActual)
   at EnvDTE.ProjectItem.Open(String ViewKind)
   at Microsoft.VisualStudio.TextTemplating07E2522337018FCFC0AADC805726764A22758F53E05D3026845E2AA5E0226E57B77322743831B8A035E8EFE18D3CC1F808985A2958CCE46A2085D3520D2EC605.GeneratedTextTransformation.XmlSettings.Init(ITextTemplatingEngineHost host) in e:\Project\GRP\GRP.Web\T4MVC.tt:line 2120
   at Microsoft.VisualStudio.TextTemplating07E2522337018FCFC0AADC805726764A22758F53E05D3026845E2AA5E0226E57B77322743831B8A035E8EFE18D3CC1F808985A2958CCE46A2085D3520D2EC605.GeneratedTextTransformation.XmlSettings.Load[T](ITextTemplatingEngineHost host) in e:\Project\GRP\GRP.Web\T4MVC.tt:line 2093
   at Microsoft.VisualStudio.TextTemplating07E2522337018FCFC0AADC805726764A22758F53E05D3026845E2AA5E0226E57B77322743831B8A035E8EFE18D3CC1F808985A2958CCE46A2085D3520D2EC605.GeneratedTextTransformation.MvcSettings.Load(ITextTemplatingEngineHost host) in e:\Project\GRP\GRP.Web\T4MVC.tt:line 1786
   at Microsoft.VisualStudio.TextTemplating07E2522337018FCFC0AADC805726764A22758F53E05D3026845E2AA5E0226E57B77322743831B8A035E8EFE18D3CC1F808985A2958CCE46A2085D3520D2EC605.GeneratedTextTransformation.TransformText() in e:\Project\GRP\GRP.Web\T4MVC.tt:line 39e:\Project\GRP\GRP.Web\T4MVC.tt21201GRP.Web
مواجه شدم t4mvc را uninstall کردم دوباره نصب کردم موقع generate  دوباره همین پیغام را میدم. لطفا یکی به من کمک کنه.ممنون
نظرات مطالب
نوشتن پرس و جو در Entity Framework‌ با استفاده از LINQ To Entity قسمت اول
در پرس و جوهای معمولی ، بدین شکل عمل می‌شود که در نهایت نتیجه با شرط یک Id  یا چیزی شبیه این مقایسه می‌شود .
var Id=1; 
var books = (from b in db.Books
                              where b.bookId == Id
                              select new
                              {
                                 //...

                              }).ToList();

حالا اگر شرط من بجای داشتن فقط یک Id  لیستی از Id باشد چطور عمل کنم ؟
var booksId= new list<int>(){ 1 , 2 , 6 , 7}; 
var books = (from b in db.Books
                              where b.bookId == ???
                              select new
                              {
                                 //...

                              }).ToList();

چطور میتونم لیستی رو که دارم بجای مقایسه با یک Id  ، با یک لیستی از Id‌ها مقایسه کنم و نتیجه را بگیرم ؟
اشتراک‌ها
کتابخانه WebApiThrottle

ASP.NET Web API Throttling handler, OWIN middleware and filter are designed to control the rate of requests that clients can make to a Web API based on IP address, client API key and request route.

public static void Register( HttpConfiguration config )
{
    config.MessageHandlers.Add( new ThrottlingHandler
                                {
                                    Policy = new ThrottlePolicy( perSecond: 1,
                                                                    perMinute: 20,
                                                                    perHour: 200,
                                                                    perDay: 1500,
                                                                    perWeek: 3000 )
                                                {
                                                    IpThrottling = true
                                                },
                                    Repository = new CacheRepository()
                                } );
}
کتابخانه WebApiThrottle
مطالب
بهبود کارآیی Reflection در دات نت 7
استفاده‌ی از Reflection در زیر ساخت‌های دات نت و ASP.NET Core، بسیار گسترده‌است؛ به همین جهت هرگونه بهبود کارآیی در این زمینه، نه فقط بر روی خود فریم‌ورک، بلکه تمام برنامه‌هایی که از آن استفاده می‌کنند هم تاثیر گذار است. از این لحاظ دات نت 7 شاهد تغییرات گسترده‌ای است تا حدی که کارآیی برنامه‌های مبتنی بر دات نت 7 ای که از Reflection استفاده می‌کنند، نسبت به نگارش‌های قبلی دات نت، حداقل 2 برابر شده‌است و این برنامه‌ها تنها کاری را که باید انجام دهند، صرفا تغییر target framework مورد استفاده‌ی در آن‌ها به نگارش جدید است. در این مطلب نحوه‌ی رسیدن به این کارآیی بالاتر را بررسی خواهیم کرد.


تدارک یک آزمایش برای بررسی میزان افزایش کارآیی Reflection در دات نت 7

یک برنامه‌ی کنسول جدید را ایجاد کرده و سپس کلاس Person را به صورت زیر به آن اضافه می‌کنیم:
namespace NET7Reflection;

public class Person
{
    private int _age;

    internal Person(int age) => _age = age;

    private int GetAge() => _age;

    private void SetAge(int age) => _age = age;
}
همانطور که مشاهده می‌کنید، سازنده‌ی این کلاس، internal است و همچنین دو متد private هم دارد که اگر بخواهیم از آن در جای  دیگری استفاده کنیم، یکی از روش‌های متداول جهت دسترسی به این امکانات خصوصی، استفاده از Reflection است.
به همین جهت ابتدا کتابخانه‌ی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
  </ItemGroup>

</Project>
در ادامه، یک کلاس آزمایش کارآیی برنامه را که با استفاده از Reflection، به امکانات خصوصی کلاس Person دسترسی پیدا می‌کند، مشاهده می‌کنید:
using System.Reflection;
using BenchmarkDotNet.Attributes;

namespace NET7Reflection;

[MemoryDiagnoser(false)]
public class Benchmarks
{
    private readonly object?[] _ageParams = { 30 };

    private readonly ConstructorInfo _ctor =
        typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })!;

    private readonly MethodInfo _getAgeMethod =
        typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)!;

    private readonly Person _person = new(10);

    private readonly MethodInfo _setAgeMethod =
        typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)!;

    [Benchmark]
    public int GetAge() =>
        (int)typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)!
                           .Invoke(_person, Array.Empty<object?>())!;

    [Benchmark]
    public int GetAgeCachedMethod() => (int)_getAgeMethod.Invoke(_person, Array.Empty<object?>())!;

    [Benchmark]
    public void SetAge() =>
        typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)!
                      .Invoke(_person, new object?[] { 30 });

    [Benchmark]
    public void SetAgeCachedMethod() => _setAgeMethod.Invoke(_person, new object?[] { 30 });

    [Benchmark]
    public void SetAgeCachedMethodCachedParams() => _setAgeMethod.Invoke(_person, _ageParams);

    [Benchmark]
    public Person Ctor() =>
        (Person)typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })!
                              .Invoke(_person, new object?[] { 30 })!;

    [Benchmark]
    public Person CtorCachedCtorInfo() => (Person)_ctor.Invoke(_person, new object?[] { 30 })!;

    [Benchmark]
    public Person CtorCachedCtorInfoCachedParams() => (Person)_ctor.Invoke(_person, _ageParams)!;
}
توضیحات:
- در اینجا نحوه‌ی کار با متدهای خصوصی کلاس Person را توسط Reflection مشاهده می‌کنید. برای مثال در متد GetAge، به نحو متداولی این کار صورت گرفته‌است. در متد GetAgeCachedMethod، قسمت دریافت اطلاعات متد، کش شده‌است و برای نمونه در متد SetAgeCachedMethodCachedParams، هم کش شدن قسمت دریافت اطلاعات متد را مشاهده می‌کنید و هم کش شدن پارامتر ارسالی به آن‌را.
- این آزمایش را با فراخوانی زیر و تنظیم target framework به دات نت 6 و سپس دات نت 7، به صورت جداگانه‌ای اجرا می‌کنیم:
using BenchmarkDotNet.Running;
using NET7Reflection;

BenchmarkRunner.Run<Benchmarks>();
حاصل اجرای آن با target framework دات نت 6 به صورت زیر است:



و با target framework دات نت 7 به صورت زیر:


همانطور که مشاهده می‌کنید، در اکثر موارد، کارآیی Reflection در دات نت 7، حداقل 2 برابر نمونه‌ی مشابه دات نت 6 است.


چه تغییری در دات نت 7 سبب بهبود قابل ملاحظه‌ی کارآیی Reflection شده‌است؟

جزئیات تغییرات صورت گرفته‌ی در Reflection دات نت 7 را می‌توانید در این pull request مشاهده کنید که در حقیقت از امکانات سطح پایین IL Emit استفاده کرده‌اند. در این مورد پیشتر تعدادی مطلب ذیل عنوان «آشنایی با Reflection.Emit» در این سایت منتشر شده‌اند که می‌توانید آن‌ها را بررسی کنید.
در کل هرچند تغییرات جدید دات نت مانند ارائه‌ی انواع و اقسام source generators، در تعدادی از موارد نیاز به Reflection را کمتر کرده‌اند و کارآیی بیشتری را ارائه داده‌اند، اما Reflection هیچگاه منسوخ نخواهد شد و هرگونه بهبود کارآیی در این زمینه، بر روی کل فریم‌ورک و برنامه‌های مشتق شده‌ی از آن، تاثیر قابل توجهی را خواهد گذاشت.
نظرات مطالب
معرفی List Patterns Matching در C# 11
یک نکته‌ی تکمیلی: ایجاد نوع‌های سازگار با List Patterns Matching

در انتهای این مطلب در مورد «سایر نوع‌هایی که توسط List patterns قابل بررسی هستند» توضیحات مختصری عنوان شد. کامپایلر #C در جهت یافتن نوع‌های سازگار با List Patterns Matching، به دنبال اینترفیس خاصی نمی‌گردد؛ بلکه به دنبال وجود یک سری اعضای خاص، در کلاس مدنظر است و این اعضاء به شرح زیر هستند:
الف) نوع مدنظر باید به همراه یکی از خواص Length و یا Count باشد تا تعداد اعضای مجموعه را مشخص کند. اگر هر دو خاصیت با هم حضور داشته باشند، کامپایلر خاصیت Length را انتخاب می‌کند:
public int Length { get; }
public int Count { get; }

ب) نوع مجموعه‌ای باید به همراه یک ایندکسر باشد که نوع خروجی آن مهم نیست. اگر در نوع تعریف شده، هر دو امضای زیر وجود داشته باشند، کامپایلر از نمونه‌ی this[Index index] استفاده می‌کند:
public object this[int index] => throw null;
public object this[System.Index index] => throw null;

ج) نوع مجموعه‌ای باید از slice pattern، توسط یکی از امضاهای زیر که نوع خروجی آن مهم نیست، پشتیبانی کند. اگر هر دو با هم حضور داشته باشند، کامپایلر از this[System.Range index] استفاده می‌کند:
public object this[System.Range index] => throw null;
public object Slice(int start, int length) => throw null;

برای مثال با توجه به نکات فوق، نوع جدید زیر، با List Patterns Matching سازگاری دارد:
public class MyListPatternsCompatibleCollection
{
    private readonly List<int> _items = new();

    public int Length => _items.Count;

    public int this[Index index] => _items[index];

    public ReadOnlySpan<int> this[Range range]
        => CollectionsMarshal.AsSpan(_items)[range];

    public void Add(int item) => _items.Add(item);
}
و نمونه‌ای از نحوه‌ی استفاده‌ی از آن به صورت زیر است:
 var collection = new MyListPatternsCompatibleCollection();
collection.Add(1);
collection.Add(2);
collection.Add(3);

_ = collection is [var head, .. var tail];
مطالب
اجرای Stored Procedure با چند نوع مقدار برگشتی توسط EF CodeFirst
فرض کنید Stored Procedure ی با چند مقدار برگشتی را می‌خواهیم در EF CodeFirst مورد استفاده قرار دهیم. برای مثال Stored Procedure زیر را در نظر بگیرید:
CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts]
AS
    SELECT * FROM dbo.Blogs
    SELECT * FROM dbo.Posts
Stord Procedure  ی که توسط این دستور ساخته می‌شود تمام رکوردهای جدول Blogs و تمامی رکوردهای جدول Posts را واکشی کرده و به عنوان خروجی برمیگرداند (دو خروجی متفاوت). روش فراخوانی و استفاده از داده‌های این StoredProcedure در EF CodeFirst به صورت زیر است :
تعریف دو کلاس مدل Blog و Post به ترتیب  برای نگهداری اطلاعات وبلاگ‌ها و پست‌ها در زیر آمده است. در ادامه نیز تعریف کلاس BloggingContext را مشاهده می‌کنید.

public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }

        public virtual List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public virtual Blog Blog { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }


 
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;

namespace Sproc.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new BloggingContext())
            {
                 db.Database.Initialize(force: false);
               
                var cmd = db.Database.Connection.CreateCommand();
                cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";

                try
                {
                    // اجرای پروسیجر
                    db.Database.Connection.Open();
                    var reader = cmd.ExecuteReader();

                    // خواند رکوردهای blogs
                    var blogs = ((IObjectContextAdapter)db)
                        .ObjectContext
                        .Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);

                    foreach (var item in blogs)
                    {
                        Console.WriteLine(item.Name);
                    }

                    // پرش به نتایج بعدی (همان Posts)
                    reader.NextResult();
                    var posts = ((IObjectContextAdapter)db)
                        .ObjectContext
                        .Translate<Post>(reader, "Posts", MergeOption.AppendOnly);

                    foreach (var item in posts)
                    {
                        Console.WriteLine(item.Title);
                    }
                }
                finally
                {
                    db.Database.Connection.Close();
                }
            }
        }
    }
در کدهای بالا ابتدا یک Connection به بانک اطلاعاتی باز می‌شود:
 db.Database.Connection.Open();
و پس از آن نوبت به اجرای Stored Procedure می‌رسد:
 
var reader = cmd.ExecuteReader();
در کد بالا پس از اجرای Stored Procudure نتایج بدست آمده در یک reader ذخیره می‌شود. شئ reader از نوع DBDataReader می‌باشد. پس از اجرای Stored Procedure و دریافت نتایج و ذخیره سازی در شئی reader ، نوبت به جداسازی رکوردها می‌رسد. همانطور که در تعریف Stored procedure مشخص است این Stored Procedure دارای دو نوع خروجی از نوع‌های Blog و Post می‌باشد و این دو نوع باید از هم جدا شوند.برای انجام این کار از متد Translate شئی Context استفاده می‌شود. این متد قابلیت کپی کردن نتایج موجود از یک شئی DBDataReader به یک شئی از نوع مدل را دارد. برای مثال :
 
var blogs = ((IObjectContextAdapter)db)             
           .ObjectContext
           .Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
در کدهای بالا تمامی رکوردهایی از نوع Blog از شئی reader خوانده شده و پس از تبدیل به نوع Blog درون شئی Blogs ذخیره می‌شود.
پس از آن توسط حلقه foreach محتویات Blogs پیمایش شده و مقدار موجود در  فیلد  Name نمایش داده می‌شود.
  foreach (var item in blogs)
  {
             Console.WriteLine(item.Name);
  }
با توجه به اینکه حاصل اجرای این Stored Procedure دو خروجی متفاوت بوده است ، پس از پیمایش رکوردهای Blogs باید به سراغ نتایج بعدی که همان رکوردهای Post می‌باشد برویم. برای اینکار از متد NextResult شئی reader استفاده می‌شود:
 
reader.NextResult();
در ادامه برای خواندن رکوردهایی از نوع Post نیز به همان روشی که برای Blog انجام شد عمل می‌شود.
مطالب
استفاده ازExpressionها جهت ایجاد Strongly typed view در ASP.NET MVC
مدل زیر را در نظر بگیرید:
/// <summary>
    /// 
    /// </summary>
    public class CompanyModel
    {
        /// <summary>
        /// Table Identity
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Company Name
        /// </summary>
        [DisplayName("نام شرکت")]
        public string CompanyName { get; set; }

        /// <summary>
        /// Company Abbreviation
        /// </summary>
        [DisplayName("نام اختصاری شرکت")]
        public string CompanyAbbr { get; set; }
    
    }
از View زیر جهت نمایش لیستی از شرکت‌ها متناظر با مدل جاری استفاده میشود:
@{
    const string viewTitle = "شرکت ها";
    ViewBag.Title = viewTitle;
    const string gridName = "companies-grid";
}
<div class="col-md-12">
    <div class="form-panel">
        <header>
            <div class="title">
                <i class="fa fa-book"></i>
                @viewTitle
            </div>
        </header>
        <div class="panel-body">
            <div id="@gridName">
            </div>
        </div>
    </div>
</div>
</div>
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#@gridName").kendoGrid({
                dataSource: {
                    type: "json",
                    transport: {
                        read: {
                            url: "@Html.Raw(Url.Action(MVC.Company.CompanyList()))",
                            type: "POST",
                            dataType: "json",
                            contentType: "application/json"
                        }
                    },
                    schema: {
                        data: "Data",
                        total: "Total",
                        errors: "Errors"
                        }
                    },
                    pageSize: 10,
                    serverPaging: true,
                    serverFiltering: true,
                    serverSorting: true
                },
                pageable: {
                    refresh: true
                },
                sortable: {
                    mode: "multiple",
                    allowUnsort: true
                },
                editable: false,
                filterable: false,
                scrollable: false,
                columns: [ {
                    field: "CompanyName",
                    title: "نام شرکت",
                    sortable: true,
                }, {
                    field: "CompanyAbbr",
                    title: "مخفف نام شرکت",
                    sortable: true
                }]
            });
        });
    </script>
}


مشکلی که در کد بالا وجود دارد این است که با تغییر نام هر یک از متغییر هایمان ، اطلاعات گرید در ستون مربوطه نمایش داده نمیشود.همچنین عناوین ستونها نیز از DisplayName مدل پیروی نمیکنند.توسط متدهای الحاقی زیر این مشکل برطرف شده است.

 /// <summary>
    /// 
    /// </summary>
    public static class PropertyExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static MemberInfo GetMember<T>(this Expression<Func<T, object>> expression)
        {
            var mbody = expression.Body as MemberExpression;

            if (mbody != null) return mbody.Member;
            //This will handle Nullable<T> properties.
            var ubody = expression.Body as UnaryExpression;
            if (ubody != null)
            {
                mbody = ubody.Operand as MemberExpression;
            }
            if (mbody == null)
            {
                throw new ArgumentException("Expression is not a MemberExpression", "expression");
            }
            return mbody.Member;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string PropertyName<T>(this Expression<Func<T, object>> expression)
        {
            return GetMember(expression).Name;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string PropertyDisplay<T>(this Expression<Func<T, object>> expression)
        {
            var propertyMember = GetMember(expression);
            var displayAttributes = propertyMember.GetCustomAttributes(typeof(DisplayNameAttribute), true);
            return displayAttributes.Length == 1 ? ((DisplayNameAttribute)displayAttributes[0]).DisplayName : propertyMember.Name;
        }
    }



public static string PropertyName<T>(this Expression<Func<T, object>> expression)
جهت بدست آوردن نام متغییر هایمان استفاده مینماییم.


public static string PropertyDisplay<T>(this Expression<Func<T, object>> expression)
جهت بدست آوردن DisplayNameAttribute استفده میشود. درصورتیکه این DisplayNameAttribute یافت نشود نام متغییر بازگشت داده میشود.

بنابراین View مربوطه را اینگونه بازنویسی میکنیم:

@using Models
@{
    const string viewTitle = "شرکت ها";
    ViewBag.Title = viewTitle;
    const string gridName = "companies-grid";
}
<div class="col-md-12">
    <div class="form-panel">
        <header>
            <div class="title">
                <i class="fa fa-book"></i>
                @viewTitle
            </div>
        </header>
        <div class="panel-body">
            <div id="@gridName">
            </div>
        </div>
    </div>
</div>
</div>
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#@gridName").kendoGrid({
                dataSource: {
                    type: "json",
                    transport: {
                        read: {
                            url: "@Html.Raw(Url.Action(MVC.Company.CompanyList()))",
                            type: "POST",
                            dataType: "json",
                            contentType: "application/json"
                        }
                    },
                    schema: {
                        data: "Data",
                        total: "Total",
                        errors: "Errors"
                        }
                    },
                    pageSize: 10,
                    serverPaging: true,
                    serverFiltering: true,
                    serverSorting: true
                },
                pageable: {
                    refresh: true
                },
                sortable: {
                    mode: "multiple",
                    allowUnsort: true
                },
                editable: false,
                filterable: false,
                scrollable: false,
                columns: [ {
                    field: "@(PropertyExtensions.PropertyName<CompanyModel>(a => a.CompanyName))",
                    title: "@(PropertyExtensions.PropertyDisplay<CompanyModel>(a => a.CompanyName))",
                    sortable: true,
                }, {
                    field: "@(PropertyExtensions.PropertyName<CompanyModel>(a => a.CompanyAbbr))",
                    title: "@(PropertyExtensions.PropertyDisplay<CompanyModel>(a => a.CompanyAbbr))",
                    sortable: true
                }]
            });
        });
    </script>
}


مطالب
نکاتی در مورد استفاده از توابع تجمعی در Entity framework
استفاده از Aggregate functions یا توابع تجمعی چه در زمان SQL نویسی مستقیم و یا در حالت استفاده از LINQ to Entities نیاز به ملاحظات خاصی دارد که عدم رعایت آن‌ها سبب کرش برنامه در زمان موعد خواهد شد. در ادامه تعدادی از این موارد را مرور خواهیم کرد.

ابتدا مدل‌های برنامه را در نظر بگیرید که از یک صورتحساب، به همراه ریز قیمت‌های آیتم‌های مرتبط با آن تشکیل شده است:
    public class Bill
    {
        public int Id { set; get; }
        public string Name { set; get; }

        public virtual ICollection<Transaction> Transactions { set; get; }
    }

    public class Transaction
    {
        public int Id { set; get; }
        public DateTime AddDate { set; get; }
        public int Amount { set; get; }

        [ForeignKey("BillId")]
        public virtual Bill Bill { set; get; }
        public int BillId { set; get; }
    }
در ادامه این کلاس‌ها را در معرض دید EF Code first قرار می‌دهیم:
    public class MyContext : DbContext
    {
        public DbSet<Bill> Bills { get; set; }
        public DbSet<Transaction> Transactions { get; set; }
    }
همچنین تعدادی رکورد اولیه را نیز جهت انجام آزمایشات به بانک اطلاعاتی متناظر، اضافه خواهیم کرد:
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var bill1 = new Bill { Name = "bill-1" };
            context.Bills.Add(bill1);

            for (int i = 0; i < 11; i++)
            {
                context.Transactions.Add(new Transaction
                {
                    AddDate = DateTime.Now.AddDays(-i),
                    Amount = 1000000000 + i,
                    Bill = bill1
                });
            }
            base.Seed(context);
        }
    }
در اینجا به عمد از ارقام بزرگ استفاده شده است تا نمایانگر عملکرد یک سیستم واقعی در طول زمان باشد.


اولین مثال: یک جمع ساده

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var sum = context.Transactions.Sum(x => x.Amount);
                Console.WriteLine(sum);
            }
        }
    }
ساده‌ترین نیازی را که در اینجا می‌توان مدنظر داشت، جمع کل تراکنش‌‌های سیستم است. به نظر شما خروجی کوئری فوق چیست؟
خروجی SQL کوئری فوق به نحو زیر است:
SELECT 
         [GroupBy1].[A1] AS [C1]
         FROM ( SELECT 
                    SUM([Extent1].[Amount]) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
                    )  AS [GroupBy1]
و خروجی واقعی آن استثنای زیر می‌باشد:
 Arithmetic overflow error converting expression to data type int.
بله. محاسبه ممکن نیست؛ چون جمع حاصل از بازه اعداد صحیح خارج شده است.

راه حل:
نیاز است جمع را بر روی Int64 بجای Int32 انجام دهیم:
var sum2 = context.Transactions.Sum(x => (Int64)x.Amount);

SELECT 
      [GroupBy1].[A1] AS [C1]
         FROM ( SELECT 
                    SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
               )  AS [GroupBy1]                  


مثال دوم: سیستم باید بتواند با نبود رکوردها نیز صحیح کار کند
برای نمونه کوئری زیر را بر روی بازه‌ا‌ی که سیستم عملکرد نداشته است، در نظر بگیرید:
var date = DateTime.Now.AddDays(10);
var sum3 = context.Transactions
                  .Where(x => x.AddDate > date)  
                  .Sum(x => (Int64)x.Amount);               
یک چنین خروجی SQL ایی دارد:
SELECT 
     [GroupBy1].[A1] AS [C1]
        FROM ( SELECT 
                    SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
                    WHERE [Extent1].[AddDate] > @p__linq__0
              )  AS [GroupBy1]
اما در سمت کدهای ما با خطای زیر متوقف می‌شود:
The cast to value type 'Int64' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
راه حل: استفاده از نوع‌های nullable در اینجا ضروری است:
var date = DateTime.Now.AddDays(10);
var sum3 = context.Transactions
                  .Where(x => x.AddDate > date)
                  .Sum(x => (Int64?)x.Amount) ?? 0;
به این ترتیب، خروجی صفر را بدون مشکل، دریافت خواهیم کرد.

مثال سوم: حالت‌های خاص استفاده از خواص راهبری
کوئری زیر را در نظر بگیرید:
 var sum4 = context.Bills.First().Transactions.Sum(x => (Int64?)x.Amount) ?? 0;
در اینجا قصد داریم جمع تراکنش‌های صورتحساب اول را بدست بیاوریم که از طریق استفاده از خاصیت راهبری Transactions کلاس Bill، به نحو فوق میسر شده است. به نظر شما خروجی SQL آن به چه صورتی است؟
SELECT 
     [Extent1].[Id] AS [Id], 
     [Extent1].[AddDate] AS [AddDate], 
     [Extent1].[Amount] AS [Amount], 
     [Extent1].[BillId] AS [BillId]
   FROM [dbo].[Transactions] AS [Extent1]
   WHERE [Extent1].[BillId] = @EntityKeyValue1
بله! در اینجا خبری از Sum نیست. ابتدا کل اطلاعات دریافت شده و سپس جمع و منهای نهایی در سمت کلاینت بر روی آن‌ها انجام می‌شود؛ که بسیار ناکارآمد است. (قرار است این مورد ویژه، در نگارش‌های بعدی بهبود یابد)
راه حل کنونی:
var entry = context.Bills.First();
var sum5 = context.Entry(entry).Collection(x => x.Transactions).Query().Sum(x => (Int64?)x.Amount) ?? 0;
در اینجا باید از روش خاصی که مشاهده می‌کنید جهت کار با خواص راهبری استفاده کرد و نکته اصلی آن استفاده از متد Query است. حاصل کوئری LINQ فوق اینبار SQL مطلوب زیر است که سمت سرور عملیات جمع را انجام می‌دهد و نه سمت کلاینت:
SELECT 
    [GroupBy1].[A1] AS [C1]
     FROM ( SELECT 
               SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                   FROM [dbo].[Transactions] AS [Extent1]
                    WHERE [Extent1].[BillId] = @EntityKeyValue1
            )  AS [GroupBy1]                  


نکاتی که در اینجا ذکر شدند در مورد تمام توابع تجمعی مانند Sum، Count، Max و Min و غیره صادق هستند و باید به آن‌ها نیز دقت داشت.