مطالب
سری بررسی SQL Smell در EF Core - استفاده از مدل Entity Attribute Value - بخش اول
یکی از چالش‌های دیتابیس‌های رابطه‌ایی، ذخیره‌سازی داده‌هایی با ساختار داینامیک است. در حالت عادی، یک جدول مجموعه‌ایی از موجودیت‌ها است. هر موجودیت نیز شامل یکسری ویژگی‌های (Attributes) مشخص می‌باشد. اما شرایطی را در نظر بگیرید که تعداد این ویژگی‌ها به صورت مشخص و ثابتی نباشد؛ یعنی برای هر موجودیت، ویژگی‌های متفاوتی داشته باشیم. یک روش پیاده‌سازی اینچنین سناریوهایی، استفاده از مدلی با نام Entity Attribute Value است. در این روش ستون‌های داینامیک را درون یک جدول جنریک تعریف خواهیم کرد. به عنوان مثال برای ذخیره‌سازی اطلاعات اشخاص، در حالت نرمال، یک جدول با ساختار مشخصی خواهیم داشت: 
create table Employees
(
   Id int auto_increment
   primary key,
   FirstName text null,
   LastName text null,
   DateOfBirth timestamp not null
);
تعریف جدول فوق نیز در Entity Framework به اینصورت خواهد بود:
public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTimeOffset DateOfBirth { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseMySQL(_configuration.GetConnectionString("DataConnection"));
    }
}


اما در مدل EAV، خواص داینامیک را به درون جدول دومی منتقل خواهیم کرد:

create table EmployeeEav
(
   Id int auto_increment
   primary key
);

create table EmployeeAttributes
(
  Id int auto_increment
  primary key,
  EmployeeId int not null,
  AttributeName text null,
  AttributeValue text null,
  constraint FK_EmployeeAttributes_EmployeeEav_EmployeeId
  foreign key (EmployeeId) references EmployeeEav (Id)
  on delete cascade
);

create index IX_EmployeeAttributes_EmployeeId
on EmployeeAttributes (EmployeeId);

تعریف جداول فوق نیز در Entity Framework به اینصورت خواهند بود:

public class EmployeeEav
{
    public int Id { get; set; }
    public virtual ICollection<EmployeeAttribute> Attributes { get; set; }
}

public class EmployeeAttribute
{
    public int Id { get; set; }
    public virtual EmployeeEav Employee { get; set; }
    public int EmployeeId { get; set; }
    public string AttributeName { get; set; }
    public string AttributeValue { get; set; }
}

public class MyDbContext : DbContext
{

    public DbSet<EmployeeEav> EmployeeEav { get; set; }
    public DbSet<EmployeeAttribute> EmployeeAttributes { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseMySQL(_configuration.GetConnectionString("DataConnection"));
    }
}



درون این جدول دوم، سه فیلد اصلی داریم: یکی به عنوان Entity که در اینجا یک ارجاع را به جدول EmployeeEav دارد. یک فیلد به عنوان Attribute که برای تعیین نام ویژگی داینامیک استفاده می‌شود و در نهایت یک Value که برای ذخیره‌سازی مقدار ویژگی مورد استفاده قرار میگیرد. بنابراین به این نوع طراحی، Entity Attribute Value گفته می‌شود. مزیت اصلی این روش، انعطاف زیاد آن است در واقع می‌توانیم N تعداد ویژگی را برای Entity موردنظرمان داشته باشیم. اما این روش یک SQL Smell است و اشکالات زیادی را به همراه دارد:

  • کوئری گرفتن در این روش سخت است
