نظرات مطالب
راه اندازی دیتابیس postgresql در برنامه‌های ASP.NET Core – قسمت 2
یک نکته‌ی تکمیلی: بهبود کارآیی متدهای async با کمتر کردن کار آن‌ها

اگر بدنه‌ی متدهای async سرویس شما، تک سطری بودند (فقط یک return await بودند)، مانند مثال زیر:
public async Task<Blog?> Get(uint id)
{
    return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id);
}
نیازی به ذکر async و await نیست؛ فقط خود Task را بازگشت دهید. افزونه‌هایی مانند AsyncFixer و Asyncify این نوع نکات را بهتر مشخص می‌کنند. اطلاعات بیشتر
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #1
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشت‌ها بی اطلاع می‌ماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائه‌ی روشی جدید می‌کنم که با استفاده از Attribute‌ها تنظیمات نگاشت اشیاء را در AutoMapper انجام می‌دهد.
در نهایت می‌خواهیم نگاشت‌ها را اینچنین تنظیم کنیم:
 [MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
 public class AdminStudentViewModel
 {
     // [IgnoreMap]
     public int Id { set; get; }

     [MapForMember("Name")]
     public string FirstName { set; get; }

     [MapForMember("Family")]
     public string LastName { set; get; }

     public string Email { set; get; }

     [MapForMember("RegisterDateTime")]
     public string RegisterDateTimePersian { set; get; }

     [UseValueResolver(typeof (BookCountValueResolver))]
     public int BookCounts { set; get; }

     [UseValueResolver(typeof (BookPriceValueResolver))]
     public decimal BookPrice { set; get; }
 };
  این سبک تنظیم کردن نگاشت‌های اشیاء به نظر بهتر از روش‌های دیگر است؛ چون کلاس‌های ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفاده‌ی از ویژگی‌ها، یک شیوه‌ی کاری معمول به حساب می‌آید. 
به تعریف و توضیح صفت‌های (ویژگی‌ها یا Attributes) مورد نیاز می‌پردازم:

صفت MapFromAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapFromAttribute : Attribute
{
    public Type SourceType { get; private set; }
    public bool IgnoreAllNonExistingProperty { get; private set; }
    public bool AlsoCopyMetadata { get; private set; }    //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576

    public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false,
        bool alsoCopyMetadata = false)
    {
        SourceType = sourceType;
        IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty;
        AlsoCopyMetadata = alsoCopyMetadata;
    }
};
این صفت روی کلاس‌ها می‌نشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص می‌کند. در واقع همه چیز از اینجا شروع می‌شود. همچنین آرگومان ignoreAllNonExistingProperty مشخص می‌کند کلیه‌ی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آن‌ها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمی‌شود؛ ولی می‌تواند پرچمی باشد تا اجازه دهد  Data Annotations از مدل‌های ef به ViewModel انتقال یابند.


صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreMapAttribute : Attribute {};
از این صفت برای رد کردن خصیصه‌‌ای در نگاشت‌ها استفاده می‌کنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که می‌تواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفاده‌ی از این صفت می‌کند»


صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)]
public class MapForMemberAttribute : Attribute
{
    public string MemberToMap { get; private set; }
    public MapForMemberAttribute(string memberToMap)
    {
        MemberToMap = memberToMap;
    }
};
اگر نام خصیصه‌ها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده می‌کنیم.


صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)]
public class UseValueResolverAttribute : Attribute
{
    public IValueResolver ValueResolver { get; private set; }
    public UseValueResolverAttribute(Type valueResolver)
    {
        ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver;
    }
};
استفاده از ValueResolver‌ها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده می‌شود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل می‌شود.

تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفت‌ها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
مطالب
نحوه نمایش تمام آیکون‌های تعریف شده در یک قلم در WPF
سال نو مبارک! به امید روزهایی شاد، سلامت و پر برکت.

پیرو مطلب قلم‌هایی حاوی آیکون که خصوصا در برنامه‌های مترو بیشتر مرسوم شده‌اند، شاید بد نباشد کار برنامه Character Map ویندوز را با WPF شبیه سازی کنیم.
ابتدا Model و ViewModel  این برنامه را درنظر بگیرید:
namespace CrMap.Models
{
    public class Symbol
    {
        public char Character { set; get; }
        public string CharacterCode { set; get; }
    }
}

using System;
using System.Collections.Generic;
using System.Windows.Media;
using CrMap.Models;

namespace CrMap.ViewModels
{
    public class CrMapViewModel
    {
        public IList<Symbol> Symbols { set; get; }
        public int GridRows { set; get; }
        public int GridCols { set; get; }

        public CrMapViewModel()
        {
            fillDataSource();
        }

        private void fillDataSource()
        {
            Symbols = new List<Symbol>();
            GridCols = 15;

            var fontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/Fonts/#whhglyphs");

            GlyphTypeface glyph = null;
            foreach (var typeface in fontFamily.GetTypefaces())
            {
                if (typeface.TryGetGlyphTypeface(out glyph) && (glyph != null))
                    break;
            }

            if (glyph == null)
                throw new InvalidOperationException("Couldn't find a GlyphTypeface.");

            GridRows = (glyph.CharacterToGlyphMap.Count / GridCols) + 1;

            foreach (var item in glyph.CharacterToGlyphMap)
            {
                var index = item.Key;
                Symbols.Add(new Symbol
                {
                    Character = Convert.ToChar(index),
                    CharacterCode = string.Format("&#x{0:X}", index)
                });
            }
        }
    }
}
توضیحات:

یک سری قابلیت جالب در WPF برای استخراج اطلاعات قلم‌ها وجود دارند که در فضای نام System.Windows.Media اسمبلی PresentationCore.dll قرار گرفته‌اند. برای نمونه پس از وهله سازی FontFamily بر اساس یک قلم مدفون شده در برنامه، امکان استخراج تعداد گلیف‌های موجود در این قلم وجود دارد که نحوه انجام آن‌را در متد fillDataSource ملاحظه می‌کنید.
این اطلاعات استخراج شده و لیست Symbols برنامه را تشکیل می‌دهند. در نهایت برای نمایش این اطلاعات، از ترکیب ItemsControl و UniformGrid استفاده خواهیم کرد:
<Window x:Class="CrMap.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:CrMap.ViewModels"
        Title="MainWindow" WindowStartupLocation="CenterScreen" WindowState="Maximized"
        Height="350" Width="525">
    <Window.Resources>
        <vm:CrMapViewModel x:Key="vmCrMapViewModel" />
    </Window.Resources>
    <ScrollViewer VerticalScrollBarVisibility="Visible">
        <ItemsControl
                      DataContext="{StaticResource vmCrMapViewModel}"  
                      ItemsSource="{Binding Symbols}"
                      Name="MainItemsControl"
                      VerticalAlignment="Top"     
                      HorizontalAlignment="Center"
                      Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Columns="{Binding GridCols}" 
                        Rows="{Binding GridRows}">
                    </UniformGrid>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ContentControl>
                        <Border BorderBrush="SlateGray"
                            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                            BorderThickness="1" CornerRadius="3" Margin="3">
                            <StackPanel Margin="3" Orientation="Vertical">
                                <TextBlock                                         
                                    HorizontalAlignment="Center" 
                                    VerticalAlignment="Center" 
                                    FontFamily="Fonts/#whhglyphs"
                                    Foreground="DarkRed"
                                    FontSize="17"
                                    Text="{Binding Character}" />
                                <TextBlock                                         
                                    HorizontalAlignment="Center" 
                                    VerticalAlignment="Center" 
                                    Text="{Binding CharacterCode}" />
                            </StackPanel>
                        </Border>
                    </ContentControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>
