اشتراک‌ها
بیش از 50 سوال Angular در مصاحبه ها به همراه پاسخ

It’s time to get serious about improving your programming skills. Let’s do it!

That’s an easy career improvement goal to give oneself, but “become a kick-$!! programmer” is not a simple goal. For one thing, saying, “I want to get better” assumes that you recognize what “better” looks like. Plus, too many people aim for improvement without any sense of how to get there. 

بیش از 50 سوال Angular در مصاحبه ها به همراه پاسخ
اشتراک‌ها
استفاده از کلمه کلیدی new در عنوان متدی که با متدی در base class آن همنام است
When used as a declaration modifier, the new keyword explicitly hides a member that is inherited from a base class. When you hide an inherited member, the derived version of the member replaces the base class version. Although you can hide members without using the new modifier, you get a compiler warning. If you use new to explicitly hide a member, it suppresses this warning. 
استفاده از کلمه کلیدی new در عنوان متدی که با متدی در base class آن همنام است
اشتراک‌ها
کتابخانه instafetch
instafetch fetches media from Instagram based on (and only on) the user and/or tag, relying on the Instagram API.

If you use the Instagram API to make a call, you will only get 33 results back, no matter what you specify in the count paramter. Instafetch will help you fetch more media than the limit imposes, in exchange for more API calls, which can count against your hourly limit.  Demo
کتابخانه instafetch
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
یک نکته‌ی تکمیلی: تعریف متد AddConfig و عدم نیاز به استفاده‌ی از IOptions برای کار با آن
کدهای متد الحاقی AddConfig زیر:
services.AddConfig<MySettings>(Configuration.GetSection("MySettings"));
به این صورت تعریف شده‌است:
public static class ServiceCollectionExtensions
{
    public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration)
        where TSettings : class, new()
    {
        return services.AddConfig<TSettings>(configuration, options => { });
    }

    public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration, Action<BinderOptions> configureOptions)
        where TSettings : class, new()
    {
        if (services == null) { throw new ArgumentNullException(nameof(services)); }
        if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); }
        TSettings setting = configuration.Get<TSettings>(configureOptions);
        services.TryAddSingleton(setting);
        return setting;
    }
}
در اینجا وهله‌ای از کلاس تنظیم خوانده شده را به صورت Singleton در سیستم ثبت می‌کند. بنابراین برای دریافت آن در برنامه، الزامی به استفاده‌ی از اینترفیس IOptions نبوده و می‌توان مستقیما خود کلاس تنظیم را به سازنده‌ی کلاس استفاده کننده‌ی از آن تزریق کرد:
private readonly MySettings _settings;

public MyViewComponent(MySettings settings)
{
    _settings = settings;
}
مزیت آن حذف وابستگی مرتبط با IOptions در قسمت‌های مختلف برنامه است.
نظرات مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
یک نکته‌ی تکمیلی: روش دیگری برای بهبود کنترل نمایش و مخفی سازی قسمت‌های مختلف صفحه

کدهای یک دایرکتیو سفارشی نمایش و یا مخفی سازی قسمت‌های مختلف صفحه را بر اساس سطوح دسترسی کاربر جاری، در IsVisibleForAuthUserDirective مشاهده کردید. روش دیگر انجام اینکار، نوشتن یک دایرکتیو ساختاری شبیه به ngIf توکار خود Angular است. کاری که ngIf انجام می‌دهد، مخفی کردن یک المان در صفحه نیست؛ بلکه کل آن‌را از DOM حذف می‌کند.

نکته‌ی اصلی پیاده سازی یک دایرکتیو ساختاری