یکی از مشکلات اصلی این روش این است امکان کوئری گرفتن از جدول ویژگی‌ها را سخت میکند. در واقع این روش به store everything, query nothing معروف است. مثلاً فرض کنید می‌خواهیم لیست کارمندانی را که تاریخ تولدشان ۲۵ سال پیش است، واکشی کنیم. در حالت عادی با تعداد ستون ثابت می‌توانیم به راحتی اینکار را انجام دهیم:
SELECT `e`.`Id`, `e`.`DateOfBirth`, `e`.`FirstName`, `e`.`LastName`
FROM `Employees` AS `e`
WHERE `e`.`DateOfBirth` > @__endDate_0
کوئری LINQ کد فوق اینچنین شکلی خواهد داشت:
var endDate = DateTimeOffset.Now.AddYears(Convert.ToInt32(-25));
var normalTypes = dbContext.Employees.Where(x => x.DateOfBirth > endDate).ToList();
اما در مدل EAV نوشتن کوئری فوق خیلی سخت‌تر خواهد بود: 
SELECT MAX(CASE AttributeName
               WHEN 'FirstName'
                   THEN AttributeValue
    END)        AS FirstName,
       MAX(CASE AttributeName
               WHEN 'LastName'
                   THEN AttributeValue
           END) AS LastName,
       MAX(CASE AttributeName
               WHEN 'DateOfBirth'
                   THEN AttributeValue
           END) AS DateOfBirth
FROM efcoresample.EmployeeAttributes
WHERE EmployeeId IN (SELECT EmployeeId
                     FROM efcoresample.EmployeeAttributes
                     WHERE AttributeName = 'DateOfBirth'
                       AND AttributeValue > DATE_SUB(CURRENT_DATE(), INTERVAL 25 YEAR))
  AND AttributeName IN ('FirstName', 'LastName', 'DateOfBirth')
GROUP BY EmployeeId;
همچنین کوئری LINQ آن نیز به همان اندازه سخت میباشد: 
string[] columnNames = {"FirstName", "LastName", "DateOfBirth"};
var employees = dbContext.EmployeeAttributes
    .Where(x => 
                dbContext.EmployeeAttributes
                    .Where(i => i.AttributeName == "DateOfBirth")
                    .Select(eId => eId.EmployeeId).Contains(x.EmployeeId) &&
                columnNames.Contains(x.AttributeName))
    .GroupBy(x => x.EmployeeId)
    .Select(g => new
    {
        FirstName = g.Max(f => f.AttributeName == "FirstName" ? f.AttributeValue : ""),
        LastName = g.Max(f => f.AttributeName == "LastName"? f.AttributeValue : ""),
        DateOfBirth = g.Max(f => f.AttributeName == "DateOfBirth"? f.AttributeValue : ""),
        Id = g.Key
    })
    .ToList()
    .Where(x => DateTime.ParseExact(x.DateOfBirth, "yyyy-MM-dd", CultureInfo.InvariantCulture) > DateTime.Now.AddYears(-25));

  • امکان تعریف فیلدهای اجباری را نخواهیم داشت
در حالت نرمال و ساختاریافته، برای هرکدام از فیلدها می‌توانیم الزامی و یا اختیاری بودن آنها را به راحتی با NOT NULL تعیین کنیم. اما در مدل EAV این امکان را نخواهیم داشت. 

  • امکان تعیین نوع ستون‌ها را نخواهیم داشت
در حالت نرمال به راحتی می‌توانیم نوع فیلد موردنظر را تعیین کنیم. اما در مدل EAV به دلیل ماهیت داینامیک ستون‌ها، این امکان را نداریم. ستون AttributeValue همزمان ممکن است تاریخ، عددی، اعشاری و… باشد در نتیجه چون از ورودی مطمئن نیستیم، مجبوریم تایپ آن را به رشته تنظیم کنیم. 

  • امکان تعریف کلیدهای خارجی را نخواهیم داشت
در مدل EAV نمی‌توانیم صحت دیتا را تضمین کنیم؛ زیرا امکان تعریف کلید خارجی را نخواهیم داشت.

