نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
در مثال بالا هر لاگین کاربر حداقل 9 بار کاربر همراه با رل هاش از دیتابیس فراخوانی میشه اگر ورود دو مرحله فعال باشه حدودا 28 بار میشه ، روش پیش فرض که آی دی رو string پیاد سازی کرده رو بررسی کردم چنین مشکلی نداشت. در کلاس ApplicationUserManager متد FindByIdAsync  رو بصورت زیر تغییر بدید می‌تونید تعداد فراخوانی را بررسی کنید: 
      public override Task<ApplicationUser> FindByIdAsync(int userId)
        {
                     return base.FindByIdAsync(userId);
        }

        public override Task<ApplicationUser> FindByNameAsync(string userName)
        {
            return base.FindByNameAsync(userName);
        }
در کدهای  Identity به دفعات از متود  FindByIdAsync  استفاده شده و فرض بر این بوده که دفعات بعد از کش DbContext اسفاده می‌شود.
در سورس UserStore متود FindByIdAsync  از یک متد واسطه  به نام GetUserAggregateAsync استفاده کرده :
public virtual Task<TUser> FindByIdAsync(TKey userId)
        {
            ThrowIfDisposed();
            return GetUserAggregateAsync(u => u.Id.Equals(userId));
        }
     protected virtual async Task<TUser> GetUserAggregateAsync(Expression<Func<TUser, bool>> filter)
        {
            TKey id;
            TUser user;
            if (FindByIdFilterParser.TryMatchAndGetId(filter, out id))
            {
                user = await _userStore.GetByIdAsync(id).WithCurrentCulture();
            }
            else
            {
                user = await Users.FirstOrDefaultAsync(filter).WithCurrentCulture();
            }
            if (user != null)
            {
                await EnsureClaimsLoaded(user).WithCurrentCulture();
                await EnsureLoginsLoaded(user).WithCurrentCulture();
                await EnsureRolesLoaded(user).WithCurrentCulture();
            }
            return user;
        }
وقتی پیاده سازی آی دی براساس int باشه متد FindByIdFilterParser.TryMatchAndGetId درست عمل نمی‌کنه و به جای فراخوانی GetByIdAsync که پشت صحنه از FindAsync و قابلیت فراخوانی از کش رو داره از FirstOrDefaultAsync استفاده می‌کنه و باعث فراخوانی مجدد از دیتابیس می‌شود.
راه حلی که من استفاده کردم پیاده سازی UserStore و تحریف متد FindByIdAsync  :
    public class MyUserStore
        :UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {

        private DbSet<ApplicationUser> _myUserStore;
        public MyUserStore(ApplicationDbContext context)
            : base(context)
        {
            _myUserStore = (DbSet<ApplicationUser>) context.Set<ApplicationUser>();

        }

        public override Task<ApplicationUser> FindByIdAsync(int userId)
        {
           return  _myUserStore.FindAsync(userId);
        }
  
    }
}
 همچنین در کلاس SmObjectFactory کد زیر :
               ioc.For<IUserStore<ApplicationUser, int>>()
                .HybridHttpOrThreadLocalScoped()
                .Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>>();
با این کد جایگزین شود :
ioc.For<IUserStore<ApplicationUser, int>>()
                    .HybridHttpOrThreadLocalScoped()
                 .Use<MyUserStore>();

نظرات مطالب
ایجاد یک ActionFilter جهت تمیز کردن اطلاعات ورودی در ASP.NET Core
نسخه کاملتر این متد به صورت زیر می‌باشد که ورودی  string را هم پوشش می‌دهد :
 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public class SanitizeInputAttribute : ActionFilterAttribute
    {
        private readonly ISanitizer _sanitizer;

        public SanitizeInputAttribute()
        {
            _sanitizer = new Sanitizer();
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.ActionArguments != null)
            {
                foreach (var parameter in filterContext.ActionArguments.ToList())
                {
                    if (parameter.Value != null)
                    {
                        var type = parameter.Value.GetType();
                        if (type == typeof(string))
                        {
                           var sanitized = _sanitizer.Sanitize(parameter.Value.ToString());
                           filterContext.ActionArguments[parameter.Key] = sanitized;
                        }
                        else
                        {
                            var properties = parameter.Value.GetType()
                                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                .Where(x => x.CanRead && x.CanWrite && x.PropertyType == typeof(string) &&
                                            x.GetGetMethod(true).IsPublic && x.GetSetMethod(true).IsPublic);



                            foreach (var propertyInfo in properties)
                            {
                                if (propertyInfo.GetValue(parameter.Value) != null)
                                    propertyInfo.SetValue(parameter.Value,
                                        _sanitizer.Sanitize(propertyInfo.GetValue(parameter.Value).ToString()));
                            }
                        }
                    }
                }
            }

        }
    }

مطالب
Tuple در دات نت 4

نوع جدیدی در دات نت 4 به نام Tuple اضافه شده است که در این مطلب به بررسی آن خواهیم پرداخت.
در ریاضیات، Tuple به معنای لیست مرتبی از اعضاء با تعداد مشخص است. Tuple در زبان‌های برنامه نویسی Dynamic مانند اف شارپ، Perl ، LISP و بسیاری موارد دیگر مطلب جدیدی نیست. در زبان‌های dynamic برنامه نویس‌ها می‌توانند متغیرها را بدون معرفی نوع آن‌ها تعریف کنند. اما در زبان‌های Static مانند سی شارپ، برنامه نویس‌ها موظفند نوع متغیرها را پیش از کامپایل آن‌ها معرفی کنند که هر چند کار کد نویسی را اندکی بیشتر می‌کند اما به این صورت شاهد خطاهای کمتری نیز خواهیم بود (البته سی شارپ 4 این مورد را با معرفی واژه‌ی کلیدی dynamic تغییر داده است).
برای مثال در اف شارپ داریم:
let data = (“John Doe”, 42)

که سبب ایجاد یک tuple که المان اول آن یک رشته و المان دوم آن یک عدد صحیح است می‌شود. اگر data را بخواهیم نمایش دهیم خروجی آن به صورت زیر خواهد بود:
printf “%A” data
// Output: (“John Doe”,42)

در دات نت 4 فضای نام جدیدی به نام System.Tuple معرفی شده است که در حقیقت ارائه دهنده‌ی نوعی جنریک می‌باشد که توانایی در برگیری انواع مختلفی را دارا است :
public class Tuple<T1>
up to:
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>

همانند آرایه‌ها، اندازه‌ی Tuples نیز پس از تعریف قابل تغییر نیستند (immutable). اما تفاوت مهم آن با یک آرایه در این است که اعضای آن می‌توانند نوع‌های کاملا متفاوتی داشته باشند. همچنین تفاوت مهم آن با یک ArrayList یا آرایه‌ای از نوع Object، مشخص بودن نوع هر یک از اعضاء آن است که type safety بیشتری را به همراه خواهد داشت و کامپایلر می‌تواند در حین کامپایل دقیقا مشخص نماید که اطلاعات دریافتی از نوع صحیحی هستند یا خیر.

یک مثال کامل از Tuples را در کلاس زیر ملاحظه خواهید نمود:

using System;
using System.Linq;
using System.Collections.Generic;

namespace TupleTest
{
class TupleCS4
{
#region Methods (4)

// Public Methods (4)

public static Tuple<string, string> GetFNameLName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new NullReferenceException("name is empty.");

var nameParts = name.Split(',');

if (nameParts.Length != 2)
throw new FormatException("name must contain ','");

return Tuple.Create(nameParts[0], nameParts[1]);
}

public static void PrintSelectedTuple()
{
var list = new List<Tuple<string, int>>
{
new Tuple<string, int>("A", 1),
new Tuple<string, int>("B", 2),
new Tuple<string, int>("C", 3)
};

var item = list.Where(x => x.Item2 == 2).SingleOrDefault();
if (item != null)
Console.WriteLine("Selected Item1: {0}, Item2: {1}",
item.Item1, item.Item2);
}

public static void PrintTuples()
{
var tuple1 = new Tuple<int>(12);
Console.WriteLine("tuple1 contains: item1:{0}", tuple1.Item1);

var tuple2 = Tuple.Create("Item1", 12);
Console.WriteLine("tuple2 contains: item1:{0}, item2:{1}",
tuple2.Item1, tuple2.Item2);

var tuple3 = Tuple.Create(new DateTime(2010, 5, 6), "Item2", 20);
Console.WriteLine("tuple3 contains: item1:{0}, item2:{1}, item3:{2}",
tuple3.Item1, tuple3.Item2, tuple3.Item3);
}

public static void Tuple8()
{
var tup =
new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9));