اگر به سازنده‌ی IsVisibleForAuthUserDirective دقت کنید، تزریق وابستگی ElementRef را داریم:
@Directive({
  selector: "[isVisibleForAuthUser]"
})
export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy {
  constructor(private elem: ElementRef, private authService: AuthService) { }
به این ترتیب Angular به صورت خودکار امکان دسترسی به المان جاری را با تزریق آن در سازنده‌ی کلاس، میسر می‌کند.
برای ایجاد یک دایرکتیور ساختاری، نیاز است از تزریق TemplateRef و ViewContainerRef استفاده کرد:
@Directive({
selector: "[hasAuthUserViewPermission]"
})
export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {

  constructor(
  private templateRef: TemplateRef<any>,
  private viewContainer: ViewContainerRef,
  private authService: AuthService
) { }
در این حالت Angular تنها زمانی این وابستگی‌ها را به سازنده‌ی کلاس تزریق می‌کند که پیش از نام این دایرکتیو، یک * قرار دهید (مانند ngIf توکار آن):
<div *hasAuthUserViewPermission="['Admin','User']">
    *hasAuthUserViewPermission="['Admin','User']"
</div>
پس از آن می‌توان از viewContainer، برای نمایش المان و یا قالب مدنظر:
 this.viewContainer.createEmbeddedView(this.templateRef);
و یا حذف کامل آن المان از DOM کمک گرفت:
 this.viewContainer.clear();

اگر این نکات را کنار هم قرار دهیم و کدهای IsVisibleForAuthUserDirective فوق را بر این اساس اصلاح کنیم، به قطعه کد زیر می‌رسیم:
import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
import { Subscription } from "rxjs/Subscription";

import { AuthService } from "./../../core/services/auth.service";

@Directive({
  selector: "[hasAuthUserViewPermission]"
})
export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {
  private isVisible = false;
  private requiredRoles: string[] | null = null;
  private subscription: Subscription | null = null;

  @Input()
  set hasAuthUserViewPermission(roles: string[] | null) {
    this.requiredRoles = roles;
  }

  // Note, if you don't place the * in front, you won't be able to inject the TemplateRef<any> or ViewContainerRef into your directive.
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private authService: AuthService
  ) { }

  ngOnInit() {
    this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
    this.changeVisibility(this.authService.isAuthUserLoggedIn());
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  private changeVisibility(status: boolean) {
    const isInRoles = !this.requiredRoles ? true : this.authService.isAuthUserInRoles(this.requiredRoles);
    if (isInRoles && status) {
      if (!this.isVisible) {
        this.viewContainer.createEmbeddedView(this.templateRef);
        this.isVisible = true;
      }
    } else {
      this.isVisible = false;
      this.viewContainer.clear();
    }
  }
}

سپس تعریف آن‌را به قسمت‌های declarations و exports مربوط به SharedModule اضافه می‌کنیم:
import { HasAuthUserViewPermissionDirective } from "./directives/has-auth-user-view-permission.directive";

@NgModule({
  declarations: [
    HasAuthUserViewPermissionDirective
  ],
  exports: [
    HasAuthUserViewPermissionDirective
  ]
})
export class SharedModule {}

اکنون ماژول Dashboard برای استفاده‌ی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
import { SharedModule } from "../shared/shared.module";
@NgModule({
  imports: [
    SharedModule
  ]
})
export class DashboardModule { }

پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشده‌ی به سیستم، فقط کافی است دایرکتیو ساختاری hasAuthUserViewPermission را به المان اعمال کنیم:
<div *hasAuthUserViewPermission="">
    *hasAuthUserViewPermission=""
</div>
و یا اگر نیاز به اعمال نقش‌ها نیز وجود داشت می‌توان به صورت زیر عمل کرد:
<div *hasAuthUserViewPermission="['Admin','User']">
    *hasAuthUserViewPermission="['Admin','User']"
</div>

خلاصه‌ی این تغییرات به کدهای نهایی این سری اعمال شده‌اند.
مطالب
React 16x - قسمت 26 - احراز هویت و اعتبارسنجی کاربران - بخش 1 - ثبت نام و ورود به سیستم
می‌خواهیم به برنامه‌ی لیست فیلم‌هایی که تا این قسمت تکمیل کردیم، امکانات جدیدی را مانند ورود به سیستم، خروج از آن، کار با JWT، فراخوانی منابع محافظت شده‌ی سمت سرور، نمایش و یا مخفی کردن المان‌های صفحه بر اساس سطوح دسترسی کاربر و همچنین محافظت از مسیرهای مختلف تعریف شده‌ی در برنامه، اضافه کنیم.
برای قسمت backend، از همان برنامه‌ی تکمیل شده‌ی قسمت قبل استفاده می‌کنیم که به آن تولید مقدماتی JWTها نیز اضافه شده‌است. البته این سری، مستقل از قسمت سمت سرور آن تهیه خواهد شد و صرفا در حد دریافت توکن از سرور و یا ارسال مشخصات کاربر جهت لاگین، نیاز بیشتری به قسمت سمت سرور آن ندارد و تاکید آن بر روی مباحث سمت کلاینت React است. بنابراین اینکه چگونه این توکن را تولید می‌کنید، در اینجا اهمیتی ندارد و کلیات آن با تمام روش‌های پیاده سازی سمت سرور سازگار است (و مختص به فناوری خاصی نیست). پیشنیاز درک کدهای سمت سرور قسمت JWT آن، مطالب زیر هستند:
  1. «معرفی JSON Web Token»
  2. «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» 
  3. «پیاده سازی JSON Web Token با ASP.NET Web API 2.x»
  4. « آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT»  


ثبت یک کاربر جدید

فرم ثبت نام کاربران را در قسمت 21 این سری، در فایل src\components\registerForm.jsx، ایجاد و تکمیل کردیم. البته این فرم هنوز به backend server متصل نیست. برای کار با آن هم نیاز است شیءای را با ساختار زیر که ذکر سه خاصیت اول آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Users به صورت یک HTTP Post ارسال کنیم:
{
  "name": "string",
  "email": "string",
  "password": "string",
  "isAdmin": true,
  "id": 0
}
در سمت سرور هم در Services\UsersDataSource.cs که در انتهای بحث می‌توانید پروژه‌ی کامل آن‌را دریافت کنید، منحصربفرد بودن ایمیل وارد شده بررسی می‌شود و اگر یک رکورد دو بار ثبت شود، یک BadRequest را به همراه پیام خطایی، بازگشت می‌دهد.

اکنون نوبت به اتصال کامپوننت registerForm.jsx، به سرویس backend است. تا اینجا دو سرویس src\services\genreService.js و src\services\movieService.js را در قسمت قبل، به برنامه جهت کار کردن با endpoint‌های backend server، اضافه کردیم. شبیه به همین روش را برای کار با سرویس سمت سرور api/Users نیز در پیش می‌گیریم. بنابراین فایل جدید src\services\userService.js را با محتوای زیر، به برنامه‌ی frontend اضافه می‌کنیم:
import http from "./httpService";
import { apiUrl } from "../config.json";

const apiEndpoint = apiUrl + "/users";

export function register(user) {
  return http.post(apiEndpoint, {
    email: user.username,
    password: user.password,
    name: user.name
  });
}
توسط متد register این سرویس می‌توانیم شیء user را با سه خاصیت مشخص شده، از طریق HTTP Post، به آدرس api/Users ارسال کنیم. خروجی این متد نیز یک Promise است. در این سرویس، تمام متدهایی که قرار است با این endpoint سمت سرور کار کنند، مانند ثبت، حذف، دریافت اطلاعات و غیره، تعریف خواهند شد.
اطلاعات شیء user ای که در اینجا دریافت می‌شود، از خاصیت data کامپوننت RegisterForm تامین می‌گردد:
class RegisterForm extends Form {
  state = {
    data: { username: "", password: "", name: "" },
    errors: {}
  };
البته اگر دقت کرده باشید، در شیء منتسب به خاصیت data، خاصیتی به نام username تعریف شده‌است، اما در سمت سرور، نیاز است خاصیتی با نام Name را دریافت کنیم. یک چنین نگاشتی در داخل متد register سرویس کاربر، قابل مشاهده‌‌است. در غیراینصورت می‌شد در متد http.post، کل شیء user را به عنوان پارامتر دوم، درنظر گرفت و ارسال کرد.

پس از تعریف userService.js، به registerForm.jsx بازگشته و ابتدا امکانات آن‌را import می‌کنیم:
import * as userService from "../services/userService";
می‌شد این سطر را به صورت زیر نیز نوشت، تا تنها یک متد از ماژول userService را دریافت کنیم:
import { register } userService from "../services/userService";
اما روش as userService * به معنای import تمام متدهای این ماژول است. به این ترتیب نام ذکر شده‌ی پس از as، به عنوان شیءای که می‌توان توسط آن به این متدها دسترسی یافت، قابل استفاده می‌شود؛ مانند فراخوانی متد userService.register. اکنون می‌توان متد doSubmit این فرم را به سرور متصل کرد:
  doSubmit = async () => {
    try {
      await userService.register(this.state.data);
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors }; // clone an object
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };


مدیریت و نمایش خطاهای دریافتی از سمت سرور

در این حالت برای ارسال اطلاعات یک کاربر، در بار اول، یک چنین خروجی را از سمت سرور می‌توان شاهد بود که id جدیدی را به این رکورد نسبت داده‌است:


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


که از نوع 400 یا همان BadRequest است:


بنابراین نیاز است بدنه‌ی response را در یک چنین مواردی که خطایی از سمت سرور صادر می‌شود، دریافت کرده و با به روز رسانی خاصیت errors در state فرم (همان قسمت بدنه‌ی catch کدهای فوق)، سبب درج و نمایش خودکار این خطا شویم:


پیشتر در قسمت بررسی «کار با فرم‌ها» آموختیم که برای مدیریت خطاهای پیش بینی شده‌ی دریافتی از سمت سرور، نیاز است قطعه کدهای مرتبط با سرویس http را در بدنه‌ی try/catch‌ها محصور کنیم. برای مثال در اینجا اگر ایمیل شخصی تکراری وارد شود، سرویس یک return BadRequest("Can't create the requested record.") را بازگشت می‌دهد که در اینجا status code معادل BadRequest، همان 400 است. بنابراین انتظار داریم که خطای 400 را از سمت سرور، تحت شرایط خاصی دریافت کنیم. به همین دلیل است که در اینجا تنها مدیریت status code=400 را در بدنه‌ی catch نوشته شده ملاحظه می‌کنید.
سپس برای نمایش آن، نیاز است خاصیت متناظری را که این خطا به آن مرتبط می‌شود، با پیام دریافت شده‌ی از سمت سرور، مقدار دهی کنیم که در اینجا می‌دانیم مرتبط با username است. به همین جهت سطر errors.username = ex.response.data، کار انتساب بدنه‌ی response را به خاصیت جدید errors.username انجام می‌دهد. در این حالت اگر به کمک متد setState، کار به روز رسانی خاصیت errors موجود در state را انجام دهیم، رندر مجدد فرم، در صف انجام قرار گرفته و در رندر بعدی آن، پیام موجود در errors.username، نمایش داده می‌شود.


پیاده سازی ورود به سیستم

فرم ورود به سیستم را در قسمت 18 این سری، در فایل src\components\loginForm.jsx، ایجاد و تکمیل کردیم که این فرم نیز هنوز به backend server متصل نیست. برای کار با آن نیاز است شیءای را با ساختار زیر که ذکر هر دو خاصیت آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Auth/Login به صورت یک HTTP Post ارسال کنیم:
{
  "email": "string",
  "password": "string"
}
با ارسال این اطلاعات به سمت سرور، درخواست Login انجام می‌شود. سرور نیز در صورت تعیین اعتبار موفقیت آمیز کاربر، به صورت زیر، یک JSON Web token را بازگشت می‌دهد:
var jwt = _tokenFactoryService.CreateAccessToken(user);
return Ok(new { access_token = jwt });
یعنی بدنه‌ی response رسیده‌ی از سمت سرور، دارای یک شیء JSON خواهد بود که خاصیت access_token آن، حاوی JSON Web token متعلق به کاربر جاری لاگین شده‌است. در آینده اگر این کاربر نیاز به دسترسی به یک api endpoint محافظت شده‌ای را در سمت سرور داشته باشد، باید این token را نیز به همراه درخواست خود ارسال کند تا پس از تعیین اعتبار آن توسط سرور، مجوز دسترسی به منبع درخواستی برای او صادر شود.

در ادامه برای تعامل با منبع api/Auth/Login سمت سرور، ابتدا یک سرویس مختص آن‌را در فایل جدید src\services\authService.js، با محتوای زیر ایجاد می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/auth";

export function login(email, password) {
  return http.post(apiEndpoint + "/login", { email, password });
}
متد login، کار ارسال ایمیل و کلمه‌ی عبور کاربر را به اکشن متد Login کنترلر Auth، انجام می‌دهد و خروجی آن یک Promise است. برای استفاده‌ی از آن به کامپوننت src\components\loginForm.jsx بازگشته و متد doSubmit آن‌را به صورت زیر تکمیل می‌کنیم:
import * as auth from "../services/authService";

class LoginForm extends Form {
  state = {
    data: { username: "", password: "" },
    errors: {}
  };

  // ...

  doSubmit = async () => {
    try {
      const { data } = this.state;
      const {
        data: { access_token }
      } = await auth.login(data.username, data.password);
      console.log("JWT", access_token);
      localStorage.setItem("token", access_token);
      this.props.history.push("/");
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors };
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };
توضیحات:
- ابتدا تمام خروجی‌های ماژول authService را با نام شیء auth دریافت کرده‌ایم.
- سپس در متد doSubmit، اطلاعات خاصیت data موجود در state را که معادل فیلدهای فرم لاگین هستند، به متد auth.login برای انجام لاگین سمت سرور، ارسال کرده‌ایم. این متد چون یک Promise را باز می‌گرداند، باید await شود و پس از آن متد جاری نیز باید به صورت async معرفی گردد.
- همانطور که عنوان شد، خروجی نهایی متد auth.login، یک شیء JSON دارای خاصیت access_token است که در اینجا از خاصیت data خروجی نهایی دریافت شده‌است.
- سپس نیاز است برای استفاده‌های آتی، این token دریافتی از سرور را در جایی ذخیره کرد. یکی از مکان‌های متداول اینکار، local storage مرورگرها است (اطلاعات بیشتر).
- در آخر کاربر را توسط شیء history سیستم مسیریابی برنامه، به صفحه‌ی اصلی آن هدایت می‌کنیم.
- در اینجا قسمت catch نیز ذکر شده‌است تا خطاهای حاصل از return BadRequestهای دریافتی از سمت سرور را بتوان ذیل فیلد نام کاربری نمایش داد. روش کار آن، دقیقا همانند روشی است که برای فرم ثبت یک کاربر جدید استفاده کردیم.

اکنون اگر برنامه را ذخیره کرده و اجرا کنیم، توکن دریافتی، در کنسول توسعه دهندگان مرورگر لاگ شده و سپس کاربر به صفحه‌ی اصلی برنامه هدایت می‌شود. همچنین این token ذخیره شده را می‌توان در ذیل قسمت application->storage آن نیز مشاهده کرد:



لاگین خودکار کاربر، پس از ثبت نام در سایت

پس از ثبت نام یک کاربر در سایت، بدنه‌ی response بازگشت داده شده‌ی از سمت سرور، همان شیء user است که اکنون Id او مشخص شده‌است. بنابراین اینبار جهت ارائه‌ی token از سمت سرور، بجای response body، از یک هدر سفارشی در فایل Controllers\UsersController.cs استفاده می‌کنیم (کدهای کامل آن در انتهای بحث پیوست شده‌است):
var jwt = _tokenFactoryService.CreateAccessToken(user);
this.Response.Headers.Add("x-auth-token", jwt);



در ادامه در کدهای سمت کلاینت src\components\registerForm.jsx، برای استخراج این هدر سفارشی، اگر شیء response دریافتی از سرور را لاگ کنیم:
const response = await userService.register(this.state.data);
console.log(response);
یک چنین خروجی را به همراه دارد که در آن، هدر سفارشی ما درج نشده‌است و فقط هدر content-type در آن مشخص است:


برای اینکه در کدهای سمت کلاینت بتوان این هدر سفارشی را خواند، نیاز است هدر مخصوص access-control-expose-headers را نیز به response اضافه کرد:
var jwt = _tokenFactoryService.CreateAccessToken(data);
this.Response.Headers.Add("x-auth-token", jwt);
this.Response.Headers.Add("access-control-expose-headers", "x-auth-token");
به این ترتیب وب سرور برنامه، هدر سفارشی را که قرار است برنامه‌ی کلاینت به آن دسترسی پیدا کند، مجاز اعلام می‌کند. اینبار اگر خروجی دریافتی از Axios را لاگ کنیم، در لیست هدرهای آن، هدر سفارشی x-auth-token نیز ظاهر می‌شود:


اکنون می‌توان این هدر سفارشی را در متد doSubmit کامپوننت RegisterForm، از طریق شیء response.headers خواند و در localStorage ذخیره کرد. سپس کاربر را توسط شیء history سیستم مسیریابی، به ریشه‌ی سایت هدایت نمود:
class RegisterForm extends Form {
  // ...

  doSubmit = async () => {
    try {
      const response = await userService.register(this.state.data);
      console.log(response);
      localStorage.setItem("token", response.headers["x-auth-token"]);
      this.props.history.push("/");
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors }; // clone an object
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-26-backend.zip و sample-26-frontend.zip
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت چهارم - انجام اعمال async با Redux
در قسمت اول این سری، با مفاهیم توابع خالص و ناخالص آشنا شدیم و همچنین عنوان شد که هرگونه تعامل با یک API خارجی به عنوان یک اثر جانبی و یا side effect در نظر گرفته شده و توابع را ناخالص می‌کند. به علاوه Redux تنها امکان کار با توابع خالص قابل پیش بینی را دارد و همچنین dispatch تمام اکشن‌ها توسط آن، به صورت پیش‌فرض synchronous است و نه asynchronous. اما در برنامه‌های واقعی نیاز است بتوان با یک API خارجی کارکرد و اطلاعاتی را از آن دریافت نمود و یا به آن ارسال کرد. برای رفع این مشکل، یک کتابخانه‌ی کمکی به نام redux-thunk ایجاد شده‌است که جزئیات کار با آن‌را در این قسمت بررسی می‌کنیم.


معرفی کتابخانه‌ی Redux Thunk

thunk، تابعی است که خروجی تابعی دیگر است؛ مانند مثال زیر:
function definitelyNotAThunk() {
   return function aThunk() {
     console.log('Hello, I am a thunk.');
   }
}
هدف اصلی از انجام یک چنین کاری، فراهم آوردن روشی برای اجرای به تاخیر افتاده‌است. برای مثال زمانیکه برای اجرای آن می‌نویسیم ()definitelyNotAThunk، تابع درونی آن هنوز اجرا نشده‌است. اجرای کامل آن چنین شکلی را دارد: ()()definitelyNotAThunk. حالا فرض کنید میان‌افزاری پیش از اجرای reducer قرار گرفته‌است که به تمام اشیاء رسیده‌ی به آن (یا همان اکشن‌ها در اینجا) گوش فرا می‌دهد. اگر اکشنی بجای یک شیء، یک تابع را بازگرداند، این میان‌افزار، آن‌را اجرا می‌کند یا همان ()() که عنوان شد. این کل کاری است که میان‌افزار 14 سطری redux-thunk انجام می‌دهد. زمانیکه از این میان‌افزار استفاده می‌شود، تابع درونی، دو پارامتر dispatch و getState را دریافت می‌کند. پارامتر dispatch که در حقیقت یک متد است، امکان اجرای اعمال synchronous و ارسال آن‌ها را به سمت reducer متناظر، میسر می‌کند.
برای مثال فرض کنید که نیاز است یک فراخوانی Ajax ای صورت گیرد و پس از پایان آن، جهت به روز رسانی state، یک شیء اکشن، به سمت reducer متناظری dispatch شود. مشکل اینجا است که نمی‌توان به Redux، یک callback حاصل از دریافت نتیجه‌ی عملیات Ajax ای و یا یک Promise را ارسال کرد. تمام این‌ها یک اثر جانبی یا side effect هستند که با توابع خالص Redux ای سازگاری ندارند. برای مدیریت یک چنین مواردی، یک میان‌افزار را به نام redux-thunk ایجاد کرده‌اند که اجازه‌ی dispatch تابعی را می‌دهد (همان thunk در اینجا) که قرار است action اصلی را در زمانی دیگر dispatch کند. به این ترتیب Redux اطلاعاتی را در مورد یک عمل async نخواهد داشت؛ میان‌افزاری در این بین آن‌را دریافت می‌کند و زمانیکه آن‌را dispatch می‌کنیم، آنگاه اکشن متناظر با آن، به redux منتقل می‌شود. به این ترتیب امکان منتظر ماندن تا زمان رسیدن پاسخ از شبکه، میسر می‌شود.
فرض کنید یک action creator متداول به صورت زیر ایجاد شده‌است:
export const getAllItems = () => ({
   type: UPDATE_ALL_ITEMS,
   items,
});
اکنون این سؤال مطرح می‌شود که چگونه می‌توان متوجه شد، پاسخی از سمت API دریافت شده‌است؟
برای پاسخ به این سؤال، اینبار action creator فوق را بر اساس الگوی redux-thunk به صورت زیر بازنویسی می‌کنیم:
export const getAllItems = () => {
  return dispatch => {
    Api.getAll().then(items => {
      dispatch({
        type: UPDATE_ALL_ITEMS,
        items,
      });
    });
  };
};
اینبار action creator ای را داریم که بجای بازگشت یک شیء، یک تابع را بازگشت داده‌است که به آن thunk گفته می‌شود و پارامتر dispatch را دریافت می‌کند. در این حالت زمانیکه یک Promise بازگشت داده می‌شود (امکان منتظر نتیجه شدن را فراهم می‌کند)، کار dispatch اکشن اصلی مدنظر (برای مثال ارسال آرایه‌ای از اشیاء)، صورت می‌گیرد.


برپایی پیش‌نیازها

در اینجا برای افزودن کامپوننتی که اطلاعات خودش را از یک API خارجی تامین می‌کند، از همان برنامه‌ی به همراه کامپوننت شمارشگر که در قسمت قبل آن‌را تکمیل کردیم، استفاده می‌کنیم. فقط در آن کتابخانه‌ها‌ی Axios و همچنین redux thunk را نیز نصب می‌کنید. به همین جهت در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios redux-thunk
برنامه‌ی backend مورد استفاده هم همان برنامه‌ای است که از قسمت 22 شروع به توسعه‌ی آن کردیم و کدهای کامل آن‌را از پیوست‌های انتهای بحث، می‌توانید دریافت کنید. این برنامه که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است. برای راه اندازی آن، به پوشه‌ی این برنامه، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts آن در مرورگر دسترسی یافت.

پس از نصب پیش‌نیازها و راه اندازی برنامه‌ی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api، ایجاد می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
در ادامه می‌خواهیم در برنامه‌ی React خود، لیست مطالب برنامه‌ی backend را از سرور دریافت کرده و نمایش دهیم.


افزودن میان‌افزار redux-thunk به برنامه

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


چون نیاز به عملیات async وجود دارد، باید از میان‌افزار مخصوص thunk برای انجام آن استفاده کرد. برای این منظور به فایل src\index.js مراجعه کرده و میان‌افزار thunk را توسط تابع applyMiddleware، به متد createStore، معرفی می‌کنیم:
import { applyMiddleware, compose, createStore } from "redux";
import thunk from "redux-thunk";

//...

const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

//...
در اینجا چون نیاز است چندین تابع را به متد createStore ارسال کرد، باید از متد compose برای اعمال دسته جمعی آن‌ها کمک گرفت.


دریافت اطلاعات از یک API خارجی به کمک redux-thunk

پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux در قسمت قبل، پیاده سازی دریافت اطلاعات را بر اساس همان الگو، پیاده سازی می‌کنیم:
1)  ایجاد نام نوع اکشن متناظر با دکمه‌ی افزودن مقدار
به فایل src\constants\ActionTypes.js، نوع جدید دریافت مطالب را اضافه می‌کنیم:
export const GetPostsSuccess = "GetPostsSuccess";
export const GetPostsStarted = "GetPostsStarted";
export const GetPostsFailure = "GetPostsFailure";
در حین دریافت اطلاعات از API، حداقل سه اکشن نمایش loading (و یا GetPostsStarted در اینجا)، نمایش نهایی اطلاعات دریافت شده‌ی از سرور (و یا GetPostsSuccess در اینجا) و یا نمایش خطاهای حاصل (با نوع GetPostsFailure در اینجا) باید مدنظر باشند. به همین جهت سه نوع مختلف در اینجا تعریف شده‌اند.

2) ایجاد متد Action Creator
در فایل src\actions\index.js، متد ایجاد کننده‌ی شیء اکشن ارسالی به reducer متناظری را تعریف می‌کنیم تا بتوان بر اساس نوع آن در reducer دریافت اطلاعات، منطق نهایی را پیاده سازی کرد:
import axios from "axios";

import { apiUrl } from "../config.json";
import * as types from "../constants/ActionTypes";

export const fetchPosts = () => {
  return (dispatch, getState) => {
    dispatch(getPostsStarted());
    axios.get(apiUrl + "/posts").then(response => {
      dispatch(getPostsSuccess(response.data)).catch(err => {
        dispatch(getPostsFailure(err));
      });
    });
  };
};

export const fetchPostsAsync = () => {
  return async (dispatch, getState) => {
    dispatch(getPostsStarted());
    try {
      const { data } = await axios.get(apiUrl + "/posts");
      console.log(data);
      dispatch(getPostsSuccess(data));
    } catch (error) {
      dispatch(getPostsFailure(error));
    }
  };
};

const getPostsSuccess = posts => ({
  type: types.GetPostsSuccess,
  payload: { posts }
});

const getPostsStarted = () => ({
  type: types.GetPostsStarted
});

const getPostsFailure = error => ({
  type: types.GetPostsFailure,
  payload: {
    error
  }
});
- در اینجا همان الگوی بازگشت یک تابع را بجای یک شیء، در توابع action creator، مشاهده می‌کنید.
- تابع fetchPosts، از همان روش قدیمی callback، برای مدیریت اطلاعات دریافتی از سرور استفاده می‌کند. زمانیکه اطلاعاتی دریافت شد، آن‌را با فراخوانی dispatch و با قالبی که تابع getPostsSuccess ارائه می‌دهد، به reducer متناظر، ارسال می‌کند.
- تابع fetchPostsAsync، نمونه‌ی به همراه async/await کار با کتابخانه‌ی axios است. هر دو روش callback و یا async/await در اینجا پشتیبانی می‌شوند.

به صورت پیش‌فرض action creators کتابخانه‌ی redux از اعمال async پشتیبانی نمی‌کنند. برای رفع این مشکل پس از ثبت میان‌افزار thunk، اینبار متدهای action creator، بجای بازگشت یک شیء، یک تابع را بازگشت می‌دهند که این تابع درونی در زمانی دیگر توسط میان‌افزار thunk و پیش از رسیدن به reducer، فراخوانی خواهد شد. این تابع درونی، دو پارامتر dispatch و  getState را دریافت می‌کند. هر دوی این‌ها نیز متد هستند. برای مثال اگر نیاز به دریافت وضعیت فعلی state در اینجا وجود داشت، می‌توان متد ()getState رسیده را فراخوانی کرد و حاصل آن‌را بررسی نمود. برای مثال شاید تصمیم گرفته شود که بر اساس وضعیت فعلی state، نیازی نیست تا اطلاعاتی از سرور دریافت شود و بهتر است همان اطلاعات کش شده‌ی موجود در state را بازگشت دهیم. البته در این مثال فقط از متد dispatch ارسالی، برای بازگشت نتیجه‌ی نهایی به reducer متناظر، استفاده شده‌است.

- در نهایت آرایه‌ی اشیاء مطلب دریافتی از سرور، به عنوان مقدار خاصیت posts شیء منتسب به خاصیت payload شیء ارسالی به reducer، در متد getPostsSuccess تعریف شده‌است. یعنی reducer متناظر، اطلاعات را از طریق خاصیت action.payload.posts شیء رسیده، دریافت می‌کند.
- همچنین دو اکشن شروع به دریافت اطلاعات (getPostsStarted) و بروز خطا (getPostsFailure) نیز در ابتدا و در قسمت catch عملیات async، به سمت reducer متناظر، dispatch خواهند شد.

3) ایجاد تابع reducer مخصوص دریافت اطلاعات از سرور
اکنون در فایل جدید src\reducers\posts.js، بر اساس نوع شیء رسیده و مقدار action.payload.posts آن، کار تامین آرایه‌ی posts موجود در state انجام می‌شود:
import * as types from "../constants/ActionTypes";

const initialState = { loading: false, posts: [], error: null };

export default function postsReducer(state = initialState, action) {
  switch (action.type) {
    case types.GetPostsStarted:
      return {
        loading: true,
        posts: [],
        error: null
      };
    case types.GetPostsSuccess:
      return {
        loading: false,
        posts: action.payload.posts,
        error: null
      };
    case types.GetPostsFailure:
      return {
        loading: false,
        posts: [],
        error: action.payload.error
      };
    default:
      return state;
  }
}
در این reducer با استفاده از یک switch، سه حالت ممکنی را که اکشن‌های رسیده‌ی به آن می‌توانند داشته باشند، مدیریت کرده‌ایم:
- در حالت آغاز کار و یا GetPostsStarted، با تنظیم خاصیت loading به true، سبب نمایش یک div «لطفا منتظر بمانید» خواهیم شد.
- در حالت دریافت نهایی اطلاعات از سرور، خاصیت loading به false تنظیم می‌شود تا div «لطفا منتظر بمانید» را مخفی کند. همچنین آرایه‌ی posts را نیز از payload رسیده استخراج کرده و به سمت کامپوننت‌ها ارسال می‌کند.
- در حالت بروز خطا و یا GetPostsFailure، خاصیت error شیء action.payload استخراج شده و جهت نمایش div متناظری، بازگشت داده می‌شود.

پس از تعریف این reducer باید آن‌را در فایل src\reducers\index.js به کمک combineReducers، با سایر reducer‌های موجود، ترکیب و یکی کرد تا در نهایت این rootReducer در فایل index.js اصلی برنامه، جهت ایجاد store اصلی redux، مورد استفاده قرار گیرد:
import { combineReducers } from "redux";

import counterReducer from "./counter";
import postsReducer from "./posts";

const rootReducer = combineReducers({
  counterReducer,
  postsReducer
});

export default rootReducer;


تشکیل کامپوننت‌های دکمه‌ی دریافت اطلاعات و نمایش لیست مطالب

UI این قسمت از سه کامپوننت تشکیل شده‌است که کدهای کامل آن‌ها را در ادامه مشاهده می‌کنید:

الف) کامپوننت src\components\FetchPosts.jsx
import React from "react";

const FetchPosts = ({ fetchPostsAsync }) => {
  return (
    <section className="card mt-5">
      <div className="card-header text-center">
        <button className="btn btn-primary" onClick={fetchPostsAsync}>
          Fetch Posts
        </button>
      </div>
    </section>
  );
};

export default FetchPosts;
کار این کامپوننت، نمایش دکمه‌ی Fetch Posts است. با کلیک بر روی آن قرار است action creator ای به نام fetchPostsAsync اجرا شود که کدهای آن‌را پیشتر مرور کردیم.
همانطور که مشاهده می‌کنید، این کامپوننت هیچ اطلاعاتی از وجود کامپوننت دومی که قرار است لیست مطالب را نمایش دهد، ندارد. کارش تنها dispatch یک اکشن است.
بنابراین این کامپوننت از طریق props فقط یک اشاره‌گر به متد رویدادگردانی را دریافت می‌کند و اطلاعات دیگری را نیاز ندارد.

ب) کامپوننت src\components\Posts.jsx
import React from "react";

import Post from "./Post";

const Posts = ({ posts, loading, error }) => {
  return (
    <>
      <section className="card mt-5">
        <div className="card-header">
          <h2>Posts</h2>
        </div>
        <div className="card-body">
          {loading ? (
            <div className="alert alert-info">Loading ...</div>
          ) : (
            <div className="list-group list-group-flush">
              {posts.map(post => (
                <Post key={post.id} post={post} />
              ))}
            </div>
          )}
          {error && <div className="alert alert-warning">{error.message}</div>}
        </div>
      </section>
    </>
  );
};

export default Posts;
این کامپوننت، آرایه‌ای از اشیاء مطالب را دریافت کرده و با  ایجاد حلقه‌ای بر روی آن‌ها، توسط کامپوننت Post، هر کدام را در صفحه درج می‌کند. بنابراین این کامپوننت اکشنی را dispatch نمی‌کند. فقط از طریق props، یک آرایه‌ی posts، وضعیت جاری عملیات و خطاهای حاصل را باید دریافت کند.
در این کامپوننت اگر loading رسیده به true تنظیم شده باشد، یک div با عبارت loading نمایش داده می‌شود. در غیراینصورت، لیست مطالب را درج می‌کند. همچنین اگر خطایی نیز رخ داده باشد، آن‌را نیز درون یک div در صفحه نمایش می‌دهد.

ج) کامپوننت src\components\Post.jsx
import React from "react";