</Window>


دریافت مثال این مطلب
CrMap.zip
 
مطالب
پیاده سازی Full-Text Search با SQLite و EF Core - قسمت دوم - کوئری گرفتن از جدول مجازی FTS
پس از آشنایی با نحوه‌ی ایجاد و به روز رسانی جدول مجازی FTS، اکنون قصد داریم با روش‌های کوئری گرفتن از آن آشنا شویم. برای این منظور در ابتدا نیاز است تعدادی رکورد را در آن ثبت کنیم:
        private static void seedDb(ApplicationDbContext context)
        {
            if (!context.Chapters.Any())
            {
                var user1 = context.Users.Add(new User { Name = "Test User" });
                context.Chapters.Add(new Chapter
                {
                    Title = "Learn SQlite FTS5",
                    Text = "This tutorial teaches you how to perform full-text search in SQLite using FTS5",
                    User = user1.Entity
                });
                context.Chapters.Add(new Chapter
                {
                    Title = "Advanced SQlite Full-text Search",
                    Text = "Show you some advanced techniques in SQLite full-text searching",
                    User = user1.Entity
                });
                context.Chapters.Add(new Chapter
                {
                    Title = "SQLite Tutorial",
                    Text = "Help you learn SQLite quickly and effectively",
                    User = user1.Entity
                });
                context.Chapters.Add(new Chapter
                {
                    Title = "Handle markup in text",
                    Text = "<p>Isn't this <font face=\"Comic Sans\">funny</font>?",
                    User = user1.Entity
                });

                context.Chapters.Add(new Chapter
                {
                    Title = "آزمایش متن فارسی",
                    Text = "برای نمونه تهیه شده‌است",
                    User = user1.Entity
                });

                context.Chapters.Add(new Chapter
                {
                    Title = "Exclude test 1",
                    Text = "in the years 2018-2019 something happened.",
                    User = user1.Entity
                });
                context.Chapters.Add(new Chapter
                {
                    Title = "Exclude test 2",
                    Text = "It was 2018 and then it was 2019",
                    User = user1.Entity
                });

                context.SaveChanges();
            }
        }
در اینجا به صورت متداولی، اطلاعات در جدول اصلی Chapters ثبت می‌شوند و چون SaveChanges را در قسمت قبل جهت به روز رسانی خودکار جدول مجازی Chapters_FTS بازنویسی کردیم، فراخوانی آن، سبب تولید ایندکس‌های Full Text هم می‌شود.

ثبت اطلاعات فوق، چنین رکوردهایی را در جدول Chapters به وجود می‌آورد که شامل اطلاعات یونیکد، HTML ای و غیره است:



اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS به صورت مستقیم

کوئری‌های Full-text در SQLite، چنین شکل کلی را دارند و توسط تابع match انجام می‌شوند:
select * from Chapters_FTS where Chapters_FTS match "fts5"
که یک چنین خروجی را نیز به همراه دارد:


همانطور که مشاهده می‌کنید در اینجا تنها دو ستونی که ایندکس شده‌اند، در خروجی نهایی ظاهر می‌شوند؛ اما این جدول به همراه ستون‌های مخفی توکار دیگری نیز هست:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"
در این کوئری اینبار ستون‌های مخفی rank و همچنین rowid را نیز می‌توانید مشاهده کنید:


- Rowid با توجه به تعریفی که در قسمت قبل انجام دادیم:
CREATE VIRTUAL TABLE "Chapters_FTS"
USING fts5("Text", "Title", content="Chapters", content_rowid="Id")
به همان primary-key جدول اصلی chapters اشاره می‌کند. بنابراین اگر نیاز باشد تا این خروجی حاصل از کوئری بر روی جدول مجازی Chapters_FTS را به جدول اصلی chapters متصل کرد، می‌توان از مقدار rowid بازگشتی استفاده نمود.

- تمام جداول مجازی FTS، به همراه ستون مخفی rank نیز هستند که میزان نزدیک بودن خروجی حاصل را به کوئری درخواستی مشخص می‌کنند. این عدد توسط تابعی به نام bm25 تهیه می‌شود. اگر کوئری FTS به همراه قسمت where نباشد، مقدار rank همواره نال خواهد بود. اما اگر قسمت where به همراه match قید شود، مقدار rank، مقدار از پیش محاسبه شده‌ی تابع توکار bm25 است. به همین جهت کار با این مقدار از پیش محاسبه شده، سریعتر از فراخوانی مستقیم متد bm25 است. برای مثال دو کوئری زیر اساسا یکی هستند؛ اما دومی سریعتر است:
select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY bm25(fts);
select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY rank;

یک نکته: کوئری FTS فوق بر روی هر دو ستون title و text اجرا می‌شود (و یا هر ستون موجود دیگری که پیشتر ایندکس شده باشد).


اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS توسط EF Core

پس از آشنایی مقدماتی با کوئری نویسی FTS در SQLite، بر انجام یک چنین کوئری در EF Core می‌توان به صورت زیر عمل کرد:
- ابتدا باید یک موجودیت بدون کلید را مطابق ستون‌های مخفی و ایندکس شده‌ی بازگشتی تهیه کنیم:
namespace EFCoreSQLiteFTS.Entities
{
    public class ChapterFTS
    {
        public int RowId { get; set; }
        public decimal? Rank { get; set; }

        public string Title { get; set; }
        public string Text { get; set; }
    } 
}
همانطور که مشاهده می‌کنید، rank به صورت نال پذیر تعریف شده‌است؛ چون اگر قسمت where ذکر نشود، مقداری نخواهد داشت.
- سپس نیاز است این موجودیت بدون کلید را به EF معرفی کنیم:
namespace EFCoreSQLiteFTS.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
        //...

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<ChapterFTS>().HasNoKey().ToView(null);
        }

        //...
    }
}
در اینجا ChapterFTS تهیه شده، با متد HasNoKey علامتگذاری می‌شود تا آن‌را بتوان بدون مشکل در کوئری‌های EF استفاده کرد. همچنین فراخوانی ToView(null) سبب می‌شود تا EF Core جدولی را در حین Migration از روی این موجودیت ایجاد نکند و آن‌را به همین حال رها کند.

- و در آخر روش کوئری گرفتن از جدول مجازی FTS در EF Core به صورت زیر می‌باشد که توسط متد FromSqlRaw به صورت پارامتری (مقاوم در برابر حملات تزریق اس‌کیوال)، قابل انجام است:
const string ftsSql = "SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH {0}";
foreach (var chapter in context.Set<ChapterFTS>().FromSqlRaw(ftsSql, "fts5"))
{
  Console.WriteLine($"Title: {chapter.Title}");
  Console.WriteLine($"Text: {chapter.Text}");
}


بررسی قابلیت‌های ویژه‌ی کوئری‌های FTS در SQLite

اکنون که با روش کلی کوئری گرفتن از جدول مجازی FTS آشنا شدیم، نکات ویژه‌ی آن‌را بررسی می‌کنیم و در اینجا بیشتر پارامتر ذکر شده‌ی پس از عملگر match تغییر خواهد کرد و مابقی قسمت‌های آن ثابت و مانند قبل هستند.