Console.WriteLine("tup.Rest Item1: {0}, Item2: {1}",
tup.Rest.Item1,tup.Rest.Item2);
}

#endregion Methods
}
}

using System;

namespace TupleTest
{
class Program
{
static void Main()
{
var data = TupleCS4.GetFNameLName("Vahid, Nasiri");
Console.WriteLine("Data Item1:{0} & Item2:{1}",
data.Item1, data.Item2);

TupleCS4.PrintTuples();

TupleCS4.PrintSelectedTuple();

TupleCS4.Tuple8();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

توضیحات :
- روش‌های متفاوت ایجاد Tuples را در متد PrintTuples می‌توانید ملاحظه نمائید. همچنین نحوه‌ی دسترسی به مقادیر هر کدام از اعضاء نیز مشخص شده است.
- کاربرد مهم Tuples در متد GetFNameLName نمایش داده شده است؛ زمانیکه نیاز است تا چندین خروجی از یک تابع داشته باشیم. به این صورت دیگر نیازی به تعریف آرگومان‌هایی به همراه واژه کلیدی out نخواهد بود یا دیگر نیازی نیست تا یک شیء جدید را ایجاد کرده و خروجی را به آن نسبت دهیم. به همان سادگی زبان‌های dynamic در اینجا نیز می‌توان یک tuple را ایجاد و استفاده کرد.
- بدیهی است از Tuples در یک لیست جنریک و یا حالات دیگر نیز می‌توان استفاده کرد. مثالی از این دست را در متد PrintSelectedTuple ملاحظه خواهید نمود. ابتدا یک لیست جنریک از Tuple ایی با دو عضو تشکیل شده است. سپس با استفاده از امکانات LINQ ، عضوی که آیتم دوم آن مساوی 2 است یافت شده و سپس المان‌های آن نمایش داده می‌شود.
- نکته‌ی دیگری را که حین کار با Tuples می‌توان در نظر داشت این است که اعضای آن حداکثر شامل 8 عضو می‌توانند باشند که عضو آخر باید یک Tuple تعریف گردد و بدیهی است این Tuple‌ نیز می‌تواند شامل 8 عضو دیگر باشد و الی آخر که نمونه‌ای از آن را در متد Tuple8 می‌توان مشاهده کرد.

مطالب
مروری بر کاربردهای Action و Func - قسمت اول
delegate‌ها، نوع‌هایی هستند که ارجاعی را به یک متد دارند؛ بسیار شبیه به function pointers در C و CPP هستند، اما برخلاف آن‌ها، delegates شی‌ء‌گرا بوده، به امضای متد اهمیت داده و همچنین کد مدیریت شده و امن به شمار می‌روند.
سیر تکاملی delegates را در مثال ساده زیر می‌توان ملاحظه کرد:
using System;

namespace ActionFuncSamples
{
    public delegate int AddMethodDelegate(int a);
    public class DelegateSample
    {
        public void UseDelegate(AddMethodDelegate addMethod)
        {
            Console.WriteLine(addMethod(5));
        }
    }

    public class Helper
    {
        public int CustomAdd(int a)
        {
            return ++a;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Helper helper = new Helper();

            // .NET 1            
            AddMethodDelegate addMethod = new AddMethodDelegate(helper.CustomAdd);
            new DelegateSample().UseDelegate(addMethod);

            // .NET 2, anonymous delegates
            new DelegateSample().UseDelegate(delegate(int a) { return helper.CustomAdd(a); });

            // .NET 3.5
            new DelegateSample().UseDelegate(a => helper.CustomAdd(a));
        }
    }
}
معنای کلمه delegate، واگذاری مسئولیت است. به این معنا که ما در متد UseDelegate، نمی‌دانیم addMethod به چه نحوی تعریف خواهد شد. فقط می‌دانیم که امضای آن چیست.
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی می‌شد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بی‌نام، ساده‌تر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم ساده‌تر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نام‌های Action و Func نیز ارائه گردیده‌اند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شده‌اند. تفاوت‌های این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت می‌دهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
Action<int> example1 = x => Console.WriteLine("Write {0}", x);
example1(5);

Func<int, string> example2 = x => string.Format("{0:n0}", x);
Console.WriteLine(example2(5000));
در دو مثال فوق، نحوه تعریف inline یک Action و یا Func را ملاحظه می‌کنید. Action به متدی اشاره می‌کند که خروجی ندارد و در اینجا تنها یک ورودی int را می‌پذیرد. Func در اینجا به تابعی اشاره می‌کند که یک ورودی int را دریافت کرده و یک خروجی string را باز می‌گرداند.

پس از این مقدمه، در ادامه قصد داریم مثال‌های دنیای واقعی Action و Func را که در سال‌های اخیر بسیار متداول شده‌اند، بررسی کنیم.


مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانه‌های ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاس‌های دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمی‌اندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار می‌کنند و کتابخانه ما نمی‌داند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده می‌کند:
using System;

namespace ActionFuncSamples
{
    public interface ISchedule
    {
        void Run();
    }

    public class Runner
    {
        public void Exceute(ISchedule schedule)
        {
            schedule.Run();
        }
    }

    public class HelloSchedule : ISchedule
    {
        public void Run()
        {
            Console.WriteLine("Just Run!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Runner().Exceute(new HelloSchedule());
        }
    }
}
در اینجا ابتدا باید این interface را در طی یک کلاس جدید (مثلا HelloSchedule) پیاده سازی کرد و سپس حاصل را در کلاس Runner استفاده نمود.
نظر شما در مورد این طراحی ساده شده چیست؟
using System;

namespace ActionFuncSamples
{
    public class Schedule
    {
        public void Exceute(Action run)
        {
            run();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Schedule().Exceute(() => Console.WriteLine("Just Run!"));
        }
    }
}
با توجه به اینکه هدف از معرفی interface در طراحی اول، واگذاری مسئولیت نحوه تعریف متد Run به کلاسی دیگر است، به همین طراحی با استفاده از یک Action delegate نیز می‌توان رسید. مهم‌ترین مزیت آن، حجم بسیار کمتر کدنویسی استفاده کننده نهایی از API تعریف شده ما است. به علاوه امکان inline coding نیز فراهم گردیده است و در همان محل تعریف Action، بدنه آن‌را نیز می‌توان تعریف کرد.
بدیهی است delegates نمی‌توانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهله‌ای از کلاسی است که یک interface را پیاده سازی کرده‌است.
علت استفاده بیش از حد interfaceها در سایر زبان‌ها برای ابتدایی‌ترین کارها، کمبود امکانات پایه‌ای آن زبان‌ها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همه‌جا از interfaceها استفاده کنند.

ادامه دارد ...

 
مطالب
واکشی اطلاعات به صورت chunk chunk (تکه تکه) و نمایش در ListView
یکی از مسائلی که در هنگام کار با کنترلهای داده‌ای نظیر ListView , GridView و .. با آن روبرو هستیم مسئله صفحه بندی می‌باشد و در بسیاری از موارد، کل اطلاعات در هر درخواست، بارگذاری میشود. در حالیکه روش بهینه به این صورت است که با توجه به PageSize و Index رکورد، می‌توان تعداد رکورد مورد نظر در همان صفحه را بارگذاری کرد، نه کل رکوردها را.
در این مثال که از Ef Code First و الگوی Unit Of Work استفاده کرده ام ، قصد نمایش اطلاعات در یک ListView و صفحه بندی آن را بصورت chunk chunk دارم.
برای آشنایی بیشتر با الگوی Unit Of Work  می‌توانید به مقاله UOW در همین سایت مراجعه کنید.
   
ابتدا یک کنترل ListView روی صفحه ایجاد می‌کنیم. برای آشنایی بیشتر با این کنترل و بررسی قابلیتهای آن میتوان از این مقاله معرفی کنترل‌های ListView و DataPager استفاده کنید.

سپس با توجه به الگوی Unit Of Work از یک مدل ساده استفاده می‌کنیم. همچنین برای بارگذاری اطلاعات به صورت صفحه به صفحه، نیاز به داشتن Index رکورد و PageSize، جهت محاسبه تعداد رکورد مورد نیاز داریم.

public class User
    {       
        public Int64 UserId { get; set; }       
        public String UserName { get; set; }
     }        
 public interface IUserService
    {        
        int GetCustomerCount();
        List<User> GetCustomers(int StartIndex, int PageSize);
    }
public class ImplUserService : IUserService
    {
        IUnitOfWork _uow;
        IDbSet<User> _user;

        public ImplUserService(IUnitOfWork uow)
        {
            _uow = uow;
            _user = uow.Set<User>();
        }

        public int GetCustomerCount()
        {
            int totalCount = _user.ToList().Count;             
            return totalCount;
        }

        public List<User> GetCustomers(int StartIndex, int PageSize)
        {
            return _user.OrderBy(i => i.UserId).Skip(StartIndex).Take(PageSize).ToList();
        }
  }
در کدهای بالا متد GetCustomerCount تعداد کل رکوردهایی که باید واکشی شوند را مشخص می‌کند. همچنین می‌توان این تعداد را در cache یا viewState ذخیره کرد تا در دفعات بعدی در صورت خالی نبودن مقدار Cacheبا viewState ، نیاز به محاسبه مجدد count نداشته باشیم.

متد GetCustomer که کار اصلی را انجام میدهد از دو پارامتر استفاده می‌کند: StartIndex نقطه شروع و PageSize تعداد رکورد مورد نظر. در اینجا از دستورات Linq استفاده شده و دستور Skip مشخص میکند از کدام شماره رکورد به بعد شروع به واکشی نماید و دستور Take مشخص می‌کند که چه تعداد رکورد را واکشی نماید.

حالا به سراغ کدهای HTML می‌رویم. در آنجا علاوه بر ListView نیاز به DataPager جهت صفحه بندی و Object DataSource جهت کنترل بارگذاری اطلاعات به صورت chunk chunk داریم.
<asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1">
            <ItemTemplate>
                <tr style="background-color: #DCDCDC; color: #000000;">
                    <td>
                        <asp:Label ID="UserIdLabel" runat="server" Text='<%# Eval("UserId") %>' />
                    </td>
                    <td>
                        <asp:Label ID="UserNameLabel" runat="server" Text='<%# Eval("UserName") %>' />
                    </td>
                </tr>
            </ItemTemplate>
            
            
            <LayoutTemplate>
                <table runat="server">
                    <tr runat="server">
                        <td runat="server">
                            <table id="itemPlaceholderContainer" runat="server" border="1" style="background-color: #FFFFFF;
                                border-collapse: collapse; border-color: #999999; border-style: none; border-width: 1px;">
                                <tr runat="server" style="background-color: #DCDCDC; color: #000000;">
                                    <th runat="server">
                                        UserId
                                    </th>
                                    <th runat="server">
                                        UserName
                                    </th>
                                </tr>
                                <tr id="itemPlaceholder" runat="server">
                                </tr>
                            </table>
                        </td>
                    </tr>
                    <tr runat="server">
                        <td runat="server" style="text-align: center; background-color: #CCCCCC;
                            color: #000000;">
                            <asp:DataPager ID="DataPager1" runat="server" PageSize="2">
                                <Fields>
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                    <asp:NumericPagerField />
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                </Fields>
                            </asp:DataPager>
                        </td>
                    </tr>
                </table>
            </LayoutTemplate>                        
        </asp:ListView>
همانظور که مشاهده میکنید در DataPager ، مقدار PageSize مشخص شده است.اما Object DataSource
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="True" SelectCountMethod="GetCustomerCount"
            SelectMethod="GetCustomers" TypeName="UserService.ImplUserService" EnableViewState="False"
            MaximumRowsParameterName="PageSize" StartRowIndexParameterName="StartIndex">
که فکر می‌کنم با توجه به متدهای تعریف شده در بالا ، تعریف Object DataSource کاملاٌ گویا میباشد.

نکته مهم:
اگر الان برنامه را اجرا کنید با خطای No parameterless constructor defined for this object ر وبرو خواهید شد که جهت حل این مشکل از کد زیر استفاده میکنیم.
protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {            
            e.ObjectInstance = ObjectFactory.GetInstance<IUserService>();            
        }
در واقع نیاز است تا یک وهله از کلاس مورد نظر را به Object DataSource معرفی کنیم.
حال با اجرای برنامه و Trace آن متوجه خواهید شد که با کلیک بر روی شماره صفحه، تنها به تعداد رکوردهای همان صفحه، واکشی خواهیم داشت.

با تشکر از راهنمایی‌های آقای نصیری، امیدوارم این مقاله برای دوستان مفید باشه. منتظر نظرات دوستان هستم و اینکه چه جوری بتونیم اینکار رو با jquery ajax و به صورت سبکتر انجام بدیم.

مطالب
پیاده سازی Full-Text Search با SQLite و EF Core - قسمت سوم - بهبود کیفیت جستجوهای FTS توسط یک غلط یاب املایی
فرض کنید کاربری برای جستجوی رکورد زیر:
context.Chapters.Add(new Chapter
{
    Title = "آزمایش متن فارسی",
    Text = "برای نمونه تهیه شده‌است",
    User = user1.Entity
});
بجای «فارسی»، واژه‌ی «فارشی» را وارد کند و یا بجای «آزمایش»، بنویسد «آزمایس». در هر دو حالت نتیجه‌ی جستجوی او خروجی را به همراه نخواهد داشت. برای بهبود تجربه‌ی کاربری جستجوی تمام متنی SQLite، افزونه‌ای به نام spell fix1 برای آن تهیه شده‌است که بر اساس توکن‌های ایندکس شده‌ی FTS، یک واژه‌نامه، تشکیل می‌شود و سپس بر اساس الگوریتم‌های غلط‌یابی املایی آن، از این توکن‌های از پیش موجود که واقعا در فیلدهای متنی بانک اطلاعاتی جاری وجود خارجی دارند، نزدیک‌ترین واژه‌های ممکن را پیشنهاد می‌کند تا بتوان بر اساس آن‌ها، جستجوی دقیق‌تری را ارائه کرد.


کامپایل افزونه‌ی spell fix1

افزونه‌ی spell fix، به همراه هیچکدام از توزیع‌های باینری SQLite ارائه نمی‌شود. ارائه‌ی آن فقط به صورت سورس کد است و باید خودتان آن‌را کامپایل کنید!


برای این منظور ابتدا به آدرس https://www.sqlite.org/src/dir?ci=99749d4fd4930ccf&name=ext/misc مراجعه کرده و فایل ext/misc/spellfix.c آن‌را دریافت کنید. اگر بر روی لینک spellfix.c کلیک کنید، در نوار ابزار بالای صفحه‌ی بعدی، لینک download آن هم وجود دارد.

سپس به صفحه‌ی دریافت اصلی SQLite یعنی https://www.sqlite.org/download.html مراجعه کرده و بسته‌ی amalgamation آن‌را دریافت کنید. این بسته به همراه کدهای اصلی SQLite است که باید در کنار افزونه‌های آن قرار گیرند تا بتوان این افزونه‌ها را کامپایل کرد. بنابراین پس از دریافت بسته‌ی amalgamation و گشودن آن، فایل spellfix.c را به داخل پوشه‌ی آن کپی کنید:


اکنون نوبت به کامپایل فایل spellfix.c و تبدیل آن به یک dll است تا بتوان آن‌را به صورت یک افزونه در برنامه بارگذاری کرد. برای این منظور از هر کامپایلر ++C ای می‌توانید استفاده کنید. برای نمونه به آدرس http://www.codeblocks.org/downloads/binaries مراجعه کرده و بسته‌ی codeblocks-20.03mingw-setup.exe را دریافت کنید (بسته‌ای که به همراه mingw است). پس از نصب آن، در مسیر C:\Program Files (x86)\CodeBlocks\MinGW\bin می‌توانید کامپایلر چندسکویی gcc را مشاهده کنید. توسط آن می‌توان با اجرای دستور زیر، سبب تولید فایل spellfix1.dll شد:
 "C:\Program Files (x86)\CodeBlocks\MinGW\bin\gcc.exe" -g -shared -fPIC -Wall D:\path\to\sqlite-amalgamation-3310100\spellfix.c -o spellfix1.dll


روش معرفی افزونه‌های SQLite به Microsoft.Data.Sqlite

EF Core، از بسته‌ی Microsoft.Data.Sqlite در پشت صحنه برای کار با SQLite استفاده می‌کند و در اینجا هم برای معرفی افزونه‌ی کامپایل شده، باید ابتدا آن‌را به اتصال برقرار شده، معرفی کرد. خود Sqlite در ویندوز، افزونه‌هایش را بر اساس معرفی مستقیم مسیر فایل dll آن‌ها بارگذاری نمی‌کند. بلکه path ویندوز را برای جستجوی آن‌ها بررسی کرده و در صورتیکه فایل dll ای را افزونه تشخیص داد، آن‌را بارگذاری می‌کند. بنابراین یا باید به صورت دستی مسیر فایل dll تولید شده را به متغیر محیطی path ویندوز اضافه کرد و یا می‌توان توسط قطعه کد زیر، آن‌را به صورت پویایی معرفی کرد:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace EFCoreSQLiteFTS.DataLayer
{
    public static class LoadSqliteExtensions
    {
        public static void AddToSystemPath(string extensionsDirectory)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                throw new NotSupportedException("Modifying the path at runtime only works on Windows. On Linux and Mac, set LD_LIBRARY_PATH or DYLD_LIBRARY_PATH before running the app.");
            }

            var path = new HashSet<string>(Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator));
            if (path.Add(extensionsDirectory))
            {
                Environment.SetEnvironmentVariable("PATH", string.Join(Path.PathSeparator, path));
            }
        }
    }
}
در این متد extensionsDirectory، همان پوشه‌ای است که فایل dll کامپایل شده، در آن قرار دارد. مابقی آن، معرفی این مسیر به صورت پویا به PATH سیستم عامل است.

در ادامه پیش از معرفی services.AddDbContext، باید مسیر پوشه‌ی افزونه‌ها را ثبت کرد و سپس UseSqlite را به همراه اتصالی استفاده کرد که توسط متد LoadExtension آن، افزونه‌ی spellfix1 به آن معرفی شده‌است:
LoadSqliteExtensions.AddToSystemPath("path to .dll file");
services.AddDbContext<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
    {
        var connection = new SqliteConnection(connectionString);
        connection.Open();

        connection.LoadExtension("spellfix1");
        // Passing in an already open connection will keep the connection open between requests.
        optionsBuilder.UseSqlite(connection);
    });