const Post = ({ post }) => {
  return (
    <article className="list-group-item">
      <header>
        <h2>{post.title}</h2>
      </header>
      <p>{post.body}</p>
    </article>
  );
};

export default Post;
این کامپوننت کار نمایش جزئیات هر رکورد مطلب را به عهده دارد؛ مانند نمایش عنوان و بدنه‌ی یک مطلب.


اتصال کامپوننت‌های FetchPosts و Posts به مخزن redux

مرحله‌ی آخر کار، تامین state کامپوننت‌های FetchPosts و Posts از طریق props است. به همین جهت باید دو دربرگیرنده را برای این دو کامپوننت ایجاد کنیم.
الف) ایجاد دربرگیرنده‌ی کامپوننت FetchPosts
برای این منظور فایل جدید src\containers\FetchPosts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import { fetchPostsAsync } from "../actions";
import FetchPosts from "../components/FetchPosts";

const mapDispatchToProps = {
  fetchPostsAsync
};

export default connect(null, mapDispatchToProps)(FetchPosts);
- کار این تامین کننده، اتصال action creator ای به نام fetchPostsAsync به props کامپوننت FetchPosts است.
- چون اطلاعات state ای قرار نیست به این کامپوننت ارسال شود، تابع mapStateToProps را در اینجا مشاهده نمی‌کنید و با نال مقدار دهی شده‌است.