بجای عملگر match می‌توان از = نیز استفاده کرد

دو کوئری زیر دقیقا به یک معنا هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5";
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS = "fts5";
و هر دو همانطور که عنوان شد بر روی تمام ستون‌های ایندکس شده‌ی موجود اجرا می‌شوند و اگر نیاز است نتایج را بر اساس میزان نزدیکی آن‌ها به کوئری انجام شده مرتب کرد، می‌توان یک ORDER by rank را نیز به انتهای آن‌ها افزود:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER by rank;


جستجوهایی به همراه واژه‌هایی در کنار هم

از دیدگاه FTS، دو کوئری زیر که در قسمت match آن‌ها، واژه‌ها با فاصله در کنار هم قرار گرفته‌اند، یکی هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn SQLite" ORDER by rank;
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn + SQLite" ORDER by rank;
و هر دو خروجی زیر را تولید می‌کنند:


علت اینجا است که یک full-text search بر اساس ایندکس شدن واژه‌ها تولید می‌شود و هر کدام از این واژه‌ها به یک توکن نگاشت خواهند شد. به همین جهت است که در اینجا تفاوتی بین + و فاصله در عبارت جستجو شده وجود ندارد. در این حالت اگر در یکی از ستون‌های ایندکس شده، واژه‌ی learn و یا واژه‌ی SQLite بکار رفته باشد، در خروجی نهایی لیست خواهد شد.


امکان جستجو بر اساس پیشوندها

می‌توان با استفاده از *، تمام توکن‌های ایندکس شده و شروع شده‌ی با واژه‌ی مشخصی را جستجو کرد:
 SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search*" ORDER by rank;
برای مثال در اینجا رکوردهایی که دارای واژه‌هایی مانند search، searching و غیره هستند، بازگشت داده می‌شوند:



امکان استفاده از عملگرهای بولی NOT، AND و OR

اگر learn text را جستجو کنیم:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn text" ORDER by rank;


رکوردی با ID مساوی 1 بازگشت داده می‌شود. اما اگر نیاز باشد رکوردی بازگشت داده شود که حاوی learn باشد، اما text خیر، می‌توان از عملگر NOT استفاده کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn NOT text" ORDER by rank;


که اینبار رکوردی با ID مساوی 3 را بازگشت داده‌است.

نکته‌ی مهم: عملگرهای بولی FTS مانند AND، OR، NOT و غیره باید با حروف بزرگ قید شوند.

در ادامه مثال دیگری از ترکیب عملگرهای بولی را مشاهده می‌کنید:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND sqlite OR help" ORDER by rank;


که تقدم و تاخر این عملگرها را می‌توان توسط پرانتزها به صورت صریحی نیز مشخص کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND (sqlite OR help)" ORDER by rank;



امکان ذکر صریح ستون‌های مدنظر در کوئری

همانطور که عنوان شد، حالت پیش‌فرض جستجوهای تمام متنی، جستجوی واژه‌ی مدنظر در تمام ستون‌های ایندکس شده‌است؛ اما شاید این مورد مدنظر شما نباشد. به همین منظور می‌توان ابتدا نام ستون مدنظر را ذکر کرد و پس از آن یک : را قرار داد تا فقط جستجو بر روی آن ستون خاص صورت گیرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "text:some AND title:sqlite" ORDER by rank;


امکان ترکیب نام ستون‌ها به صورت {col2 col1 col3} نیز وجود دارد.

نکته‌ی مهم! در جستجوهای FTS در SQLite، ذکر - به معنای قید صریح نام یک ستون خاص است (و یا لیست ستون‌هایی به صورت {col2 col1 col3}-) که قرار نیست چیزی با آن(ها) انطباق داده شود (- شبیه به عملگر NOT عمل می‌کند؛ اینبار در مورد ستون‌ها) و این مورد عموما تازه‌کاران را به اشتباه می‌اندازد. برای مثال در ابتدای بحث، دو رکورد را که دارای text ای مساوی عبارات زیر هستند، ثبت کردیم:
"in the years 2018-2019 something happened"
"It was 2018 and then it was 2019"
اکنون فرض کنید می‌خواهیم 2018-2019 را جستجو کنیم:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "2018-2019" ORDER by rank;
خروجی آن خطای زیر است و عنوان می‌کند که ستون 2019 تعریف نشده‌است؛ چون پس از -، به دنبال نام یک ستون ایندکس شده می‌گردد:
Execution finished with errors.
Result: no such column: 2019
برای رفع این مشکل می‌توان - را حذف کرد:


و یا می‌توان عبارت جستجو شده را بین "" قرار داد:

SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH '"2018-2019"' ORDER by rank;


و یا حتی می‌توان '"2018 2019"' را نیز جستجو کرد که نتیجه‌ی مشابهی را ارائه می‌دهد.


امکان جستجوی بر روی عبارات یونیکد

FTS5 و آخرین نگارش SQLite، به همراه tokenizer مخصوص یونیکد نیز هست و با اینگونه جستجوهای تمام متنی، مشکلی ندارد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "آزمایش"
ORDER by rank;



توابع کمکی FTS در SQLite برای متمایز سازی عبارات یافت شده‌ی در متن

فرض کنید می‌خواهیم واژه‌ی fts5 را جستجو کرده و همچنین در خروجی نهایی، هرجائیکه fts5 قرار دارد، آن‌را به صورت bold نمایش دهیم. برای اینکار، تابع توکار highlight قابل استفاده‌است. اما اگر در این بین خواستیم فقط قسمت کوتاهی از متن مورد نظر را که به جستجوی ما نزدیک است نمایش دهیم، می‌توان از متد توکار snippet استفاده کرد:
SELECT rowid, highlight(Chapters_FTS, title, '<b>', '</b>') as title,
snippet(Chapters_FTS, text, '<b>', '</b>', '...', 64) as text, rank FROM Chapters_FTS
WHERE Chapters_FTS MATCH "fts5" ORDER BY rank


نکته‌ی مهم: چون بر اساس نکات قسمت قبل، متنی که به Chapters_FTS  ارسال می‌شود، نرمال سازی شده‌است، متدهای فوق کارآیی خودشان را از دست می‌دهند. برای مثال اگر در کوئری فوق، واژه‌ی funny را که به یک رکورد HTML ای اشاره می‌کند، جستجو کنیم، خروجی زیر را دریافت خواهیم کرد:


خروجی نهایی، چون به جدول اصلی chapters متصل است، اصل متن را بازگشت می‌دهد، اما چون اطلاعاتی را که به Chapters_FTS  ارسال کرده‌ایم، فاقد تگ‌های HTML هستند، تا خروجی دقیقی حاصل شود، متدهای highlight و snippet دیگر قادر به علامتگذاری خروجی نهایی نبوده و اینکار را باید خودمان به صورت دستی در سمت کلاینت انجام دهیم.
مطالب
اعتبارسنجی در فرم‌های ASP.NET MVC با Remote Validation