همانطور که عنوان شد، متد LoadExtension، مسیری را دریافت نمی‌کند. این متد فقط نام افزونه را دریافت می‌کند و مسیر آن‌را از PATH سیستم عامل می‌خواند.


ایجاد جداول ویژه‌ی spell fix در برنامه

در قسمت اول، با متد createFtsTables آشنا شدیم. اکنون این متد را برای ایجاد جداول کمکی مرتبط با افزونه‌ی spell fix به صورت زیر تکمیل می‌کنیم:
        private static void createFtsTables(ApplicationDbContext context)
        {
            // For SQLite FTS
            // Note: This can be added to the `protected override void Up(MigrationBuilder migrationBuilder)` method too.
            context.Database.ExecuteSqlRaw(@"CREATE VIRTUAL TABLE IF NOT EXISTS ""Chapters_FTS""
                                    USING fts5(""Text"", ""Title"", content=""Chapters"", content_rowid=""Id"");");

            // 'SQLite Error 1: 'no such module: spellfix1'.' --> must be loaded ...
            // EditCost for unicode support
            context.Database.ExecuteSqlRaw("CREATE VIRTUAL TABLE IF NOT EXISTS Chapters_FTS_Vocab USING fts5vocab('Chapters_FTS', 'row');");
            context.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS Chapters_FTS_SpellFix_EditCost(iLang INT, cFrom TEXT, cTo TEXT, iCost INT);");
            context.Database.ExecuteSqlRaw("CREATE VIRTUAL TABLE IF NOT EXISTS Chapters_FTS_SpellFix USING spellfix1(edit_cost_table=Chapters_FTS_SpellFix_EditCost);");
        }
- اگر در حین اجرای این دستورات خطای «no such module: spellfix1» را دریافت کردید، یعنی متد LoadExtension را به درستی فراخوانی نکرده‌اید.
- همانطور که مشاهده می‌کنید، ابتدا بر اساس Chapters_FTS یا همان جدول مجازی FTS برنامه، یک جدول مجازی از نوع fts5vocab ایجاد می‌شود. کار آن استخراج توکن‌های FTS و آماده سازی آن‌ها برای استفاده در غلط یاب املایی هستند.
- سپس جدول ویژه‌ی EditCost را مشاهده می‌کنید. نام آن مهم نیست، اما ساختار آن باید دقیقا به همین صورت باشد. اگر این جدول اختیاری را تهیه کنیم، الگوریتم spellfix1 به utf8 سوئیچ خواهد کرد و برای پردازش متون یونیکد، بدون مشکل کار می‌کند. بدون آن، جستجوهای فارسی نتایج مطلوبی را به همراه نخواهند داشت.
- در آخر جدول مجازی مرتبط با spellfix1 که از جدول cost_table معرفی شده استفاده می‌کند، ایجاد شده‌است.

اجرای این دستورات، جداول زیر را ایجاد می‌کنند (که ساختار آن‌ها استاندارد است و باید مطابق فرمول‌های مستندات آن‌ها باشد):



به روز رسانی جدول واژه نامه‌ی غلط یابی برنامه

آخرین جدولی را که ایجاد کردیم، Chapters_FTS_SpellFix است که اطلاعات خودش را از Chapters_FTS_Vocab دریافت می‌کند:


  هر بار که بانک اطلاعاتی را به روز می‌کنیم، نیاز است اطلاعات این جدول را نیز توسط دستور زیر به روز کرد:
database.ExecuteSqlRaw(@"INSERT INTO Chapters_FTS_SpellFix(word, rank)
    SELECT term, cnt FROM Chapters_FTS_Vocab
    WHERE term not in (SELECT word from Chapters_FTS_SpellFix_vocab)");
البته خود SQLite اطلاعات این جدول را فقط یکبار بارگذاری می‌کند. برای اجبار آن به بارگذاری مجدد، می‌توان دستور reset زیر را صادر کرد:
database.ExecuteSqlRaw("INSERT INTO Chapters_FTS_SpellFix(command) VALUES(\"reset\");");


کوئری گرفتن از جدول مجازی Chapters_FTS_SpellFix

تا اینجا افزونه‌ی spellfix1 را کامپایل و به سیستم معرفی کردیم. سپس جداول واژه نامه‌ی آن‌را نیز تشکیل دادیم، اکنون نوبت به کوئری گرفتن از آن است. به همین جهت یک موجودیت بدون کلید دیگر را بر اساس ساختار خروجی کوئری‌های آن ایجاد کرده:
namespace EFCoreSQLiteFTS.Entities
{
    public class SpellCheck
    {
        public string Word { get; set; }
        public decimal Rank { get; set; }
        public decimal Distance { get; set; }
        public decimal Score { get; set; }
        public decimal Matchlen { get; set; }
    }
}
و آن‌را توسط متد HasNoKey به EF Core معرفی می‌کنیم:
namespace EFCoreSQLiteFTS.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
        //...

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

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

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

در آخر، کوئری گرفتن از این جدول، ساختار زیر را دارد:
foreach (var item in context.Set<SpellCheck>().FromSqlRaw(
          @"SELECT word, rank, distance, score, matchlen FROM Chapters_FTS_SpellFix
            WHERE word MATCH {0} and top=6", "فارشی"))
{
    Console.WriteLine($"Word: {item.Word}");
    Console.WriteLine($"Distance: {item.Distance}");
}
با این خروجی:


top=6 در این کوئری خاص یعنی 6 رکورد را بازگشت بده.

یک نکته: اگر می‌خواهید کوئری فوق را توسط برنامه‌ی «DB Browser for SQLite» اجرا کنید، باید از منوی tools آن، گزینه‌ی load extension را انتخاب کرده و فایل dll افزونه را به برنامه معرفی کنید.


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
مطالب
نکاتی توصیه ای برای برنامه نویسی اندروید : قسمت دوم
در ادامه‌ی قسمت اول، ده مورد دیگر از نکات کاربردی را بیان می‌کنیم.

  یازده. در جاوا رویدادها با استفاده از اینترفیس‌ها پیاده سازی می‌شوند. برای نامگذاری یک رویداد، قاعده آن در جاوا بدین شکل است که نام‌ها به صورت (+ ) Camel نوشته شده و آخرین عبارت هم Listener باشد و نیازی هم به حرف I در نامگذاری اینترفیس نیست؛ چون همه می‌دانند که این Listener آخری یعنی رویدادی که با اینترفیس پیاده سازی شده است و استفاده از I بی معنی است. هر چند بر خلاف دات نت، در اینجا استفاده از قاعده I چندان متداول نیست.
 public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }

دوازده. گوگل اینترفیس‌هایی را که برای رویدادها میسازد، داخل کلاس اصلی تعریف میکند. پس بهتر هست که شما هم همین روند را ادامه بدید و از این قاعده خارج نشوید. اگر خوب دقت کرده باشید، در برنامه نویسی اندروید تمام اینترفیس‌ها داخل کلاس اصلی هستند:
 textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
در کد بالا رویداد OnclickListener در خود کلاس View تعریف شده است. پس ما هم بهتر هست همین کار را بکنیم:
public class MemoryWare {

    public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }
....
}
یک نکته دیگر اینکه موقعی که یک رویداد را به یک پراپرتی set انتساب می‌دهیم، رسم این است که نام آن پراپرتی با عبارت SetOn آغاز شود و با نام اینترفیس پایان یابد:
SetOnClickListener
البته اگر کلاس شما لیستی از رویدادها را درست میکند بهتر است از عبارت Add به جای SetOn استفاده کنید و برای آن یک Remove هم قرار دهید. نمونه آن را می‌توانید در کد زیر ببینید:
 editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

سیزده
. آداپتورها و آداپتور ویوها (چون لیست) قسمت مهمی از برنامه‌های اندرویدی به شمار می‌آیند؛ تا حدی که در بیشتر برنامه‌های ساده هم حضور پررنگی دارند. ولی برای استفاده از این آداپتورها باید بدانید که نحوه کار آن‌ها چگونه است. بسیاری از کاربران در این قسمت اشتباهات زیادی می‌کنند. اگر در stackOverflow هم در اینباره نگاه کنید، با حجم انبوهی از سوالات روبرو می‌شوید و فقط به خاطر اینکه نحوه کارکرد آن را نمی‌دانند، به مشکل برخورده‌اند.
کلاس BaseAdapter اصلی‌ترین کلاس آداپتور هاست که بقیه از آن مشتق شده‌اند و معروفترین مشتقات آن، کلاس‌های CursorAdapter و ArrayAdapter هستند که امکانات بیس آداپتور را افزایش داده‌اند.به عنوان مثال در کد پایین از ArrayAdapter استفاده شده است.


نحوه کار یک آداپتور بدین صورت است که متدی را به نام GetView با قابلیت override دارد که با هر تعداد آیتم موجود صدا زده می‌شود. ولی اگر تصور کنیم فقط چند صدهزار آیتم هم داشته باشیم، آیا واقعا اجرا می‌شود؟ جواب این سوال این است که با هر بار اسکرولی که شما میکنید آیتم‌های بعدی ایجاد می‌شوند ولی باز این سوال پیش می‌آید که هر آیتم برای خود جداگانه تشکیل می‌شود؟ مطمئنا جواب خیر است. آداپتورها از سیستمی به نام ViewRecycler برای کش کردن آیتم‌های ایجاد شده استفاده می‌کنند و با هر اسکرولی که انجام می‌شود آیتم‌های بعدی از روی آیتم‌های قبلی که قبلا از صفحه خارج شده‌اند، ساخته می‌شوند و آیتم‌های کش شده قبلی را با پارامتری با نام convertView به دست شما می‌رساند.
کد زیر را ببینید:
  @Override
    public View getView(int position, View rowView, ViewGroup parent) {

        ViewHolder viewHolder=null;
        if(rowView==null)
        {
            // 1. Create inflater
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            // 2. Get rowView from inflater
            rowView = inflater.inflate(R.layout.row_bank_group_list, parent, false);
            viewHolder=new ViewHolder();
            viewHolder.txtGroupName=(TextView) rowView.findViewById(R.id.text_groupName);
            rowView.setTag(viewHolder);
        }
        else
        {
            viewHolder=(ViewHolder)rowView.getTag();
        }
        viewHolder.txtGroupName.setText(getItem(position).getName());
        viewHolder.txtGroupName.setTypeface(new FontSystem().get_General_Font(context));
        viewHolder.txtGroupName.setTextColor(context.getResources().getColor(R.color.black));

        return rowView;
    }
کد بالا ابتدا بررسی میکند که آیا convertView نال است یا خیر. اگر نال بود به این معنا است که کش برای شما چیزی نداشته است و باید آیتم جدیدی را بسازید. پس اشیاء موجود در آن را در حافظه می‌آورید و مقداردهی می‌کنید. ولی اگر برابر نال نباشد، یعنی کش حاوی یک سری آیتم است که قبلا ایجاد شده‌اند. پس نیاز به inflate کردن مجدد ندارد و میتوانید  همان را مستقیما مورد استفاده قرار دهید و با مقادیر جدید آن را ست کنید.
کلاس داخلی ViewHolder هم یک الگو برای عدم بررسی Viewهای داخل آن است که نیازی به یافتن و تبدیل مجدد آن‌ها نداشته باشید. در این روش شیء، داخل خصوصیت tag آیتم قرار گرفته است و وقتی از کش برداشته شود، خاصیت تگ آن را می‌خوانیم و مستقیما مورد استفاده قرار می‌دهیم. در این حالت شما بهترین استفاده را از پردازش‌ها و حافظه، می‌کنید.

چهارده. یکی از کارهایی را که قبل از کار کردن در یک مسیر فیزیکی باید انجام دهید این است که مطمئن باشید اجازه نوشتن در آن ناحیه را دارید یا خیر. در غیر اینصورت برنامه شما با خطای FC روبرو می‌شود و اجرای آن خاتمه می‌یابد. به همین دلیل اکثر برنامه نویسان از متد CanWrite در کلاس File استفاده می‌کنند. ولی در هنگام استفاده از این متد باید دقت داشته باشید که کلاس File فقط باید حاوی مسیر باشد و اسمی از فایل مربوطه در آن نباشد. دلیل هم آن است که این احتمال می‌رود اگر فایلی هم وجود نداشته باشد، مقدار false را به شما برگرداند. مثال زیر قرار است فایلی را در کارت حافظه بنویسید، ولی بررسی اجازه نوشتن در مسیر، اشتباه است:
File file=new File(sdcardPath,fileName);

if(file.CanWrite())
{
  .....
}
کد بالا را به طور صحیح بازنویسی می‌کنیم:
File file=new File(sdcardPath);

if(file.CanWrite())
{
file=new File(sdcardPath,filePath);
  .....
}
اگر هنگام تست این کد مشکلی نداشتید، دلیل بر صحت کد نیست. بلکه بنابر تجربه شخصی من، زمانی این مشکل پیش آمده بود که روی چند گوشی تست شده و بعدها مشکل در گوشی پیش آمده بود که هم مدل و دقیقا مشابه یکی از گوشی‌های تستی بود.

پانزده. کارت حافظه خارجی: همه برنامه نویسان اندروید حداقل یکبار از کد زیر استفاده کرده اند:
Environment.getExternalStorageDirectory()
بررسی‌ها در استک اورفلو نشان می‌دهد که برنامه نویسان، گزارش عملکرد اشتباهی را از این متد دارند. ولی حقیقت این است که این متد به هیچ عنوان مقدار اشتباهی را بر نمی‌گرداند. بلکه منطق آن متفاوت از چیزی است که شما فکر می‌کنید. وقتی ما صحبت از حافظه خارجی برای یک گوشی میکنیم، منظور همان کارت حافظه‌ای است که به طور جداگانه داخل گوشی قرار داده‌ایم و انتظار داریم متد بالا آدرس و مدخل همین کارت را برای ما فراهم کند. ولی در کمال تعجب می‌بینیم که آدرس حافظه داخلی برگردانده می‌شود. پس باید ببینیم اندروید از  آن چه انتظاری دارد؟
هر برنامه‌ای که در اندروید نصب می‌شود در مسیر
/Data/Data
یک دایرکتوری با نام خود دارد مثل:
/Data/Data/Info.Dotnettips.MyApp
این دایرکتوری تنها متعلق به این برنامه بوده و کسی جز Root به آن دسترسی ندارد. اندروید این دایرکتوری را به عنوان حافظه داخلی در نظر میگیرد که برای کار با آن نیاز به هیچ آدرس دهی نیست. ولی در کنار این دایرکتوری حافظه داخلی خود گوشی که در آن انبوه فایل‌های خود را ذخیره می‌کنید هم هست که اندروید آن را حافظه خارجی می‌پندارد. حال آن حافظه‌ای را که شما جداگانه به صورت یک کارت یا USB OTG متصل میکنید، به عنوان حافظه خارجی در نظر نمیگیرد. در صورتی که شما چنین انتظاری را دارید، برای حل این مشکل بهتر است از کدهای موجود مثل کد زیر استفاده کنید:
    /**
     * it will returns sd path for you
     *  <p>
     *  <b>Required Permission: </b>android.permission.READ_EXTERNAL_STORAGE<br/>
     * </p>
     * @return
     */
    public  List<String> GetExternalMounts() {
        final List<String> out = new ArrayList<>();
        String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
        String s = "";
        try {
            final Process process = new ProcessBuilder().command("mount")
                    .redirectErrorStream(true).start();
            process.waitFor();
            final InputStream is = process.getInputStream();
            final byte[] buffer = new byte[1024];
            while (is.read(buffer) != -1) {
                s = s + new String(buffer);
            }
            is.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // parse output
        final String[] lines = s.split("\n");
        for (String line : lines) {
            if (!line.toLowerCase(Locale.US).contains("asec")) {
                if (line.matches(reg)) {
                    String[] parts = line.split(" ");
                    for (String part : parts) {
                        if (part.startsWith("/"))
                            if (!part.toLowerCase(Locale.US).contains("vold"))
                                if(new File(part).canWrite())
                                    out.add(part);
                    }
                }
            }
        }
        return out;
    }

شانزده.
یکی از روش‌های انتقال اطلاعات بین اکتیویتی‌ها مختلف استفاده از Extras هاست که شما با تعیین یک نام یا کلید، اطلاعات مربوطه را ارسال و توسط همان کلید؛ اطلاعات را در اکتیویتی مقصد دریافت میکنید:
notesIntent.putExtra("PartyId", PartyId);
startActivity(notesIntent);
و در مقصد:
PartyId=getIntent().getLongExtra("PartyId",0);
در این حالت بهتر است با این رشته‌ها نیز همانند مورد شماره دو در قسمت اول رفتار شود تا نیازی به نوشتن و تکرار این نام‌ها نباشد. ولی با یک نگاه به استانداردهای نوشته شده در خود اندروید و بسیاری از کتابخانه‌های ثالث در می‌یابیم که بهترین روش این است که این کلید‌ها را به صورت متغیرهای ایستا در خود اکتیویتی ذخیره کنیم؛ در این حالت هر کلید در جایگاه واقعی خودش قرار گرفته است. نمونه‌ای از این استفاده را می‌توانید در کتابخانه FilePicker مشاهده کنید:
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);

هفده.
قواعد نامگذاری: برای نامگذاری متغیرها از قانون CamelCase استفاده میکنیم. ولی برای حالات زیر از روش‌های دیگر استفاده می‌شود:
  • برای ثابت‌ها از حروف بزرگ و _ استفاده کنید.
  • برای متغیرهای خصوصی از حرف m در ابتدای نام متغیر استفاده کنید.
  • برای متغیرهای استاتیک از حرف s در ابتدای نام متغیر استفاده کنید.
نمونه ای از این نامگذاری که توسط مستندات گوگل سفارش شده است:
public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

هجده:
قاعده نظم و ترتیب در import‌ها توسط مستندات گوگل بدین شکل تعریف شده است:
  1. نام پکیج‌های ارائه شده توسط گوگل
  2. نام پکیج‌های ثالث
  3. نام پکیج‌های موجود در java و javax
  4. پکیج‌های موجود در پکیج اصلی
البته رعایت این قاعده کمی سخت و عموما بدون اجراست ولی نگران نباشید. از آنجایی که ادیتور از طرف jet brains ارائه شده‌است و عمل import به طور خودکار صورت میگیرد و با ابزارهای دیگری که در دل این ادیتور قرار گرفته است، این عمل به طور خودکار انجام می‌گیرد.

نوزدهم. مرتب سازی متدهای دسترسی یک کلاس: بسیار خوب است که همیشه کدهای ما نظم خاصی را داشته باشد تا پیگیری‌های شخصی و تیمی در آن راحت‌تر صورت بگیرد. برای مثال در یک کلاس ابتدا متدهای public و سپس private قرار گیرند و الی آخر.
الگوی عمومی که برای کار با جاوا صورت گرفته است به شکل زیر می‌باشد:
public, protected, private,abstract, static, transient, volatile, synchronized, final, native.
البته این متدهای دسترسی بسته به فیلد بودن و متد بودن نیز تغییر میکند. به عنوان مثال ابتدا فیلدها در نظر گرفته می‌شوند و سپس متدها و ...
ادیتور intelij شامل تنظمیاتی برای مرتب سازی کدهاست که در این مورد بسیار سودمند است. با طی کردن مسیر زیر می‌توانید برای آن ترتیب اینگونه موارد را مشخص کنید.
Settings>Editor>Code Style>Arrangement
در اینجا شما امکان تعاریف این ترتیب‌ها را دارید. البته بهتر هست در حالت پیش فرض باشد تا حالتی عمومی بین افراد مختلف برقرار باشد.

در تصویر بالا متدها به ترتیب متدهای دستری بین بلوک‌های کامنت method start و method end قرار گرفته اند.

 همچنین شامل گزینه‌های دیگری نیز میباشد که به نظرم فعال کردنشان بسیار خوب است. گزینه keep overridden methods together به شما کمک می‌کند تا متدهایی را که رونویسی می‌شوند، در کنار یکدیگر قرار بگیرند که برای کلاس‌های اندرویدی مثل اکتیویتی‌ها و فرگمنت‌ها و ... بسیار خوب است. گزینه مفید دیگر Keep dependent methods together است که در دو حالت عمقی یا خطی متدهای وابسته (متدهایی که متدهای دیگر را در آن کلاس صدا میزنند) در کنار یکدیگر قرار میدهد و مابقی گزینه‌ها، که بسیار سودمند هست. به هر حال هر قاعده‌ای را که برای خود انتخاب میکنید اگر در حالت پیش فرض نیست بهتر است در مستندات پروژه ذکر شود تا افراد دیگر سریعتر به موضوع پی ببرند.

قسمت بیستم. این مورد برای افراد تازه کار می‌باشد که تازه اندروید استادیو را باز کرده‌اند و مشغول کدنویسی می‌باشند. یکی از مواردی که در همان مرحله اول به آن برمیخورید این است که intellisense  ادیتور به بزرگی و کوچکی حروف حساس است و تنها با حرف اول سازگاری دارد. برای تغییر این مسئله باید مسیر زیر را طی کنید:
Settings>Editor>Completion>Case-sensitive Completion>None
حالا با تایپ هر کلمه، دستورات و آبجکت‌هایی را که شامل آن کلمات باشند، به شما نمایش داده خواهند شد.

نظرات مطالب
بررسی نحوه‌ی راه اندازی پروژه‌ی Decision
با سلام؛ برای راه اندازی به این خطا خوردم:
 The path specified by "C:\FileStream\ApplicantDocuements" is not in a valid directory.
One or more files listed in the statement could not be found or could not be initialized.
پوشه FileStream را هم ساخته‌ام. 
بازخوردهای دوره
انتقال خواص محاسباتی Entity Framework به ViewModelها توسط AutoMapper
نکته تکمیلی
جهت گرفتن تعداد رکورد‌های یک Navigation Property هنگام استفاده از EF میتوان از قوانین توکار خود Automapper استفاده کرد و نیازی به نوشتن ForMember وجود ندارد.
public class ProductInfoViewModel
{
    // long
    public long ProductCommentsLongCount { get; set; }

    // int
    public int ProductCommentsCount { get; set; }
}
اسم Navigation property + اسم متود، اگر کلید اصلی جدول کامنت int  باشه از ProductCommentsCount و اگر هم long باشه از ProductCommentsLongCount استفاده میکنیم. 
public class Product
{
    #region Properties

    public long Id { get; set; }

    [Required]
    [MaxLength(200)]
    public string Title { get; set; }

    public int Price { get; set; }

    #endregion

    #region Relations

    public ICollection<ProductComment> ProductComments { get; set; }

    #endregion
}

public class ProductComment
{
    #region Properties

    public int Id { get; set; }

    public string CommentBody { get; set; }

    public long ProductId { get; set; }

    #endregion

    #region Relations

    public Product Product { get; set; }

    #endregion
}

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        this.CreateMap<Entities.Product, ProductInfoViewModel>();
    }
}
دیگه نیازی به نوشتن ForMember برای گرفتن Count وجود نداره.
مطالب
PowerShell 7.x - قسمت چهارم - نوشتن اولین اسکریپت
دستوراتی که درون کنسول مینویسیم، تک خطی یا one-linear هستند؛ هر چند میتوان با زدن کلیدهای Shift + Enter دستورات چندخطی هم نوشت یا حتی با گذاشتن semicolon بعد از هر دستور میتوانیم دریک خط چندین دستور را پشت‌سر هم بنویسیم. اما برای نوشتن دستورات طولانی‌تر بهتر است دستورات را درون فایل‌های جدایی قرار دهیم و از VSCode یا PowerShell ISE (فقط در ویندوز) نیز برای نوشتن اسکریپت‌ها استفاده کرد. اسکریپت‌های PowerShell با پسوند ps1 و psm1 (برای نوشتن ماژول) هستند؛ هر چند چندین پسوند دیگر نیز برای فایل‌های PowerShell وجود دارند که در اینجا میتوانید لیست آنها را مشاهده کنید. درون یک فایل ps1 امکان نوشتن و ترکیب دستورات مختلف را داریم. همچنین میتوانیم از امکانات زبان سی‌شارپ هم استفاده کنیم؛ زیرا PowerShell در واقع اپلیکیشنی است که توسط NET Core. و با زبان #C نوشته شده‌است. در نتیجه میتوانیم بگوئیم زبان اسکریپتی که در PowerShell استفاده میشود، یک DSL برای زبان #C است. در PowerShell همه چیز یک آبجکت محسوب میشود. برای تست این مورد میتوانید درون کنسول PowerShell دستور زیر را وارد کنید:
PS> "" | Get-Member
دستور فوق یک لیست از تمامی توابع و پراپرتی‌های نوع System.String را نمایش خواهد داد:
   TypeName: System.String