ب) ایجاد دربرگیرنده‌ی کامپوننت Posts
برای این منظور فایل جدید src\containers\Posts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import Posts from "../components/Posts";

const mapStateToProps = state => {
  console.log("PostsContainer->mapStateToProps", state);
  return {
    ...state.postsReducer
  };
};

export default connect(mapStateToProps)(Posts);
- کار این تامین کننده، اتصال خاصیت posts بازگشت داده شده‌ی از طریق postsReducer، به props کامپوننت Posts است. البته چون کامپوننت Posts سه خاصیت { posts, loading, error } را از طریق props دریافت می‌کند، با استفاده از spread operator، هر سه خاصیت دریافتی از reducer را به صورت یک شیء جدید بازگشت داده‌ایم.
- کامپوننت Posts رویدادی را سبب نخواهد شد. به همین جهت تابع mapDispatchToProps را در اینجا تعریف و ذکر نکرده‌ایم.


استفاده از کامپوننت‌های دربرگیرنده جهت نمایش نهایی کامپوننت‌های تحت کنترل Redux

اکنون به فایل src\App.js مراجعه کرده و دو تامین کننده‌ی فوق را درج می‌کنیم:
import "./App.css";

import React from "react";

import CounterContainer from "./containers/Counter";
import FetchPostsContainer from "./containers/FetchPosts";
import PostsContainer from "./containers/Posts";