بنابراین بهتر است تا حد امکان از مدل EAV استفاده نشود؛ مگر اینکه در شرایطی خاص، مجبور به استفاده‌ی از آن باشید. به عنوان مثال برنامه‌ی شما قرار است قابلیت ایمپورت هر نوع فایل CSV را داشته باشد. هر فایل هم ممکن است به تعداد نامشخصی، یکسری ستون را داشته باشد. در این شرایط می‌توانید با در نظر گرفتن موارد فوق، از مدل مطرح شده استفاده کنید.
نظرات مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
بهبود قسمت «ارسال کوئری استرینگ‌ها به کامپوننت‌های مختلف » در Blazor 6x

در Blazor 6x دیگر نیازی نیست تا با استفاده از «HttpUtility.ParseQueryString»، کار پردازش دستی کوئری استرینگ‌های رسیده، صورت گیرد. برای نمونه فرض کنید Url رسیده، چنین شکلی را دارد:
/search?filter=some+stuff&page=3&assignee=User1&assignee=User2
برای دسترسی به مقادیر کوئری استرینگ‌های آدرس فوق، اینبار کافی است به صورت زیر عمل کنیم:
@code {
    [Parameter]
    [SupplyParameterFromQuery]
    public string Filter { get; set; }

    [Parameter]
    [SupplyParameterFromQuery]
    public int? Page { get; set; }

    [Parameter]
    [SupplyParameterFromQuery(Name = "assignee")]
    public string[] Assignees { get; set; }
}
یعنی ذکر دو ویژگی [Parameter, SupplyParameterFromQuery] بر روی یک خاصیت عمومی کامپوننت، آن‌را آماده‌ی دریافت مقادیر کوئری استرینگ‌های متناظری می‌کند. اگر می‌خواهید نام پارامتر، با نام کوئری استرینگ یکی نباشد (حالت پیش‌فرض)، از خاصیت Name این ویژگی می‌توان استفاده کرد.

نکته: در اینجا مقادیری مانند ذیل قابل قبول هستند:
String, bool, DateTime, decimal, double, float, Guid, int, long
و یا حالت Nullable آن‌ها و یا آرایه‌ای آن‌ها.

امکان ساخت Urlهایی به همراه کوئری استرینگ‌ها جهت هدایت ساده‌تر به آن‌ها

به Blazor 6x، متدهای الحاقی UriWithQueryParameter نیز اضافه شده‌اند و کار آن‌ها، افزودن ساده‌تر کوئری‌استرینگ‌ها به Urlها است که نمونه‌ای از آن به صورت زیر است:
var actualUri = NavigationManager.GetUriWithQueryParameters(new Dictionary<string, object>
        {
            ["full name"] = "John Doe", // Single value
            ["ping"] = new int?[] { 35, 16, null, 87, 240 }
        });