Name                 MemberType            Definition
----                 ----------            ----------
Clone                Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo            Method                int CompareTo(System.Object value), int CompareTo(strin…
Contains             Method                bool Contains(string value), bool Contains(string value…
CopyTo               Method                void CopyTo(int sourceIndex, char[] destination, int de…
EndsWith             Method                bool EndsWith(string value), bool EndsWith(string value…
EnumerateRunes       Method                System.Text.StringRuneEnumerator EnumerateRunes()
Equals               Method                bool Equals(System.Object obj), bool Equals(string valu…
GetEnumerator        Method                System.CharEnumerator GetEnumerator(), System.Collectio…
GetHashCode          Method                int GetHashCode(), int GetHashCode(System.StringCompari…
GetPinnableReference Method                System.Char&, System.Private.CoreLib, Version=6.0.0.0, …
GetType              Method                type GetType()
GetTypeCode          Method                System.TypeCode GetTypeCode(), System.TypeCode IConvert…
IndexOf              Method                int IndexOf(char value), int IndexOf(char value, int st…
IndexOfAny           Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] any…
Insert               Method                string Insert(int startIndex, string value)
IsNormalized         Method                bool IsNormalized(), bool IsNormalized(System.Text.Norm…
LastIndexOf          Method                int LastIndexOf(string value, int startIndex), int Last…
LastIndexOfAny       Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(ch…
Normalize            Method                string Normalize(), string Normalize(System.Text.Normal…
PadLeft              Method                string PadLeft(int totalWidth), string PadLeft(int tota…
PadRight             Method                string PadRight(int totalWidth), string PadRight(int to…
Remove               Method                string Remove(int startIndex, int count), string Remove…
Replace              Method                string Replace(string oldValue, string newValue, bool i…
ReplaceLineEndings   Method                string ReplaceLineEndings(), string ReplaceLineEndings(…
Split                Method                string[] Split(char separator, System.StringSplitOption…
StartsWith           Method                bool StartsWith(string value), bool StartsWith(string v…
Substring            Method                string Substring(int startIndex), string Substring(int …
ToBoolean            Method                bool IConvertible.ToBoolean(System.IFormatProvider prov…
ToByte               Method                byte IConvertible.ToByte(System.IFormatProvider provide…
ToChar               Method                char IConvertible.ToChar(System.IFormatProvider provide…
ToCharArray          Method                char[] ToCharArray(), char[] ToCharArray(int startIndex…
ToDateTime           Method                datetime IConvertible.ToDateTime(System.IFormatProvider…
ToDecimal            Method                decimal IConvertible.ToDecimal(System.IFormatProvider p…
ToDouble             Method                double IConvertible.ToDouble(System.IFormatProvider pro…
ToInt16              Method                short IConvertible.ToInt16(System.IFormatProvider provi…
ToInt32              Method                int IConvertible.ToInt32(System.IFormatProvider provide…
ToInt64              Method                long IConvertible.ToInt64(System.IFormatProvider provid…
ToLower              Method                string ToLower(), string ToLower(cultureinfo culture)
ToLowerInvariant     Method                string ToLowerInvariant()
ToSByte              Method                sbyte IConvertible.ToSByte(System.IFormatProvider provi…
ToSingle             Method                float IConvertible.ToSingle(System.IFormatProvider prov…
ToString             Method                string ToString(), string ToString(System.IFormatProvid…
ToType               Method                System.Object IConvertible.ToType(type conversionType, …
ToUInt16             Method                ushort IConvertible.ToUInt16(System.IFormatProvider pro…
ToUInt32             Method                uint IConvertible.ToUInt32(System.IFormatProvider provi…
ToUInt64             Method                ulong IConvertible.ToUInt64(System.IFormatProvider prov…
ToUpper              Method                string ToUpper(), string ToUpper(cultureinfo culture)
ToUpperInvariant     Method                string ToUpperInvariant()
Trim                 Method                string Trim(), string Trim(char trimChar), string Trim(…
TrimEnd              Method                string TrimEnd(), string TrimEnd(char trimChar), string…
TrimStart            Method                string TrimStart(), string TrimStart(char trimChar), st…
TryCopyTo            Method                bool TryCopyTo(System.Span[char] destination)
Chars                ParameterizedProperty char Chars(int index) {get;}
Length               Property              int Length {get;}
در واقع میتوانیم بگوئیم هرچیزی در PowerShell یک آبجکت NET. است. در ادامه لیستی را از قابلیت‌های PowerShell به عنوان یک زبان اسکریپتی، بررسی خواهیم کرد.

تعریف متغیر
برای تعریف یک متغیر از علامت $ قبل از نام متغیر استفاده میکنیم. نوع متغیر نیز براساس مقداری که به آن انتساب داده میشود، تعیین خواهد شد: 
$stringVariable = "Hello World"
$letter = 'A'
$isEnabled = $false
$age = 33
$height = 76
$doubleVar = 54321.21
$singleVar = 76549.11
$longVar = 2382.22
$dateVar = "July 24, 1986"
$arrayVar = "A", "B", "C"
$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
همچنین میتوانیم نوع متغیر را نیز به صورت صریح تعیین کنیم: 
[string]$stringVariable = "Hello World"
[char]$letter = 'A'
[bool]$isEnabled = $false
[int]$age = 33
[decimal]$height = 76
[double]$doubleVar = 54321.21
[single]$singleVar = 76549.11
[long]$longVar = 2382.22
[DateTime]$dateVar = "July 24, 1986"
[array]$arrayVar = "A", "B", "C"
[hashtable]$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
لازم به ذکر است scope متغیرها در حالت پیش‌فرض به local تنظیم میشود. به این معنا که در جایی که تعریف میشوند، قابل دسترسی خواهند بود. قاعدتاً اگر متغیرها را در ابتدای اسکریپت تعریف کنید، میدان دید آن به صورت سراسری خواهد بود و در هرجایی از کد در دسترس خواهند بود. اما برای اینکه به صورت صریح یک متغیر را به صورت سراسری تعریف کنیم میتوانیم از کلمه‌کلیدی global بعد از تعیین نوع متغیر و قبل از علامت $ استفاده کنیم: 
$global:stringVariable = "Hello World"

// Or

[string]$global:stringVariable = "Hello World"

عبارات شرطی
همانند دیگر زبان‌های اسکریپتی، در PowerShell نیز قابلیت تعریف ساختارهای شرطی وجود دارد: 
$guess = 20
switch ($guess) {
    {$_ -eq 20} { Write-Host "You guessed right!" }
    {$_ -gt 20} { Write-Host "You guessed too high!" }
    {$_ -lt 20} { Write-Host "You guessed too low!" }
    default { Write-Host "You didn't guess a number!" }
}

$guess = 20
if ($guess -eq 20) { 
    Write-Host "You guessed right!" 
}
elseif ($guess -gt 20) { 
    Write-Host "You guessed too high!" 
}
elseif ($guess -lt 20) { 
    Write-Host "You guessed too low!" 
}
else { 
  Write-Host "You didn't guess a number!"
}
همانطور که مشاهده میکنید از یکسری اپراتور برای بررسی شرط‌ها استفاده شده است. در اینجا میتوانید لیست کامل آنها را مشاهده کنید. لازم به ذکر است که از PowerShell 7.0 به بعد نیز Ternary Operator اضافه شده است: 
$message = (Test-Path $path) ? "Path exists" : "Path not found"

حلقه‌ها
همچنین از حالت‌های مختلف loop نیز پشتیبانی میشود: 
[int]$num = 10
for ($i = 1; $i -le $num; $i++) {
    Write-Host "`n"

    for ($j = 1; $j -le $num; $j++) {
        Write-Host -NoNewline -ForegroundColor Green ($i * $j).ToString().PadLeft(6)
    }
}
Write-Host "`n`n"

[int]$counter = 1
while ($counter -le 10) {
    Write-Host "Hello World"
    $counter++
}

do {
    Write-Host "Hello World"
    $counter++
} while ($counter -le 10)

foreach ($i in 1..10) {
    Write-Host "Hello World"
}

$items = "Hello", "World"
$items | ForEach-Object {
    Write-Host $_
}

لازم به ذکر است برای ForEach-Object از % نیز میتوانید استفاده کنید. اما بطور کلی بهتر است تا حد امکان از aliaseها استفاده نکنید؛ زیرا خوانایی کد را مقداری سخت میکند. این مورد توسط خود VSCode هم هشدار داده میشود: 


آرایه‌ها
در PowerShell به صورت پیش‌فرض آرایه‌ها از نوع []System.Object در نظر گرفته میشوند. در اینجا نیز آرایه‌ها immutable هستند. چندین روش برای ایجاد آرایه وجود دارد. در ادامه یک آرایه خالی را تعریف کرده‌ایم: 
$array = @()
یک روش دیگر تعریف آرایه اینگونه است: 
$myArray = [Object[]]::new(10)
$byteArray = [Byte[]]::new(100)
$ipAddresses = [IPAddress[]]::new(5)

$mylist = [System.Collections.Generic.List[int]]::new()
از PowerShell 5.0 به بعد میتوانیم سینتکس namespaceها را با کمک using خلاصه‌تر بنویسیم:
using namespace System.Collections.Generic
$mylist = [List[int]]::new()
از range operator هم میتوانیم برای مقداردهی یک آرایه استفاده کرد: 
$numbers = 1..10
$alphabet = "a".."z"

foreach ($item in 1..10) {
    Write-Host "Hello World"
}
برای دسترسی به عناصر یک آرایه نیز میتوانیم از range operator استفاده کنیم: 
$numbers = 1..10

$numbers
Write-Output "".PadRight(10, "-")
$numbers[1..2 + 2..4]
همانطور که مشاهده میکنید از اپراتور + برای append کردن یک بازه از اعداد، به آرایه استفاده کرده‌ایم. دقت داشته باشید که با هر بار اضافه/حذف آیتم‌ها، یک آرایه جدید ساخته میشود. این مورد در آرایه‌هایی با سایز بزرگ میتواند مشکل کارآیی ایجاد کند. بنابراین اگر عملیات اضافه کردن و حذف کردن از یک آرایه را زیاد انجام میدهید، بهتر است از ArrayList استفاده کنید. تفاوت آن نیز این است که برخلاف آرایه‌های عادی، سایز ArrayList ثابت نیست. تعریف ArrayList نیز اینگونه است: 
$colours = [System.Collections.ArrayList]@("Red", "Green", "Blue")
$colours.Add("Yellow")
$colours.Remove("Red")
از دیگر کالکشن‌های NET. نیز میتوانید استفاده کنید؛ به عنوان مثال در مثال زیر از List برای اضافه کردن ده هزار آیتم استفاده کرده‌ایم:
using namespace System.Collections.Generic

$mylist = [List[int]]::new()
Measure-Command { 1..100000 | ForEach-Object { $mylist.Add($_) } }
Measure-Command { $mylist.Where({ $_ -gt 10000 }) }
Measure-Command { $mylist.Contains(10000) }
نکته: کد فوق در حالت استفاده از ArrayList مقداری کندتر است. دلیل آن نیز این است که ArrayList امکان اضافه کردن هر آبجکتی را به ما میدهد. در نتیجه موقع جستجو باید یکبار عملیات unboxing را برای تشخیص نوع درخواست شده انجام دهد.

Hashtable
ساختار دیگری که میتوانید استفاده کنید Hash Tableها هستند؛ از این ساختار برای ایجاد custom objects و همچنین پاس دادن پارامترها به یک command استفاده میشود:
$sensors = @{
    tempreture = "Temperature"
    humidity   = "Humidity"
    pressure   = "Pressure"
    light      = "Light"
    noise      = "Noise"
    co2        = "CO2"
    battery    = "Battery"
    min_temp   = "Min Temp"
    max_temp   = "Max Temp"
}
با دستور زیر میتوانید لیستی از Commandهایی که ورودی‌شان از نوع Hash table است را مشاهده کنید:
PS> Get-Command -ParameterType Hashtable


CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        TabExpansion2                                                 
Cmdlet          Add-Member                                         7.0.0.0    Microsoft.Powe…
Cmdlet          ConvertTo-Html                                     7.0.0.0    Microsoft.Powe…
Cmdlet          Get-Job                                            7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-Command                                     7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-RestMethod                                  7.0.0.0    Microsoft.Powe…
Cmdlet          Invoke-WebRequest                                  7.0.0.0    Microsoft.Powe…
Cmdlet          New-Object                                         7.0.0.0    Microsoft.Powe…
Cmdlet          New-PSRoleCapabilityFile                           7.2.7.500  Microsoft.Powe…
Cmdlet          New-PSSession                                      7.2.7.500  Microsoft.Powe…
Cmdlet          Remove-Job                                         7.2.7.500  Microsoft.Powe…
Cmdlet          Select-Xml                                         7.0.0.0    Microsoft.Powe…
Cmdlet          Set-PSReadLineOption                               2.1.0      PSReadLine
Cmdlet          Stop-Job                                           7.2.7.500  Microsoft.Powe…
Cmdlet          Wait-Job                                           7.2.7.500  Microsoft.Powe…

توابع
برای ایجاد توابع در PowerShell میتوانید از سینتکس زیر استفاده کنید:
function Write-HelloWorld {
    Write-Host "Hello World"
}
برای نامگذاری توابع بهتر است از قالب verb-noun استفاده کنید. برای قسمت verb نیز بهتر است از یکسری افعال تائیدشده (approved verbs) که مستندات مایکروسافت پیشنهاد میدهد استفاده کنید (+) در این زمینه VSCode در صورت انتخاب یک نام نامناسب به شما هشدار خواهد:

تفاوت بین Function و Cmdlet چیست؟
توابع و cmdletها عملاً از لحاظ کاربرد باهم تفاوتی ندارند. تنها تفاوت آنها در نحوه ساخت‌شان است. cmdletها با #C ساخته میشوند. به این معنا که یکسری DLL کامپایل شده هستند که در نهایت از آنها استفاده خواهیم کرد. اما توابع در PowerShell با کمک سینتکسی که اشاره شد ایجاد میشوند. توسط دستور زیر میتوانیم لیستی از توابعی را که درون سشن PowerShellمان بارگذاری شده‌اند، ببینیم: 
PS> Get-Command -CommandType Function

در PowerShell نیز توابع قابلیت دریافت ورودی را نیز دارند. در ساده‌ترین حالت میتوانیم از آرایه args$ استفاده کنیم؛ دقیقاً چیزی مشابه arguments در JavaScript است:
function Write-HelloWorld {
    Write-Host "First Argument: $($args[0])"
    Write-Host "Second Argument: $($args[1])"
    Write-Host "Third Argument: $($args[2])"
    Write-Host "Fourth Argument: $($args[3])"
}
به این حالت Positional Parameters گفته میشود. یک روش دیگر تعریف پارامتر استفاده از Named Parameters است:
function Write-HelloWorld(
    [string]$first,
    [string]$second,
    [string]$third,
    [string]$fourth
) {
    Write-Host "First Argument: $($first)"
    Write-Host "Second Argument: $($second)"
    Write-Host "Third Argument: $($third)"
    Write-Host "Fourth Argument: $($fourth)"
}

در قسمت بعد در مورد Advanced Functionها صحبت خواهیم کرد و اجزای دیگر توابع را بیشتر توضیح خواهیم داد.

یک نکته در مورد خروجی دستورات درون کنسول
در ویندوز میتوانیم خروجی کنسول را به Out-GridView پایپ کنیم که در واقع یک GUI برای نمایش دادن خروجی کنسول است. این کامند فقط در ویندوز قابل استفاده است:

یک نسخه cross-platform آن نیز که مناسب کنسول است تهیه شده که میتوانید از آن استفاده کنید: 
Get-Command -ParameterType Hashtable | Out-ConsoleGridview
با این خروجی:

البته به صورت پیش‌فرض نصب نیست و باید از طریق PowerShell Gallery آن را نصب کنید:

Install-Module -Name Microsoft.PowerShell.ConsoleGuiTools