function App() {
  const prop1 = 123;
  return (
    <main className="container">
      <div className="row">
        <div className="col">
          <CounterContainer prop1={prop1} />
        </div>
        <div className="col">
          <FetchPostsContainer />
        </div>
        <div className="col">
          <PostsContainer />
        </div>
      </div>
    </main>
  );
}

export default App;
در اینجا FetchPostsContainer و PostsContainer سبب خواهند شد تا اتصالات مخزن اصلی redux، به کامپوننت‌هایی که توسط آن‌ها دربرگرفته شده‌اند، برقرار شود و کار تامین props آن‌ها صورت گیرد.

یک نکته: برای مثال در انتهای کامپوننت FetchPosts، سطر export default FetchPosts را داریم. اگر این سطر را حذف کنیم و بجای آن export default connect فوق را قرار دهیم، دیگر نیازی نخواهد بود تا FetchPostsContainer را از دربرگیرنده‌ها، import کرد و سپس بجای درج المان </FetchPosts> نوشت </FetchPostsContainer>. می‌توان همانند قبل از همان نام متداول </FetchPosts> استفاده کرد و import انجام شده نیز همانند سابق از همان فایل ماژول کامپوننت صورت می‌گیرد. یعنی می‌توان پوشه‌ی containers را حذف کرد و کدهای آن را دقیقا ذیل کلاس کامپوننت درج نمود.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-redux-mobx-part04-backend.zip و state-management-redux-mobx-part04-frontend.zip
اشتراک‌ها
ReScript زبان پس از TypeScript؟