بعد از آمدن نسخه‌ی سوم ASP.NET MVC مکانیسمی به نام Remote Validation به آن اضافه شد که کارش اعتبارسنجی از راه دور بود. فرض کنید نیاز است در یک فرم، قبل از اینکه کل فرم به سمت سرور ارسال شود، مقداری بررسی شده و اعتبارسنجی آن انجام گیرد و این اعتبارسنجی چیزی نیست که بتوان سمت کاربر و بدون فرستاده شدن مقداری به سمت سرور صورت گیرد. نمونه بارز این مسئله صفحه عضویت اکثر سایت‌هایی هست که روزانه داریم با آن‌ها کار می‌کنیم. فیلد نام کاربری توسط شما پر شده و بعد از بیرون آمدن از آن فیلد، سریعا مشخص می‌شود که آیا این نام کاربری قابل استفاده برای شما هست یا خیر. به‌صورت معمول برای انجام این کار باید با جاوا اسکریپت، مدیریتی روی فیلد مربوطه انجام دهیم. مثلا با بیرون آمدن فوکوس از روی فیلد، با Ajax نام کاربری وارد شده را به سمت سرور بفرستیم، چک کنیم و بعد از اینکه جواب برگشت بررسی کنیم که الان آیا این نام کاربری قبلا گرفته شده یا نه.
انجام این کار به‌راحتی با مزین‌کردن خصوصیت (Property) مربوطه موجود در مدل برنامه به Attribute یا ویژگی Remote و داشتن یک Action در Controller مربوطه که کارش بررسی وجود یوزرنیم هست امکان پذیر است. ادامه بحث را با مثال همراه می‌کنم.
به عنوان مثال در سیستمی که قرار هست محصولات ما را ثبت کند، باید بیایم و قبل از اینکه محصول جدید به ثبت برسد این عملیات چک‌کردن را انجام دهیم تا کالای تکراری وارد سیستم نشود. شناسه اصلی که برای هر محصول وجود دارد بارکد هست و ما آن را میخواهیم مورد بررسی قرار دهیم.