که چنین Url ای را تولید می‌کند (نمونه‌ای از روش تعریف کوئری استرینگ‌های آرایه‌ای):
host/?full%20name=John%20Doe&ping=35&ping=16&ping=87&ping=240
مثال‌های بیشتر آن‌را در اینجا می‌توانید مشاهده کنید.
نظرات مطالب
ASP.NET MVC #21
- مراجعه کنید به دوره «استفاده از افزونه‌ها و امکانات jQuery در ASP.NET MVC» برای تکمیل بحث. برای نمونه مطلب «افزونه‌ای برای کپسوله سازی نکات ارسال یک فرم ASP.NET MVC به سرور توسط jQuery Ajax» اطلاعات یک فرم رو به صورت خودکار توسط متد form.serialize به سرور ارسال می‌کند.
- نمی‌تونید از Ajax معمولی (یا به عبارتی XMLHttpRequest) برای ارسال فایل استفاده کنید. یا باید از سیلورلایت یا فلش استفاده کنید، یا از
مرورگرهایی که XMLHttpRequest Level 2 را پشتیبانی کنند (از IE 10 به بعد مثلا)، امکان Ajax upload توکار به همراه گزارش درصد آپلود را بدون نیاز به فلش یا سیلورلایت، دارند.یک نمونه پیاده سازی آن 
نظرات مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
- یک سری متد کمکی اضافی برای کار با T4MVC که به صورت پیش فرض در MVC وجود ندارد؛ خصوصا برای کار با متدهایی که پارامتر Result دارند و متدهایی مانند MVC.Comments.Index را قبول می‌کنند.
- خیر. این روش کلی کار کتابخانه T4MVC است برای فراهم آوردن امکان overriding مسایل توکار خودش به همراه افزودن یک سری تعاریف قابل استفاده در کنترلر جاری. مثلا زمانی که می‌نویسند View.MyPartialViewName، نیاز است کنترلر جاری partial تعریف شود تا به رشته یاد شده که توسط T4MVC اضافه می‌شود دسترسی یافت.
در کل کار کردن با T4MVC علاوه بر دسترسی strongly typed به مسیرها و فایل‌های موجود پروژه، سرعت نهایی رندر شدن را هم بالا می‌برد؛ چون مسیرهایی را که ایجاد می‌کند، نیاز به کمترین حدس‌زدن و سعی و خطا را توسط موتور View جاری برای یافتن منبع مورد نظر دارد.
مطالب
دسترسی به Collectionها در یک ترد دیگر در WPF
اگر در WPF سعی کنیم آیتمی را به مجموعه اعضای یک Collection مانند یک List یا ObservableCollection از طریق تردی دیگر اضافه کنیم، با خطای ذیل متوقف خواهیم شد:
 This type of CollectionView does not support changes to its SourceCollection
from a thread different from the Dispatcher thread
راه حلی که برای آن تا دات نت 4 در اکثر سایت‌ها توصیه می‌شد به نحو ذیل است:
Adding to an ObservableCollection from a background thread


مشکل!
اگر همین برنامه را که برای دات نت 4 کامپایل شده‌است، بر روی سیستمی که دات نت 4.5 بر روی آن نصب است اجرا کنیم، برنامه با خطای ذیل متوقف می‌شود:
 System.InvalidOperationException: This exception was thrown because the generator for control
'System.Windows.Controls.ListView Items.Count:62' with name '(unnamed)' has received sequence of
CollectionChanged events that do not agree with the current state of the Items collection.
The following differences were detected:  Accumulated count 61 is different from actual count 62.


مشکل از کجاست؟
در دات نت 4 و نیم، دیگر نیازی به استفاده از کلاس MTObservableCollection یاد شده نیست و به صورت توکار امکان کار با Collectionها از طریق تردی دیگر میسر است. فقط برای فعال سازی آن باید نوشت:
 private static object _lock = new object();
//...
BindingOperations.EnableCollectionSynchronization(persons, _lock);
پس از اینکه برای نمونه، مجموعه‌ی فرضی persons وهله سازی شد، تنها کافی است متد جدید EnableCollectionSynchronization بر روی آن فراخوانی شود.


برای برنامه‌ی دات نت 4 ایی که قرار است در سیستم‌های مختلف اجرا شود چطور؟

در اینجا باید از Reflection کمک گرفت. اگر متد EnableCollectionSynchronization بر روی کلاس BindingOperations یافت شد، یعنی برنامه‌ی دات نت 4، در محیط جدید در حال اجرا است:
public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
      MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization",
             new Type[] { typeof(IEnumerable), typeof(object) });
      if (method != null)
      {
         method.Invoke(null, new object[] { collection, lockObject });
      }
}
در این حالت فقط کافی است این متد جدید یافت شده را بر روی Collection مدنظر فراخوانی کنیم.
همچنین اگر بخواهیم کلاس MTObservableCollection معرفی شده را جهت سازگاری با دات نت 4 و نیم به روز کنیم، به کلاس ذیل خواهیم رسید. این کلاس با دات نت 4 و 4.5 سازگار است و جهت کار با ObservableCollectionها از طریق تردهای مختلف تهیه شده‌است:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;