In the confusing jungle of transpiler languages for JavaScript, there are some gems. TypeScript is mainstream, ReScript is starting to establish itself, and Elm is still an insider tip. This article takes a detailed look at ReScript – but also sheds light on the limitations of the young language. In what projects does its use make sense? What projects should rather use TypeScript on the one hand or Elm on the other? 

ReScript زبان پس از TypeScript؟
مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
پس از بررسی نحوه‌ی انجام تنظیمات اولیه‌ی کار با EF Core و همچنین آشنایی با مهاجرت‌های آن، مرحله‌ی بعد، مرحله‌ی مدلسازی داده‌ها است و اولین مرحله‌ی آن، نحوه‌ی تعیین کلید اصلی جداول است که در این زمینه، EF Core پیشرفت‌هایی قابل ملاحظه‌ای را نسبت به EF 6.x داشته‌است. در EF 6.x تنها دو حالت کلیدهای اصلی خود افزاینده که توسط بانک اطلاعاتی مدیریت می‌شوند و یا تولید کلید اصلی در سمت کلاینت و توسط برنامه، پشتیبانی می‌شوند. در EF Core، مواردی مانند Sequence و Alternate keys نیز اضافه شده‌اند.


پیش فرض‌های تعیین کلید اصلی در EF Core

به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
public class Car
{
    public string Id { get; set; }
و یا
public class Car
{
   public string CarId { get; set; }
در مثال اول، نام خاصیت، Id است و در مثال دوم، جمع نام کلاس به همراه Id ذکر شده‌است. یک چنین مواردی، نیازی به تنظیم اضافه‌تری ندارند.


نحوه‌ی تعیین کلید اصلی به صورت صریح

اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
الف) از طریق ویژگی‌ها
public class Car
{
   [Key]
   public string LicensePlate { get; set; }
در اینجا چون LicensePlate نه Id نام دارد و نه جمع نام کلاس به همراه Id است، باید به نحو صریحی توسط ویژگی Key مشخص شود.
ب) با استفاده از روش Fluent API
public class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Car>()
                 .HasKey(c => c.LicensePlate);
    }
 }
روش تنظیم کلید اصلی به صورت صریح، از طریق کدنویسی است که به آن Fluent API یا API روان هم گفته می‌شود. برای اینکار باید متد OnModelCreating کلاس Context برنامه را بازنویسی کرد و سپس از طریق متد HasKey، نام خاصیت کلید اصلی را ذکر نمود.


پیشنیاز کار با ویژگی‌ها در EF Core

در اسمبلی که مدل‌های موجودیت‌ها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگی‌هایی مانند Key، شناسایی و قابل استفاده شوند:
{
   "dependencies": {
          "System.ComponentModel.Annotations": "4.1.0"
   }
}


تعیین کلید ترکیبی و یا Composite key

اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم می‌گویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
                       .HasKey(c => new { c.State, c.LicensePlate });
}
در قسمت HasKey می‌توان چندین خاصیت را نیز جهت تعیین کلید ترکیبی مشخص کرد.


روش‌های مختلف تولید خودکار مقادیر خواص

حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزاینده‌ای است که توسط بانک اطلاعاتی کنترل می‌شود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی می‌شود (با استفاده از الگوریتم Guid سری در SQL Server).
 اگر این حالات مطلوب شما نیست، حالت‌های سه گانه‌ی ذیل را می‌توان استفاده کرد:

الف) هیچ داده‌ی خودکاری تولید نشود
برای اینکار می‌توان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.BlogId)
           .ValueGeneratedNever();
}

ب) تولید داده‌های خودکار فقط در حالت Add
حالت Add به این معنا است که داده‌های خواص مشخصی، برای موجودیت‌های «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید می‌شوند یا خیر، صرفا وابسته‌است به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوع‌های Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام می‌دهد.
این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند و الزاما به کلید اصلی اشاره نمی‌کنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شده‌است. به این معنا که در حین ثبت اولیه‌ی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام می‌دهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید می‌کند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگی‌ها میسر نیست و فقط fluent API از آن پشتیبانی می‌کند.
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }

   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public DateTime Inserted { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
           .Property(b => b.Inserted)
           .ValueGeneratedOnAdd();
}
برای تعیین مقدار پیش فرض خاصیت Inserted به نحوی که توسط SQL Server به صورت خودکار مقدار دهی شود، می‌توان از متد HasDefaultValueSql به نحو ذیل استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        .HasDefaultValueSql("getdate()");
}
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس Blog درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? Inserted).

یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آن‌ها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشته‌ها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای این‌ها به فیلدهای فوق انتساب داده شوند، از آن‌ها استفاده می‌شود.

ج) تولید داده‌های خودکار در هر دو حالت Add و Update
تولید داده‌ها در حالت‌های Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API می‌توان استفاده کرد:
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}
و یا معادل این تنظیم با استفاده از Fluent API به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
       .Property(b => b.LastUpdated)
       .ValueGeneratedOnAddOrUpdate();
}
همانطور که پیشتر نیز عنوان شد، تولید خودکار مقادیر فیلدها فقط در حالت‌های int و Guid انجام می‌شود (که برای مثال SQL Server از آن‌ها پشتیبانی می‌کند). در مثال فوق، خاصیت LastUpdated از نوع DateTime اینگونه تعریف شده‌است و SQL Server برای یک چنین فیلدهای خاصی، مقدار خودکاری را تولید نکرده و به دنبال مقدار پیش فرض آن می‌گردد. بنابراین در اینجا نیز باید مشخص سازی HasDefaultValueSql("getdate()") را که در قسمت قبل عنوان کردیم، صراحتا در قسمت تنظیمات Fluent API ذکر و تنظیم کرد.

تذکر: در اینجا نیز همانند حالت ValueGeneratedOnAdd، اگر این خواص مشخص شده، دارای مقدار متفاوتی با مقدار پیش فرض آن‌ها باشند، از این مقادیر جدید بجای تولید خودکار مقادیر استفاده خواهد شد.


خواص محاسباتی (Computed Columns) و تفاوت آن‌ها با DatabaseGeneratedOption.Computed

خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آن‌ها در بانک اطلاعاتی محاسبه می‌شوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شده‌ی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزاینده‌ی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی می‌شوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم می‌باشند:
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
          modelBuilder.Entity<Person>()
              .Property(p => p.DisplayName)
               .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
     }
 }
در اینجا فیلد DisplayName یک فیلد محاسباتی بوده و از حاصل جمع دو فیلد دیگر در سمت دیتابیس تشکیل می‌شود. این نگاشت و محاسبه چون در سمت بانک اطلاعاتی انجام می‌شود، بازدهی بیشتری دارد نسبت به حالتی که ابتدا دو فیلد به کلاینت منتقل شده و سپس در این سمت جمع زده شوند.


امکان تعریف Sequence در EF Core 1.0

Sequence قابلیتی است که به SQL Server 2012 اضافه شده‌است و توضیحات بیشتر آن‌را در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» می‌توانید مطالعه کنید.
در EF Core، امکان مدلسازی Sequence نیز پیش بینی شده‌است. آن‌ها به صورت پیش فرض در مدل‌ها ذکر نمی‌شوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آن‌ها صرفا توسط Fluent API وجود دارد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") 
           .StartsAt(1000).IncrementsBy(5);

     modelBuilder.Entity<Order>()
         .Property(o => o.OrderNo)
         .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}
پس از اینکه یک Sequence  تعریف شد، می‌توان برای نمونه از آن جهت تولید مقادیر پیش فرض ستون‌ها استفاده کرد.
در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شده‌است که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شده‌است.

و یا از Sequence ‌ها می‌توان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence");
    modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
     {
       entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
     });
}
در اینجا یک توالی از نوع int تعریف شده و سپس هربار که قرار است رکوردی درج شود، مقدار id آن به صورت خودکار از طریق کوئری Select NEXT VALUE FOR
[PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج می‌شود.

به این روش الگوریتم Hi-Low هم می‌گویند که یکی از مهم‌ترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام می‌شود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده می‌شود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازه‌ای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا می‌برد.


نحوه‌ی تعریف ایندکس‌ها در EF Core 1.0

برای افزودن ایندکس‌ها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمی‌کند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضح‌تر است و با ابهامات کمتر).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
      modelBuilder.Entity<Blog>()
          .HasIndex(b => b.Url)
          .HasName("Index_Url");