مدل برنامه

    public class ProductModel
    {
        public int Id { get; set; }

        [Display(Name = "نام کالا")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [StringLength(50, ErrorMessage = "طول {0} باید کمتر از {1} کاراکتر باشد.")]
        public string Name { get; set; }

        [Display(Name = "قیمت")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [DataType(DataType.Currency)]
        public double Price { get; set; }

        [Display(Name = "بارکد")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [StringLength(50, ErrorMessage = "طول {0} باید کمتر از {1} کاراکتر باشد.")]
        [Remote("IsProductExist", "Product", HttpMethod = "POST", ErrorMessage = "این بارکد از قبل در سیستم وجود دارد.")]
        public string Barcode { get; set; }
    }

همونطور که می‌بینید خصوصیت Barcode را مزین کردیم به ویژگی Remote. این ویژگی دارای ورودی‌های خاص خودش هست. وارد کردن نام اکشن و کنترلر مربوطه برای انجام این چک‌کردن از مهم‌ترین قسمت‌های اصلی هست. چیزهایی دیگه‌ای هم هست که می‌توانیم آن‌ها را مقداردهی کنیم. مثل HttpMethod، ErrorMessage و یا AdditionFields. HttpMethod که همان طریقه‌ی ارسال درخواست به سرور هست. ErrorMessage هم همان خطایی هست که در زمان رخ‌داد قرار است نشان داده شود. AdditionFields هم خصوصیتی را مشخص می‌کند که ما می‌خوایم به‌همراه فیلد مربوطه به سمت سرور بفرستیم. مثلا می‌تونیم به‌همراه بارکد، نام کالا را هم برای بررسی‌های مورد نیازمان بفرستیم.


کنترلر برنامه

        [HttpPost]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult IsProductExist(string barcode)
        {
            if (barcode == "123456789") return Json(false); // اگر محصول وجود داشت
            return Json(true);
        }

در اینجا به نمایش قسمتی از کنترلر برنامه می‌پردازیم. اکشنی که مربوط می‌شود به چک‌کردن مقدارهای لازم و در پایان آن یک خروجی Json را برمی‌گردانیم که مقدار true یا false دارد. در حقیقت مقدار را به این صورت برمی‌گردانیم که اگر مقدار ورودی در پایگاه داده وجود دارد، false را برمی‌گرداند و اگر وجود نداشت true. همین‌طور آمدیم از کش شدن درخواست‌هایی که با Ajax آمده با ویژگی OutputCache جلوگیری کردیم.

پاسخ به بازخورد‌های پروژه‌ها
عدم سازگاری با EF
آقای نصیری
لایه سرویس همه این کارها رو انجام می‌ده
شیی Order به متد فوق ارسال می‌شه سپس OrderProductVarients که به عنوان یک Custome property تعریف شده پشت صحنه یک linq query اجرا می‌کنه و اگر لازم باشه مکانیزم کش هم انجام می‌شه توی تصویر هم این شی یک لیست از شی OrderProductVarient نه چیز دیگه ای و یک رکورد داره.
من متوجه نمی‌شم دیتا سورس من یک Ilist با یک رکورد این چه تناقضی با Lazy loading توکار لایه بیزنس داره

جالب اینجاست با تعریف یک کلاس مدل مشکل حل می‌شه!
public class OrderProductVarientModel
    {
       public int OrderProductVarientId
        {
            get;
            set;
        }
    }

حالا ازین کد استفاده کردم
  .MainTableDataSource(dataSource =>
            {
                var listOfRows = new List<OrderProductVarientModel>();
                for (int i = 0; i < orderProductVariants.Count; i++)
                {
                    listOfRows.Add(new OrderProductVarientModel { OrderProductVarientId = orderProductVariants[i].OrderProductVariantId });
                }
                dataSource.StronglyTypedList(listOfRows);
                //dataSource.StronglyTypedList(orderProductVariants);
            })

به نظر من یک جایی از متد StronglyTypedList داره همه property‌های شی جنریک مپ می‌کنه!
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 6 - Implementation Factory

در بعضی از شرایط پیش رفته، ممکن است نمونه سازی از یک Implementation Type، نیاز به دخالت مستقیم ما را داشته باشد. Implementation Factory کنترل بیشتری بر چگونگی استفاده‌ی از Implementation Type‌ها را  به ما ارائه می‌دهد. در هنگام ثبت سرویسی که Implementation Factory را در اختیار ما قرار می‌دهد، ما یک Delegate را برای فراخوانی سرویس استفاده می‌کنیم. این Delegate مسئول ساخت یک نمونه از Service Type است. برای مثال وقتی از الگوهای builder یا factory برای ساخت یک شیء استفاده می‌کنید، شاید نیاز باشد که Implementation Factory را به صورت دستی پیاده سازی کنید. اولین قدم این است که کدتان را در صورت امکان چنان refactor کنید تا DI Container بتواند آن را به صورت خودکار بسازد؛ ولی اینکار همیشه ممکن نیست. برای مثال بعضی از برنامه نویسان ترجیح می‌دهند یک Config را مستقیما از IOptionMonitor   بگیرند و بعد در هر جائیکه خواستند، بجای تزریق IOptionMonitor به سرویس، مستقیما از همان سرویس ثبت شده استفاده کنند:

services.AddSingleton<ILiteDbConfig>(sp =>sp.GetRequiredService<IOptionsMonitor<LiteDbConfig>>().CurrentValue);

پیاده سازیComposite Pattern 

یک کاربرد بالقوه‌ی دیگر برای Implementation Factory ، استفاده از Composite Pattern است. هر چند Microsoft DI Container به صورت پیش فرض از Composite Pattern پشتیبانی نمی‌کند، ولی ما می‌توانیم آن‌را پیاده سازی کنیم. فرض کنید که قبلا به ازای انجام کاری، به کاربر یک ایمیل را می‌فرستادیم؛ ولی حالا مالک محصول می‌آید و می‌گوید که علاوه بر ایمیل، باید پیامک هم بفرستید و ما یا این سرویس پیامک را از قبل داریم و یا باید آن را بسازیم که فرض می‌کنیم از قبل آن را داریم. برای این کار ما یک اینترفیس کلی‌تر به نام INotificationService می‌سازیم که دو سرویس IEmailNotificationService و ISmsNotificaitonService از آن ارث بری می‌کنند:

public interface INotificationService
{
      void SendMessage(string msg, int userId);
}
حالا CompositeNotificationService را به این صورت تعریف می‌کنیم:
    public class CompositeNotificationService : INotificationService
    {
        private readonly IEnumerable<INotificationService> _notificationServices;
        public CompositeNotificationService(IEnumerable<INotificationService> notificationServices)
        {
            _notificationServices = notificationServices;
        }

        public void SendMessage(string msg, int userId)
        {
            foreach (var notificationServicei in _notificationServices) 
            {
                notificationServicei.SendMessage(msg, userId);
            }
        }
    }
و این سرویس‌ها را به این صورت در DI Container ثبت می‌کنیم: 
services.AddScoped<IEmailNotificationService, EmailNotificationService>();
services.AddScoped<ISMSNotificationService, SMSNotificationService>();

services.AddSingleton<INotificationService>(sp => new CompositeNotificationService(
      new INotificationService[] { 
          sp.GetRequiredService<IEmailNotificationService>() , 
          sp.GetRequiredService<ISMSNotificationService>()
      }
));
حالا هر زمانیکه بخواهیم همزمان، هم ایمیل و هم پیامک بفرستیم، کافی است که سرویس INotificationService را در سازنده‌ی کلاس مورد نظر تزریق کرده و از آن در مکان‌ها و شرایط مختلفی استفاده کنیم. اگر هر کدام از سرویس‌های ارسال ایمیل و سرویس‌های پیامک را به صورت جداگانه بخواهیم، می‌توانیم آنها را به صورت تکی ثبت و استفاده کنیم.  


وهله سازی سفارشی

در مثال بعدی نشان می‌دهیم که چطور می‌توانیم از Implementation Factory برای برگرداندن پیاده‌سازی سرویس‌هایی که Service Provider امکان ساخت خودکار آنها را ندارد، استفاده کنیم. فرض کنید یک کلاس Account داریم که از IAccount ارث بری می‌کند و برای ساخت آن باید از متدهای IAccountBuilder که فرآیند ساخت را انجام می‌دهند، استفاده کنیم. بنابراین امکان ساخت مستقیم یک شیء از IAccount وجود ندارد. در این حالت بدین صورت عمل می‌کنیم: 

services.AddTransient<IAccountBuilder, AccountBuilder>();

services.AddScoped<IAccount>(sp => {
    var builder = sp.GetService<IAccountBuilder>();
    builder.WithAccountType(AccountType.Guest);
    return builder.Build();
});


ثبت یک نمونه برای چندین اینترفیس

ممکن است بنا به دلایلی مجبور باشیم یک implementation Type را برای چند سرویس (اینترفیس) به ثبت برسانیم. در این حالت نمونه‌ی شیء ساخته شده‌، توسط هر کدام از اینترفیس‌ها قابل استفاده است. فرض کنید یک سرویس Greeting داریم که پیش از این فقط اینترفیس IHomeGreetingService را پیاده سازی می‌کرد؛ ولی بنا به دلایلی تصمیم گرفتیم که سرویسی جامع‌تر را با نیازمندی‌های دیگری نیز تعریف کنیم و GreetingService آن را پیاده سازی کند: 

public class GreetingService : IHomeGreetingService , IGreetingService
{ // code here }

احتمالا اولین چیزی که به ذهنمان می‌رسد این است: 

services.TryAddSingleton<IHomeGreetingService, GreetingService>();
services.TryAddSingleton<IGreetingService, GreetingService>();

مشکل روش بالا این است که دو نمونه از GreetingService ساخته می‌شود و درون حافظه باقی می‌ماند و در حقیقت برای هر اینترفیس، یک نوع جداگانه از GreetingService ثبت می‌شود؛ در حالیکه ما می‌خواهیم هر دو اینترفیس فقط از یک نمونه از شیء GreetingService استفاده کنند.  دو راه حل برای این مشکل وجود دارد:

var greetingService = new GreetingService(Environment);
services.TryAddSingleton<IHomeGreetingService>(greetingService);
services.TryAddSingleton<IGreetingService>(greetingService);

در اینجا سازنده‌ی کلاس GreetingService فقط به  environment نیاز داشت که یکی از سرویس‌های پایه‌ی فریم ورک هست و در این مرحله در دسترس است. به صورت کلی مشکل روش بالا این است که ما مسئول نمونه سازی از سرویس GreetingService هستیم! اگر GreetingService برای ساخته شدن به سرویس‌ها یا ورودی هایی نیاز داشته باشد که فقط در زمان اجرا در دسترس باشند، ما امکان نمونه سازی آن‌ها را نداریم؛ علاوه بر این نمی‌توان از روش‌های بالای برای حالت‌های Scoped یا Transient  استفاده کرد.

روش بعدی همان روش استفاده از Implementation Factory است که در ادامه آن را می‌بینید: 

services.TryAddSingleton<GreetingService>();
services.TryAddSingleton<IHomeGreetingService>(sp => sp.GetRequiredService<GreetingService>());
services.TryAddSingleton<IGreetingService>(sp => sp.GetRequiredService<GreetingService>());

در این روش خود DI Container مسئول نمونه سازی از GreetingService است. علاوه بر این می‌توان با استفاده از روش فوق از طول حیات‌های Scoped و Transient هم استفاده کرد؛ در حالیکه در روش قبلی این کار امکان پذیر نبود.

 

Open Generics Service

گاهی از اوقات می‌خواهید سرویس‌هایی را ثبت کنید که از اینترفیسی جنریک ارث بری می‌کنند. هر نوع جنریک در زمان اجرا، نوع مخصوص به خود را واکشی می‌کند. ثبت کردن دستی این سرویس‌ها می‌تواند خسته کننده باشد. برای همین مایکروسافت در DI Container خود قابلیت ثبت و واکشی سرویس‌های جنریک را نیز در اختیار ما گذاشته‌است. بیایید نگاهی به سرویس ILogger<T>  بیندازیم. این یک سرویس درونی فریمورک است و می‌تواند به ازای هر نوع، کارهای مربوط به ثبت log را انجام بدهد و در پروژه‌ها معمولا از این اینترفیس برای ثبت لاگ‌ها در سطح کنترلر و سرویس‌ها استفاده می‌شود: 

public interface ILogger<out TCategoryName> : ILogger
{
}

در حالت عادی اگر سرویسی مشابه سرویس فوق را داشته باشیم، برای ثبت کردن هر سرویس با نوع جنریک اختصاصی آن، مجبوریم به صورت دستی آن را درون DI Container ثبت کنیم؛ مثلا باید به این صورت عمل کنیم: 

services.TryAddScoped<ILogger<HomeController>,Logger<HomeController>>();
این کاری طاقت فرساست. به همین جهت مایکروسافت قابلیت Open Generics Service را در اختیار ما گذاشته تا بتوانیم اینگونه سرویس‌ها را فقط و فقط یکبار ثبت کنیم: 
services.TryAddScoped(typeof(ILogger<>) , typeof(Logger<>));
و اینگونه می‌توانیم نمونه‌های مختلف از ILogger<T> را به هر جایی که خواستیم، تزریق کنیم.

 

دسته بندی سرویس‌ها درون متدهای مختلف و پاکسازی  متد ConfigurationService

 تا اینجای کار ما سرویس‌های مختلفی را به روش‌های مختلفی ثبت کرده‌ایم. حتی در همین آموزش ساده، تعداد زیاد سرویس‌های ثبت شده، باعث شلوغی و در هم ریختگی کدهای ما می‌شوند که خوانایی و در ادامه اشکال زدایی و توسعه‌ی کدها را برای ما سخت‌تر می‌کنند.  ساده‌ترین کار برای دسته بندی کدها، استفاده از متدهای private محلی یا استفاده از متدهای توسعه‌ای(الحاقی) است که در اینجا مثالی از استفاده از متدهای توسعه‌ای را آورده‌ام:
namespace AspNetCoreDependencyInjection.Extensions
{
    public static class DICRegisterationExetnsion
    {
        /// <summary>
        /// مثال ثبت برای اپن جنریت
        /// </summary>
        /// <param name="services"></param>
        public static void OpenGenericRegisterationExample(this IServiceCollection services)
        {
            services.TryAddScoped<ILogger<HomeController>, Logger<HomeController>>();
            services.TryAddScoped(typeof(ILogger<>), typeof(Logger<>));
        }


        /// <summary>
        /// ثبت تنظیمات به روش‌های مختلف 
        /// </summary>
        public static void RegisterConfiguration(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddSingleton(services => new AppConfig
            {
                ApplicationName = configuration["ApplicationName"],
                GreetingMessage = configuration["GreetingMessage"],
                AllowedHosts = configuration["AllowedHosts"]
            });

            services.AddSingleton(services => new AccountTypeBalanceConfig(
                    new List<(AccountType, long)> {
                        (AccountType.Guest , Convert.ToInt64 (configuration["AccountInitialBalance.Guest"]) ) ,
                        (AccountType.Silver , Convert.ToInt64 (configuration["AccountInitialBalance.Silver"]) ) ,
                        (AccountType.Gold , Convert.ToInt64 (configuration["AccountInitialBalance.Gold"]) ) ,
                        (AccountType.Platinum , Convert.ToInt64 (configuration["AccountInitialBalance.Platinum"]) ) ,
                        (AccountType.Titanium , Convert.ToInt64 (configuration["AccountInitialBalance.Titanium"]) ) ,
                    })
            );

            services.AddSingleton(services => new LiteDbConfig
            {
                ConnectionString = configuration["LiteDbConfig:ConnectionString"],
            });

            services.Configure<UserOptionConfig>(configuration.GetSection("UserOptionConfig"));
        }
    }
}

حالا در کلاس ConfigureServices ، درون متدStartup ، به این صورت از این متدهای توسعه‌ای استفاده می‌کنیم:
services.RegisterConfiguration(this.Configuration);
services.OpenGenericRegisterationExample();

می‌توانید کد منبع این آموزش را در اینجا  ببینید.
مطالب
نکاتی در مورد استفاده از توابع تجمعی در 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 و غیره صادق هستند و باید به آن‌ها نیز دقت داشت.
مطالب
استفاده از Lambda Expression در پروژه های مبتنی بر WCF
نکته : آشنایی با مفاهیم پایه WCF برای فهم بهتر مفاهیم توصیه می‌شود.

امروزه استفاده از WCF در پروژه‌های SOA بسیار فراگیر شده است. کمتر کسی است که در مورد قدرت تکنولوژی WCF نشنیده باشد یا از این تکنولوژی در پروژه‌های خود استفاده نکرده باشد. WCF مدل برنامه نویسی یکپارچه مایکروسافت برای ساخت نرم‌افزارهای سرویس گرا است و برای توسعه دهندگان امکانی را فراهم می‌کند که راهکارهایی امن، و مبتنی بر تراکنش را تولید نمایند که قابلیت استفاده در بین پلتفرم‌های مختلف را دارند. قبل از WCF توسعه دهندگان پروژه‌های نرم افزاری برای تولید پروژه‌های توزیع شده باید شرایط موجود برای تولید و توسعه را در نظر می‌گرفتند. برای مثال اگر استفاده کننده از سرویس در داخل سازمان و بر پایه دات نت تهیه شده بود از .net remoting استفاده می‌کردند و اگر استفاده کننده سرویس از خارج سازمان یا مثلا بر پایه تکنولوژی J2EE بود از Web Service‌ها استفاده می‌شد. با ظهور WCF این تکنولوژی با هم تجمیع شدند(بهتر بگم تبدیل به یک تکنولوژی واحد شدند) و دیگر خبری از net remoting یا web service‌ها نیست.
  WCF با تمام قدرت و امکاناتی که داراست دارای نقاط ضعفی هم می‌باشد که البته این معایب (یا محدودیت) بیشتر جهت سازگار سازی سرویس‌های نوشته شده با سیستم‌ها و پروتکل‌های مختلف است.
برای انتقال داده‌ها از طریق WCF بین سیستم‌های مختلف باید داده‌های مورد نظر حتما سریالایز شوند که مثال هایی از این دست رو در همین سایت می‌تونید مطالعه کنید:
(^ )  و (^ ) و (^ )

با توجه به این که داده‌ها سریالایز می‌شوند، در نتیجه امکان انقال داده هایی که از نوع object  هستند در WCF وجود ندارد. بلکه نوع داده باید صراحتا ذکر شود و این نوع باید قابیلت سریالایز شدن را دارا باشد.برای مثال شما نمی‌تونید متدی داشته باشید که پارامتر ورودی آن از نوع delegate باشد یا کلاسی باشد که صفت [Serializable] در بالای اون قرار نداشته باشد یا کلاسی باشد که صفت DataContract برای خود کلاس و صفت DataMember برای خاصیت‌های اون تعریف نشده باشد. حالا سوال مهم این است اگر متدی داشته باشیم که پارامتر ورودی آن حتما باید از نوع delegate باشد چه باید کرد؟

برای تشریح بهتر مسئله یک مثال می‌زنم؟

سرویسی داریم برای اطلاعات کتاب ها. قصد داریم متدی بنوسیم که پارامتر ورودی آن از نوع Lambda Expression است تا Query مورد نظر کاربر از سمت کلاینت به سمت سرور دریافت کند و خروجی مورد نظر را با توجه به Query ورودی به کلاینت برگشت دهد.( متدی متداول در اکثر پروژه ها). به صورت زیر عمل می‌کنیم.

*ابتدا یک Blank Solution ایجاد کنید.

*یک ClassLibrary به نام Model ایجاد کنید و کلاسی به نام Book در آن بسازید .(همانطور که میبینید کلاس مورد نظر سریالایز شده است):

   [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Title { get; set; }
    }
* یک WCF Service Application ایجاد کنید
یک Contract برای ارتباط بین سرور و کلاینت می‌سازیم:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.ServiceModel;

namespace WcfLambdaExpression
{
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression );
    }
}
متد GetByExpression دارای پارامتر ورودی expression است که نوع آن نیز Lambda Expression  می‌باشد. حال یک سرویس ایجاد می‌کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace WcfLambdaExpression
{    
    public class BookService : IBookService
    {        
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook 
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

            return ListOfBook.AsQueryable().Where( expression );
        }       
    }
}
بعد از Build پروژه همه چیز سمت سرور آماده است. یک پروژه دیگر از نوع Console ایجاد کنید و از روش AddServiceReference سعی کنید که سرویس مورد نظر را به پروژه اضافه کنید. در هنگام Add Service Reference برای اینکه سرویس سمت سرور و کلاینت هر دو با یک مدل کار کنند باید از یک Reference assembly استفاده کنند و کافی است از قسمت Advanced گزینه Reuse types in referenced assemblies را تیک بزنید و assembly‌های مورد نظر را انتخاب کنید.( در این پروژه باید Model و System.Xml.Linq را انتخاب کنید)


به طور حتم با خطا روبرو خواهید شد. دلیل آن هم این است که امکان سریالایز کردن برای پارامتر ورودی expression میسر نیست.
خطای مربوطه به شکل زیر خواهد بود:
Type 'System.Linq.Expressions.Expression`1[System.Func`2[WcfLambdaExpression.Book,System.Boolean]]' cannot be serialized. 
Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  
If the type is a collection, consider marking it with the CollectionDataContractAttribute.  
See the Microsoft .NET Framework documentation for other supported types
حال چه باید کرد؟
روش‌های زیادی برای بر طرف کردن این محدودیت وجود دارد. اما در این پست روشی رو که خودم از اون استفاده می‌کنم رو براتون شرح می‌دهم.
در این روش باید از XElement استفاده شود که در فضای نام System.Linq.Xml قرار دارد. یعنی آرگومان ورودی سمت کلاینت باید به فرمت Xml سریالایز شود و سمت سرور دوباره دی سریالایز شده و تبدیل به یک Lambda Expression شود. اما سریالایز کردن Lambda Expression واقعا کاری سخت و طاقت فرساست . با توجه به این که در اکثر پروژه‌ها این متد‌ها به صورت Generic نوشته می‌شوند. برای حل این مسئله بعد از مدتی جستجو، کلاسی رو پیدا کردم که این کار رو برام انجام می‌داد. بعد از مطالعه دقیق و مشاهده روش کار کلاس، تغییرات مورد نظرم رو اعمال کردم و الان در اکثر پروژه هام دارم از این کلاس استفاده می‌کنم.
یک مثال از روش استفاده :
برای اینکه از این کلاس در هر دو پروژه (سرور و کلاینت) استفاده می‌کنیم باید یک Class Library جدید به نام Common بسازید و یک ارجاع از اون رو به هر دو پروژه سمت سرور و کلاینت بدید.
سرویس و Contract بالا رو به صورت زیر باز نویسی کنید.
[ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( XElement expression );
    }
و سرویس :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace WcfLambdaExpression
{
    public class BookService : IBookService
    {
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( XElement expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

             Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            return ListOfBook.AsQueryable().Where( serializer.Deserialize( expression ) as Expression<Func<Book, bool>> );
        }
    }
بعد از Build پروژه از روش Add Service Reference استفاده کنید و می‌بینید که بدون هیچ گونه مشکلی سرویس مورد نظر به پروژه Console اضافه شد. برای استفاده سمت کلاینت به صورت زیر عمل کنید.

using System;
using System.Linq.Expressions;
using TestExpression.MyBookService;

namespace TestExpression
{
    class Program
    {
        static void Main( string[] args )
        {
            BookServiceClient bookService = new BookServiceClient();

            Expression<Func<Book, bool>> expression = x => x.Code > 2 && x.Code < 5;

            Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            bookService.GetByExpression( serializer.Serialize( expression ) );
        }
    }
}
بعد از اجرای پروژه، در سمت سرور خروجی‌های زیر رو مشاهده می‌کنیم.

خروجی هم به صورت زیر خواهد بود:

دریافت سورس کامل Expression-Serialization
مطالب
استفاده از EF7 با پایگاه داده SQLite تحت NET Core. به کمک Visual Studio Code
در این مقاله سعی داریم مراحل نوشتن و اجرای یک برنامه‌ی ساده را تحت NET Core. و با بهره گیری از دیتابیس SQLite و EF7، دنبال کنیم. همچنین از آنجایی‌که NET Core. به صورت چندسکویی طراحی شده‌است و تحت لینوکس و مکینتاش هم قابل اجراست، در نتیجه مناسب دیدم که ابزار نوشتن این پروژه‌ی ساده نیز قابلیت چندسکویی داشته و تحت لینوکس و مکینتاش نیز قابل اجرا باشد. در نتیجه به جای Visual Studio در این مقاله از Visual Studio Code استفاده شده است.

ابزارهای پیش نیاز:
  1. Visual Studio Code
  2. .NET Core
در اولین قدم، برنامه‌ی متن باز Visual Studio Code را از اینجا دانلود و نصب کنید. برنامه‌ی Visual Studio Code که در ادامه‌ی فعالیت‌های جدید متن باز مایکروسافت به بازار عرضه شده است، سریع، سبک و کاملا قابل توسعه و سفارشی سازی است و از اکثر زبان‌های معروف پشتیبانی می‌کند.
در قدم بعدی، شما باید NET Core. را از اینجا (64 بیتی) دانلود و نصب کنید.

 .NET Core چیست؟
NET Core. در واقع پیاده سازی بخشی از NET. اصلی است که به صورت متن باز در حال توسعه می‌باشد و بر روی لینوکس و مکینتاش هم قابل اجراست. موتور اجرای دات نت کامل CLR نام دارد و NET Core. نیز دارای موتور اجرایی CoreCLR است و شامل فریمورک CoreFX می‌باشد.

در حال حاضر شما می‌توانید با استفاده از NET Core. برنامه‌های کنسولی و تحت وب با ASP.NET 5 بنویسید و احتمالا در آینده می‌توان امیدوار بود که از ساختارهای پیچیده‌تری مثل WPF نیز پشتیبانی کند.

پس از آنکه NET Core. را دانلود و نصب کردید، جهت شروع پروژه، یک پوشه را در یکی از درایوها ساخته (در این مثال E:\Projects\EF7-SQLite-NETCore) و Command prompt را در آنجا باز کنید. سپس دستورات زیر را به ترتیب اجرا کنید:

dotnet new
dotnet restore
dotnet run

دستور dotnet new یک پروژه‌ی ساده‌ی Hello World را در پوشه‌ی جاری ایجاد می‌کند که حاوی فایل‌های زیر است:
  • NuGet.Config (این فایل، تنظیمات مربوط به نیوگت را جهت کشف و دریافت وابستگی‌های پروژه، شامل می‌شود)
  •  Program.cs (این فایل سی شارپ حاوی کد برنامه است)
  • project.json (این فایل حاوی اطلاعات پلتفرم هدف و لیست وابستگی‌های پروژه است)

دستور dotnet restore بر اساس لیست وابستگی‌ها و پلتفرم هدف، وابستگی‌های لازم را از مخزن نیوگت دریافت می‌کند. (در صورتی که در هنگام اجرای این دستور با خطای NullReferenceException مواجه شدید از دستور dnu restore استفاده کنید. این خطا در گیت هاب در حال بررسی است)

دستور dotnet run هم سورس برنامه را کامپایل و اجرا می‌کند. در صورتی که پیام Hello World را مشاهده کردید، یعنی برنامه‌ی شما تحت NET Core. با موفقیت اجرا شده است.

توسعه‌ی پروژه با Visual Studio Code

در ادامه، قصد داریم پروژه‌ی HelloWorld را تحت Visual Studio Code باز کرده و تغییرات بعدی را در آنجا اعمال کنیم. پس از باز کردن Visual Studio Code از منوی File گزینه‌ی Open Folder را انتخاب کنید و پوشه‌ی حاوی پروژه (EF7-SQLite-NETCore) را انتخاب کنید. اکنون پروژه‌ی شما تحت VS Code باز شده و قابل ویرایش است.

سپس از لیست فایل‌های پروژه، فایل project.json را باز کرده و در بخش "dependencies" یک ردیف را برای EntityFramework.SQLite به صورت زیر اضافه کنید. به محض افزودن این خط در project.json و ذخیره‌ی آن، در صورتیکه قبلا این وابستگی دریافت نشده باشد، Visual Studio Code با نمایش یک هشدار در بالای برنامه به شما امکان دریافت اتوماتیک این وابستگی را می‌دهد. در نتیجه کافیست دکمه‌ی Restore را زده و منتظر شوید تا وابستگی EntityFramework.SQLite از مخزن ناگت دانلود و برای پروژه‌ی شما تنظیم شود.

"EntityFramework.SQLite": "7.0.0-rc1-final"

دریافت اتوماتیک وابستگی‌ها توسط Visual Studio Code


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

اکنون همه‌ی موارد، جهت توسعه‌ی پروژه آماده است. ماوس خود را بر روی ریشه‌ی پروژه در VS Code قرار داده و New Folder را انتخاب کنید و نام Models را برای آن تایپ کنید. این پوشه قرار است مدل کلاس‌های پروژه را شامل شود. در اینجا ما یک مدل به نام Book داریم و نام کانتکست اصلی پروژه را هم LibraryContext گذاشته‌ایم.

بر روی پوشه‌ی Models راست کلیک کرده و گزینه‌ی New File را انتخاب کنید. سپس فایل‌های Book.cs و LibraryContext.cs را ایجاد کرده و کدهای زیر را برای مدل و کانتکست، در درون این دو فایل قرار دهید.

Book.cs

namespace Models
{
    public class Book
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Author{get;set;}
        public int PublishYear { get; set; }
    }
}
LibraryContext.cs
using Microsoft.Data.Entity;
using Microsoft.Data.Sqlite;

namespace Models
{
    public class LibraryContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "test.db" };
            var connectionString = connectionStringBuilder.ToString();
            var connection = new SqliteConnection(connectionString);
            optionsBuilder.UseSqlite(connection);
        }
        public DbSet<Book> Books { get; set; }
    }
}
در فایل Book.cs یک مدل ساده به نام Book داریم که حاوی اطلاعات یک کتاب است. فایل LibraryContext.cs نیز حاوی کلاس LibraryContext است که یک مجموعه از کتاب‌ها را با نام Books نگهداری می‌کند. در متد OnConfiguring تنظیمات لازم را جهت استفاده از دیتابیس SQLite با نام test.db، قرار داده‌ایم که البته این کد را در پروژه‌های کامل می‌توان در خارج از LibraryContext قرار داد تا بتوان مسیر ذخیره سازی دیتابیس را کنترل و قابل تنظیم کرد.