namespace WpfAsyncCollection
{
    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        private static object _syncLock = new object();

        public AsyncObservableCollection()
        {
            enableCollectionSynchronization(this, _syncLock);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            using (BlockReentrancy())
            {
                var eh = CollectionChanged;
                if (eh == null) return;

                var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                  let dpo = nh.Target as DispatcherObject
                                  where dpo != null
                                  select dpo.Dispatcher).FirstOrDefault();

                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }

        private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
        {
            var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
                                    new Type[] { typeof(IEnumerable), typeof(object) });
            if (method != null)
            {
                // It's .NET 4.5
                method.Invoke(null, new object[] { collection, lockObject });
            }
        }
    }
}
در این کلاس، در سازنده‌ی آن متد عمومی enableCollectionSynchronization فراخوانی می‌شود. اگر برنامه در محیط دات نت 4 فراخوانی شود، تاثیری نخواهد داشت چون method در حال بررسی نال است. در غیراینصورت، برنامه در حالت سازگار با دات نت 4.5 اجرا خواهد شد.
مطالب
Test Driven Development
نوشتن  تست برای نرم افزار امری ضروریست، چه پس از تولید نرم افزار چه در حین تولید، در کل به وسیله تست می‌توان از به وجود آمدن باگ‌ها در هنگام گسترش دادن برنامه تا حد قابل توجهی جلوگیری کرد.
از معروف ترین روش‌های تست می‌توان عناوین زیر را نام برد:
  • Unit test
  • Integration test
  • Smoke test
  • Regression test
  • Acceptance test

 Test Driven Development  یک پروسه تولید نرم افزار است که برای اولین بار توسط Kent_Beck معرفی شد.
TDD شامل 4 مرحله کلی است:
  1. نوشتن تست قبل از نوشتن کد.
  2. کامپایل کردن کد و اطمینان از Fail شدن کامپایل.
  3. پیاده سازی کد به طوری که تست ما پاس شود.
  4. Refactoring 
مراحل 4 گانه تست باید به صورت متناوب اجرا شوند.
البته بسیاری این 4 مورد را با عبارت  red/green/refactor نیز می‌شناسند.

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

البته باید به این نکته نیز اشاره داشت که مایکروسافت تحقیقی انجام داده که بر طبق آن نوشتن کد به روش TDD می‌تواند 15 تا 30 در صد روند تولید نرم افزار را افزایش دهد ولی در عوض بین 40 تا 90 در صد می‌تواند از به وجود آمدن باگ جدید جلوگیری کند.
در بسیاری از محیط‌های برنامه نویسی، نه تنها به این موضوع اهمیت داده نمی‌شود بلکه به طور کلی به اشتباه گرفته شده و حتی در پروژه هایی که تست نوشته می‌شود مفاهیم آن( که در بالا نام برده شده )جابجا شده و به اشتباه نام برده می‌شود.
هدف از نوشتن تست،تست کردن قطعات کوچک کد است,به عنوان مثال نباید تست به گونه ای باشد که یک متد با 300 خط کد را تحت پوشش قرار دهد.ابتدا باید کد به قطعات کوچک شکسته و بعد تست شود.
یک نمونه از متد تست:
        [Test]
        public void TestFullName()
        {
            
            Person person = new Person ();
            person.lname = "Doe";
            person.mname = "Roe";
            person.fname = "John";

            string actual = person.GetFullName();
            string expected = "John Roe Doe";
            Assert.AreEqual(expected, actual, ”The GetFullName returned a different Value”);
        }
هدف از نوشتن این پست مقدمه ای بر شروع سری پست‌های TDD باستفاده از MVC.Net و فریم ورک قدرت مند تست Nunit است. 
نظرات مطالب
آموزش LINQ بخش سوم
با سلام؛ دستور زیر رو چگونه می‌توان با linq شبیه سازی کرد؟
Select * from document where code between "0001" and "00010"
لازم به ذکر است که فیلد کد از نوع رشته است.