اگر قسمت HasName را ذکر نکنید، نام آن <IX_<type name>_<property name درنظر گرفته می‌شود و برای اینکه ایندکس منحصربفردی را تعریف کنید، می‌توان متد IsUnique را به انتهای این زنجیره اضافه کرد:
 modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
همچنین می‌توان همانند composite keys، در اینجا نیز ترکیبی از خواص را به صورت یک ایندکس معرفی نمود:
modelBuilder.Entity<Person>()
   .HasIndex(idx => new { idx.FirstName, idx.LastName })
   .IsUnique();
در این حالت اگر HasName ذکر نشود، نام آن همانند الگویی است که پیشتر عنوان شد؛ با این تفاوت که قسمت property name آن، جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ خواهد بود.

یک نکته: اگر از پروایدر SQL Server استفاده می‌کنید، می‌توان متد الحاقی ویژه‌ای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.


امکان تعریف Alternate Keys در EF Core 1.0

به Unique Constraints در EF Core، نام Alternate Keys را داده‌اند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Car>()
     .HasAlternateKey(c => c.LicensePlate)
     .HasName("AlteranteKey_LicensePlate");
}
برای یک Alternate Key به صورت خودکار هم ایندکس ایجاد می‌شود و هم اینکه این ایندکس منحصربفرد خواهد بود.
اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن  <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکس‌های ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شده‌ی با _ تنظیم می‌شود.
برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
modelBuilder.Entity<Person>()
     .HasAlternateKey(x => new { x.FirstName, x.LastName });
در قسمت مهاجرت‌هایی که قرار است به بانک اطلاعاتی اعمال شوند، به یک UniqueConstraint ترجمه می‌شود:
 table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });


سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟

در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشن‌تر شدن مقصود، استفاده‌ی از Unique Constraint ترجیح داده می‌شود.
البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوت‌ها وجود دارند:
الف) یک Unique Constraint را نمی‌توان غیرفعال کرد؛ برخلاف Unique Indexها.
ب) Unique Constraint‌ها موارد اضافه‌تری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز می‌توانند تنظیم کنند.
ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.

که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.
مطالب
ایندکس منحصر به فرد با استفاده از Data Annotation در EF Code First
در حال حاضر امکان خاصی برای ایجاد ایندکس منحصر به فرد در EF First Code وجود ندارد, برای این کار راه‌های زیادی وجود دارد مانند پست قبلی آقای نصیری, در این آموزش از Data Annotation و یا همان Attribute  هایی که بالای Property‌های مدل‌ها قرار می‌دهیم, مانند کد زیر : 
public class User
    {
        public int Id { get; set; }

        [Unique]
        public string Email { get; set; }

        [Unique("MyUniqueIndex",UniqueIndexOrder.ASC)]
        public string Username { get; set; }

        [Unique(UniqueIndexOrder.DESC)]
        public string PersonalCode{ get; set; }

        public string Password { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

همانطور که در کد بالا می‌بینید با استفاده از Attribute Unique ایندکس منحصر به فرد آن در دیتابیس ساخته خواهد شد.
ابتدا یک کلاس برای Attribute Unique به صورت زیر ایحاد کنید : 
using System;

namespace SampleUniqueIndex
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public class UniqueAttribute : Attribute
    {
        public UniqueAttribute(UniqueIndexOrder order = UniqueIndexOrder.ASC) {
            Order = order;
        }
        public UniqueAttribute(string indexName,UniqueIndexOrder order = UniqueIndexOrder.ASC)
        {
            IndexName = indexName;
            Order = order;
        }
        public string IndexName { get; private set; }
        public UniqueIndexOrder Order { get; set; }
    }

    public enum UniqueIndexOrder
    {
        ASC,
        DESC
    }
}
در کد بالا یک Enum برای مرتب سازی ایندکس به دو صورت صعودی و نزولی قرار دارد, همانند کد ابتدای آموزش که مشاهده می‌کنید امکان تعریف این Attribute به سه صورت امکان دارد که به صورت زیر می‌باشد:
1. ایجاد Attribute بدون هیچ پارامتری که در این صورت نام ایندکس با استفاده از نام جدول و آن فیلد ساخته خواهد شد :  IX_Unique_TableName_FieldName و مرتب ساری آن به صورت صعودی می‌باشد.
2.نامی برای ایندکس انتخاب کنید تا با آن نام در دیتابیس ذخبره شود, در این حالت مرتب سازی آن هم به صورت صعودی می‌باشد.
3. در حالت سوم شما ضمن وارد کردن نام ایندکس مرتب سازی آن را نیز وارد می‌کنید.
بعد از کلاس Attribute حالا نوبت به کلاس اصلی میرسد که به صورت زیر می‌باشد:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Linq;
using System.Reflection;

namespace SampleUniqueIndex
{
    public static class DbContextExtention
    {
        private static BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;

        public static void ExecuteUniqueIndexes(this DbContext context)
        {
            var tables = GetTables(context);
            var query = "";
            foreach (var dbSet in GetDbSets(context))
            {
                var entityType = dbSet.PropertyType.GetGenericArguments().First();
                var table = tables[entityType.Name];
                var currentIndexes = GetCurrentUniqueIndexes(context, table.TableName);
                foreach (var uniqueProp in GetUniqueProperties(context, entityType, table))
                {
                    var indexName = string.IsNullOrWhiteSpace(uniqueProp.IndexName) ?
                        "IX_Unique_" + uniqueProp.TableName + "_" + uniqueProp.FieldName :
                        uniqueProp.IndexName;

                    if (!currentIndexes.Contains(indexName))
                    {
                        query += "ALTER TABLE [" + table.TableSchema + "].[" + table.TableName + "] ADD CONSTRAINT [" + indexName + "] UNIQUE ([" + uniqueProp.FieldName + "] " + uniqueProp.Order + "); ";
                    }
                    else
                    {
                        currentIndexes.Remove(indexName);
                    }
                }
                foreach (var index in currentIndexes)
                {
                    query += "ALTER TABLE [" + table.TableSchema + "].[" + table.TableName + "] DROP CONSTRAINT " + index + "; ";
                }
            }

            if (query.Length > 0)
                context.Database.ExecuteSqlCommand(query);
        }

        private static List<string> GetCurrentUniqueIndexes(DbContext context, string tableName)
        {
            var sql = "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS where table_name = '"
                      + tableName + "' and CONSTRAINT_TYPE = 'UNIQUE'";
            var result = context.Database.SqlQuery<string>(sql).ToList();
            return result;
        }
        private static IEnumerable<PropertyDescriptor> GetDbSets(DbContext context)
        {
            foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(context))
            {
                var notMapped = prop.GetType().GetCustomAttributes(typeof(NotMappedAttribute),true);
                if (prop.PropertyType.Name == typeof(DbSet<>).Name && notMapped.Length == 0)
                    yield return prop;
            }
        }
        private static List<UniqueProperty> GetUniqueProperties(DbContext context, Type entity, TableInfo tableInfo)
        {
            var indexedProperties = new List<UniqueProperty>();
            var properties = entity.GetProperties(PublicInstance);
            var tableName = tableInfo.TableName;
            foreach (var prop in properties)
            {
                if (!prop.PropertyType.IsValueType && prop.PropertyType != typeof(string)) continue;

                UniqueAttribute[] uniqueAttributes = (UniqueAttribute[])prop.GetCustomAttributes(typeof(UniqueAttribute), true);
                NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])prop.GetCustomAttributes(typeof(NotMappedAttribute), true);
                if (uniqueAttributes.Length > 0 && notMappedAttributes.Length == 0)
                {
                    var fieldName = GetFieldName(context, entity, prop.Name);
                    if (fieldName != null)
                    {
                        indexedProperties.Add(new UniqueProperty
                        {
                            TableName = tableName,
                            IndexName = uniqueAttributes[0].IndexName,
                            FieldName = fieldName,
                            Order = uniqueAttributes[0].Order.ToString()
                        });
                    }
                }
            }
            return indexedProperties;
        }
        private static Dictionary<string, TableInfo> GetTables(DbContext context)
        {
            var tablesInfo = new Dictionary<string, TableInfo>();
            var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
            var tables = metadata.GetItemCollection(DataSpace.SSpace)
              .GetItems<EntityContainer>()
              .Single()
              .BaseEntitySets
              .OfType<EntitySet>()
              .Where(s => !s.MetadataProperties.Contains("Type")
                || s.MetadataProperties["Type"].ToString() == "Tables");
            foreach (var table in tables)
            {
                var tableName = table.MetadataProperties.Contains("Table")
                    && table.MetadataProperties["Table"].Value != null
                  ? table.MetadataProperties["Table"].Value.ToString()
                  : table.Name;
                var tableSchema = table.MetadataProperties["Schema"].Value.ToString();
                tablesInfo.Add(table.Name, new TableInfo
                {
                    EntityName = table.Name,
                    TableName = tableName,
                    TableSchema = tableSchema,
                });
            }

            return tablesInfo;
        }
        public static string GetFieldName(DbContext context, Type entityModel, string propertyName)
        {
            var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
            var osMembers = metadata.GetItem<EntityType>(entityModel.FullName, DataSpace.OSpace).Properties;
            var ssMebers = metadata.GetItem<EntityType>("CodeFirstDatabaseSchema." + entityModel.Name, DataSpace.SSpace).Properties;
            
            if (!osMembers.Contains(propertyName)) return null;

            var index = osMembers.IndexOf(osMembers[propertyName]);
            return ssMebers[index].Name;
        }

        internal class UniqueProperty
        {
            public string TableName { get; set; }
            public string FieldName { get; set; }
            public string IndexName { get; set; }
            public string Order { get; set; }
        }
        internal class TableInfo
        {
            public string EntityName { get; set; }
            public string TableName { get; set; }
            public string TableSchema { get; set; }
        }
    }
}
در کد بالا با استفاده از Extension Method برای کلاس DbContext یک متد با نام ExecuteUniqueIndexes  ایجاد می‌کنیم تا برای ایجاد ایندکس‌ها در دیتابیس از آن استفاده کنیم.
روند اجرای کلاس بالا به صورت زیر می‌باشد:
در ابتدای متد ()ExecuteUniqueIndexes  :
 public static void ExecuteUniqueIndexes(this DbContext context)
        {
            var tables = GetTables(context);
            ...
        }