در قدم آخر هم کافیست که فایل Program.cs را تغییر دهید و مقادیری را در دیتابیس ذخیره و بازخوانی کنید.
Program.cs
using System;
using Models;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("EF7 + Sqlite with the taste of .NET Core");

            try
            {
                using (var context = new LibraryContext())
                {
                    context.Database.EnsureCreated();

                    var book1 = new Book()
                    {
                        Title = "Adaptive Code via C#: Agile coding with design patterns and SOLID principles ",
                        Author = "Gary McLean Hall",
                        PublishYear = 2014
                    };

                    var book2 = new Book()
                    {
                        Title = "CLR via C# (4th Edition)",
                        Author = "Jefrey Ritcher",
                        PublishYear = 2012
                    };

                    context.Books.Add(book1);
                    context.Books.Add(book2);

                    context.SaveChanges();

                    ReadData(context);
                }

                Console.WriteLine("Press any key to exit ...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An exception occured: {ex.Message}\n{ex.StackTrace}");
            }
        }

        private static void ReadData(LibraryContext context)
        {
            Console.WriteLine("Books in database:");
            foreach (var b in context.Books)
            {
                Console.WriteLine($"Book {b.ID}");
                Console.WriteLine($"\tName: {b.Title}");
                Console.WriteLine($"\tAuthor: {b.Author}");
                Console.WriteLine($"\tPublish Year: {b.PublishYear}");
                Console.WriteLine();
            }
        }
    }
}
در فایل Program.cs جهت تست پروژه، از روی کلاس Book دو نمونه ساخته و آن‌ها را به دیتابیس افزوده و ذخیره می‌کنیم و در انتها، اطلاعات تمامی کتاب‌های موجود در دیتابیس را با جزییات نمایش می‌دهیم.