با استفاده از متد ()GetTables ما تمام جداول ساخته توسط دیتایس توسط DbContext را گرفنه:
        private static Dictionary<string, TableInfo> GetTables(DbContext context)
        {
            var tablesInfo = new Dictionary<string, TableInfo>();
            var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
            var tables = metadata.GetItemCollection(DataSpace.SSpace)
              .GetItems<EntityContainer>()
              .Single()
              .BaseEntitySets
              .OfType<EntitySet>()
              .Where(s => !s.MetadataProperties.Contains("Type")
                || s.MetadataProperties["Type"].ToString() == "Tables");
            foreach (var table in tables)
            {
                var tableName = table.MetadataProperties.Contains("Table")
                    && table.MetadataProperties["Table"].Value != null
                  ? table.MetadataProperties["Table"].Value.ToString()
                  : table.Name;
                var tableSchema = table.MetadataProperties["Schema"].Value.ToString();
                tablesInfo.Add(table.Name, new TableInfo
                {
                    EntityName = table.Name,
                    TableName = tableName,
                    TableSchema = tableSchema,
                });
            }

            return tablesInfo;
        }
با استفاده از این طریق چنانچه کاربر نام دیگری برای هر جدول در نظر بگیرد مشکلی ایجاد نمی‌شود و همینطور Schema جدول نیز گرفته می‌شود, سه مشخصه نام مدل و نام جدول و Schema جدول در کلاس TableInfo قرار داده می‌شود و در انتها تمام جداول در یک Collection قرار داده میشوند و به عنوان خروجی متد استفاده می‌شوند.
بعد از آنکه نام جداول متناظر با نام مدل آنها را در اختیار داریم نوبت به گرفتن تمام DbSet‌ها در DbContext می‌باشد که با استفاده از متد ()GetDbSets :
public static void ExecuteUniqueIndexes(this DbContext context)
        {
            var tables = GetTables(context);
            var query = "";
            foreach (var dbSet in GetDbSets(context))
            {
            ....
        }
در این متد چنانچه Property دارای Attribute NotMapped باشد در لیست خروجی متد قرار داده نمی‌شود. 
سپس داخل چرخه DbSet‌ها نوبت به گرفتن ایندکس‌های موجود با استفاده از متد ()GetCurrentUniqueIndexes برای این مدل می‌باشد تا از ایجاد دوباره آن جلوگیری شود و البته اگر ایندکس هایی را در مدل تعربف نکرده باشید از دیتابیس حذف شوند.
        public static void ExecuteUniqueIndexes(this DbContext context)
        {
            ...
            foreach (var dbSet in GetDbSets(context))
            {
                var entityType = dbSet.PropertyType.GetGenericArguments().First();
                var table = tables[entityType.Name];
                var currentIndexes = GetCurrentUniqueIndexes(context, table.TableName);
            }
        }
بعد از آن نوبت به گرفتن Property‌های دارای Attribute Unique می‌باشد که این کار نیز با استفاده از متد ()GetUniqueProperties انجام خواهد شد.
در متد ()GetUniqueProperties چند شرط بررسی خواهد شد از جمله اینکه Property از نوع Value Type باشد و نه یک کلاس سپس Attribute NotMapped را نداشته باشد و بعد از آن می‌بایست نام متناظر با آن Property را در دیتابیس به دست بیاریم برای این کار از متد ()GetFieldName استفاده می‌کنیم:
        public static string GetFieldName(DbContext context, Type entityModel, string propertyName)
        {
            var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
            var osMembers = metadata.GetItem<EntityType>(entityModel.FullName, DataSpace.OSpace).Properties;
            var ssMebers = metadata.GetItem<EntityType>("CodeFirstDatabaseSchema." + entityModel.Name, DataSpace.SSpace).Properties;
            
            if (!osMembers.Contains(propertyName)) return null;

            var index = osMembers.IndexOf(osMembers[propertyName]);
            return ssMebers[index].Name;
        }
برای این کار با استفاده از MetadataWorkspace در DbContext دو لیست SSpace و OSpace استفاده می‌کنیم که در ادامه در مورد این گونه لیست ها بیشتر توضیح می‌دهیم , سپس با استفاده از Member‌های این دو لیست و ایندکس‌های متناظر در این دو لیست نام متناظر با Property را در دیتابیس پیدا خواهیم کرد, البته یک نکته مهم هست چنانچه برای فیلد‌های دیتابیس OrderColumn قرار داده باشید دو لیست Member‌ها از نظر ایندکس متناظر متفاوت خواهند شد پس در نتیجه ایندکس به اشتباه برروی یک فیلد دیگر اعمال خواهد شد.
لیست‌ها در MetadataWorkspace:
1. CSpace : این لیست شامل آبجکت‌های Conceptual از مدل‌های شما می‌باشد تا برای Mapping دیتابیس با مدل‌های شما مانند مبدلی این بین عمل کند.
2. OSpace : این لیست شامل آبجکت‌های مدل‌های شما می‌باشد.
3. SSpace : این لیست نیز شامل آبجکت‌های مربوط به دیتابیس از مدل‌های شما می‌باشد
4. CSSpace : این لیست شامل تنظیمات Mapping بین دو لیست SSpace و CSpace می‌باشد.
5. OCSpace : این لیست شامل تنظیمات Mapping بین دو لیست OSpace و CSpace می‌باشد.
روند Mapping مدل‌های شما از OSpace شروع شده و به SSpace ختم میشود که سه لیست دیگز شامل تنظیماتی برای این کار می‌باشند.
و حالا در متد اصلی ()ExecuteUniqueIndexes ما کوئری مورد نیاز برای ساخت ایندکس‌ها را ساخته ایم.

حال برای استفاده از متد()ExecuteUniqueIndexes می‌بایست در متد Seed آن را صدا بزنیم تا کار ساخت ایندکس‌ها شروع شود، مانند کد زیر:
protected override void Seed(myDbContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
            context.ExecuteUniqueIndexes();
        }
چند نکته برای ایجاد ایندکس منحصر به فرد وجود دارد که در زیر به آنها اشاره می‌کنیم:
1. فیلد‌های متنی باید حداکثر تا 350 کاراکتر باشند تا ایندکس اعمال شود.
2. همانطور که بالاتر اشاره شد برای فیلد‌های دیتابیس OrderColumn اعمال نکنید که علت آن در بالا توضیح داده شد

دانلود فایل پروژه:
Sample_UniqueIndex.zip