جهت اجرای برنامه کافیست Command prompt را در آدرس پروژه باز کرده و دستور dotnet run را اجرا کنید. پروژه‌ی شما کامپایل و اجرا می‌شود و خروجی مشابه زیر را مشاهده خواهید کرد. اگر برنامه را مجددا اجرا کنید، به جای دو کتاب اطلاعات چهار کتاب نمایش داده خواهد شد؛ چرا که در هر مرحله اطلاعات دو کتاب در دیتابیس درج می‌شود.

.NET Core + EF7 + SQLite

اگر به پوشه‌ی bin که در پوشه‌ی پروژه ایجاد شده است، نگاهی بیندازید، خبری از فایل باینری نیست. چرا که در لحظه‌، تولید و اجرا شده است. جهت build کردن پروژه و تولید فایل باینری کافیست دستور dotnet build را اجرا کنید، تا فایل باینری در پوشه‌ی bin ایجاد شود.

جهت انتشار برنامه می‌توانید دستور dotnet publish را اجرا کنید. این دستور نه تنها برنامه، که تمام وابستگی‌های مورد نیاز آن را برای اجرای در یک پلتفرم خاص تولید می‌کند. برای مثال بعد از اجرای این دستور یک پوشه‌ی win7-x64 حاوی 211 فایل در مجموع تولید شده است که تمامی وابستگی‌های این پروژه را شامل می‌شود.

Publishing .NET Core app

در واقع این پوشه تمام وابستگی‌های مورد نیاز پروژه را همراه خود دارد و در نتیجه جهت اجرای این برنامه برخلاف برنامه‌های معمولی دات نت، دیگر نیازی به نصب هیچ وابستگی مجزایی نیست و حتی پروژه‌های نوشته شده تحت NET Core. را می‌توانید در سیستم‌های عامل‌های دیگری مثل لینوکس و مکینتاش و یا  Windows IoT بر روی سخت افزار Raspberry Pi 2 هم اجرا کنید.

جهت مطالعه‌ی بیشتر: