مسیرراه‌ها
ASP.NET MVC
              مطالب دوره‌ها
              انتقال خواص محاسباتی Entity Framework به ViewModelها توسط AutoMapper
              در طی دو مطلب (^ و ^) با نحوه‌ی قرار دادن خواص محاسباتی، درون کلاس‌های مدل‌های بانک اطلاعاتی مورد استفاده‌ی توسط Entity Framework آشنا شدیم. در اینجا قصد داریم این خواص محاسباتی را از کلاس‌های اصلی مدل‌های بانک اطلاعاتی خود خارج و به ViewModelها منتقل کنیم؛ چون اساسا هدف از این نوع خواص ویژه، ارائه اطلاعات نمایشی است به کاربر و نه ذخیره سازی آن‌ها در بانک اطلاعاتی.


              مدل‌ها و تنظیمات برنامه

              مدل‌ها و تنظیمات مورد استفاده‌ی در مثال جاری، با مدل‌های مطلب «لغو Lazy Loading در حین کار با AutoMapper و Entity Framework» یکی است. فقط ViewModel مورد استفاده اینبار یک‌چنین ساختاری را دارد:
              public class UserViewModel
              {
                  public int Id { set; get; }
                  public string CustomName { set; get; }
                  public int PostsCount { set; get; }
              }
              در  اینجا می‌خواهیم در حین نگاشت اطلاعات جدول کاربران بانک اطلاعاتی به UserViewModel :
               - خاصیت CustomName از جمع نام و سن شخص تشکیل شود.
               - خاصیت PostsCount بیانگر جمع مطالب ارسالی آن شخص باشد.


              نگاشت‌های AutoMapper می‌توانند حاوی توابع تجمعی نیز باشند

              برای حل مساله‌ی فوق تنها کافی است نگاشت ذیل را تهیه کنیم:
              public class TestProfile : Profile
              {
                  protected override void Configure()
                  {
                      this.CreateMap<User, UserViewModel>()
                          .ForMember(dest => dest.CustomName,
                                     opt => opt.MapFrom(src => src.Name + "[" + src.Age + "]"))
                           .ForMember(dest => dest.PostsCount,
                                      opt => opt.MapFrom(src => src.BlogPosts.Count()));
                  }
               
                  public override string ProfileName
                  {
                      get { return this.GetType().Name; }
                  }
              }
              در این نگاشت عنوان شده‌است که اطلاعات CustomName را مطابق فرمول خاص جمع نام شخص و سن او تهیه کن. همچنین مقدار PostsCount، باید از جمع تعداد مطالب ارسالی او تشکیل شود.


              کوئری نهایی استفاده کننده از تنظیمات نگاشت تهیه شده

              در ادامه متدهای Project To را جهت استفاده‌ی از تنظیمات نگاشت فوق بکار می‌گیریم:
              using (var context = new MyContext())
              {
                  var user1 = context.Users
                                     .Project()
                                     .To<UserViewModel>()
                                     .FirstOrDefault();
               
                  if (user1 != null)
                  {
                      Console.Write(user1.CustomName);
                      Console.Write(user1.PostsCount);
                  }
              }
              این کوئری یک چنین خروجی SQL ایی را به همراه دارد:
                              SELECT
                                  [Limit1].[Id] AS [Id],
                                  [Limit1].[C1] AS [C1],
                                  [Limit1].[C2] AS [C2]
                                  FROM ( SELECT TOP (1)
                                      [Project1].[Id] AS [Id],
                                      CASE WHEN ([Project1].[Name] IS NULL) THEN N'' ELSE [Project1].[Name] END
                                   + N'[' +  CAST( [Project1].[Age] AS nvarchar(max)) + N']' AS [C1],
                                      [Project1].[C1] AS [C2]
                                      FROM ( SELECT
                                          [Extent1].[Id] AS [Id],
                                          [Extent1].[Name] AS [Name],
                                          [Extent1].[Age] AS [Age],
                                          (SELECT
                                              COUNT(1) AS [A1]
                                              FROM [dbo].[BlogPosts] AS [Extent2]
                                              WHERE [Extent1].[Id] = [Extent2].[UserId]) AS [C1]
                                          FROM [dbo].[Users] AS [Extent1]
                                      )  AS [Project1]
                                  )  AS [Limit1]
              همانطور که مشاهده می‌کنید، تنظیمات نگاشت تهیه شده (نحوه‌ی تهیه‌ی نام و جمع تعداد مطالب شخص) به SQL ترجمه شده‌اند.


              کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
              مطالب
              بررسی نحوه‌ی راه اندازی پروژه‌ی Decision
              پروژه‌ی Decision را می‌توان چکیده‌ی تمام مطالب سایت دانست که در آن جمع آوری نکات ASP.NET MVC 5.x، EF Code First 6.x، مباحث تزریق وابستگی‌ها، کار با AutoMapper، بوت استرپ 3 و غیره لحاظ شده‌اند. به همین جهت درک آن بدون مطالعه‌ی « تمام » مطالب سایت میسر نیست و همچنین راه اندازی آن.
              در این مطلب با توجه به سؤالات زیادی که در مورد صرفا نحوه‌ی اجرای بدون خطای آن وجود داشت، ریز مراحل آن‌را بررسی می‌کنیم.


              پیشنیازهای توسعه‌ی برنامه
              - با توجه به استفاده از ویژگی‌های C# 6 در این پروژه، حتما نیاز است برای کار و اجرای آن از VS 2015 استفاده کنید.
              - همچنین این پروژه از قابلیت «فایل استریم» SQL Server استفاده می‌کند. بنابراین نیاز است نگارش متناسبی از SQL Server را پیشتر نصب کرده باشید (هر نگارشی بالاتر از SQL Server 2005).
              - اگر از ReSharper استفاده می‌کنید، به صورت موقت آن‌را به حالت تعلیق درآورید (منوی tools، گرینه‌ی options و انتخاب resharper و سپس suspend کردن آن). این مورد سرعت بازیابی بسته‌های نیوگت را به شدت افزایش می‌دهد.


              بازیابی وابستگی‌های نیوگت پروژه

              مرسوم نیست چند 10 مگابایت وابستگی‌های پروژه را به صورت فایل‌های باینری، به مخزن کدها ارسال کرد. از این جهت که نیوگت بر اساس مداخل فایل‌های packages.config، قابلیت بازیابی و نصب خودکار آن‌ها را دارد. بنابراین ابتدا package manger console را باز کنید؛ از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console


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

              پس از پایان کار بازیابی بسته‌ها، یکبار کل Solution را Build کنید تا مطمئن شوید که تمام بسته‌های مورد نیاز به درستی بازیابی و نصب شده‌اند (Ctrl+Shift+B و یا همان منوی Build و انتخاب گزینه‌ی Build Solution).



              تنظیمات رشته اتصالی بانک اطلاعاتی برنامه

              پس از Build موفق کل Solution در مرحله‌ی قبل، اکنون نوبت به برپایی تنظیمات بانک اطلاعاتی برنامه است. برای این منظور فایل web.config ذیل را باز کنید:
              Decision\src\Decision.Web\Web.config
              یک چنین تنظیمی را مشاهده می‌کنید:
                <connectionStrings>
                  <clear />
                  <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
                </connectionStrings>
              از آنجائیکه بر روی سیستم من SQL Server نگارش Developer نصب است و از SQL Server Express استفاده نمی‌کنم، تنظیمات فوق را به نحو ذیل تغییر خواهم داد:
                <connectionStrings>
                  <clear />
                  <add name="DefaultConnection" connectionString="Data Source=(local);Initial Catalog=DecisionDb;Integrated Security = true;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
                </connectionStrings>
              تنها تغییر صورت گرفته، تنظیم data source است. مابقی موارد یکی است و تفاوتی نمی‌کند.

              در این حالت نیاز است بانک اطلاعاتی خالی DecisionDb را خودتان ایجاد کنید. علت آن به AutomaticMigrationsEnabled = false بر می‌گردد؛ که در ادامه توضیح داده شده‌است و همچنین وجود تنظیم ذیل در فایل Decision\src\Decision.Web\App_Start\ApplicationStart.cs
               Database.SetInitializer<ApplicationDbContext>(null);
              این تنظیم و نال بودن پارامتر ورودی آن به این معنا است که اولا برنامه یک بانک اطلاعاتی جدید را به صورت خودکار ایجاد نمی‌کند و همچنین کار Migrations خودکار نیست.


              ایجاد بانک اطلاعاتی برنامه و تنظیمات آن

              پس از آن، نوبت به ایجاد بانک اطلاعاتی برنامه است. چون این برنامه از EF Code first استفاده می‌کند، قادر است بانک اطلاعاتی ذکر شده‌ی در Initial Catalog فوق را به صورت خودکار ایجاد کند (با تمام جداول، روابط و تنظیمات آن‌ها). این اطلاعات هم از پروژه‌ی Decision.DataLayer و پوشه‌ی Migrations آن تامین می‌شوند.
              اگر به فایل Decision\src\Decision.DataLayer\Migrations\201602072159421_Initial.cs مراجعه کنید، یکسری تنظیمات دستی را هم علاوه بر کدهای خودکار EF، مشاهده خواهید کرد:
               //. . .
              Sql("EXEC sp_configure filestream_access_level, 2");
              Sql("RECONFIGURE", true);
              
              Sql("alter database DecisionDb Add FileGroup FileGroupApplicant contains FileStream", true);
              Sql("alter database DecisionDb add file ( name = 'ApplicantDocuements'  ,  filename = 'C:\\FileStream\\ApplicantDocuements') to filegroup FileGroupApplicant", true);
              //. . .
              این‌ها مواردی هستند که کار تنظیمات فایل استریم را به صورت خودکار انجام می‌دهند.
              بنابراین نیاز است در درایور C، پوشه‌ی خالی FileStream از پیش تهیه شده باشد (نیازی به ایجاد پوشه‌ی ApplicantDocuements نیست و این پوشه به صورت خودکار ایجاد می‌شود).

              و در فایل Decision\src\Decision.DataLayer\Migrations\Configuration.cs مشخص شده‌است که AutomaticMigrationsEnabled = false. به این معنا که تنظیمات فوق به صورت خودکار به بانک اطلاعاتی اعمال نشده و باید چند دستور ذیل را به صورت دستی صادر کنیم:
              الف) ابتدا package manager console را مجددا باز کنید و در اینجا default project را بر روی Decision.DataLayer قرار دهید. از این جهت که قرار است اطلاعات migration را از این پروژه دریافت کنیم:


              در غیراینصورت پیام خطای No migrations configuration type was found in the assembly را دریافت خواهید کرد.

              ب) سپس دستور ذیل را صادر کنید (با این فرض که بانک اطلاعاتی خالی DecisionDb ذکر شده‌ی در قسمت قبل را پیشتر ایجاد کرده‌اید):
               PM> Update-Database -Verbose -ConnectionStringName "DefaultConnection" -StartUpProjectName "Decision.Web"
              این تنظیمات به این معنا است که Update-Database را بر اساس اطلاعات پروژه‌ی Decision.DataLayer انجام بده (همان انتخاب default project)؛ اما رشته‌ی اتصالی را از پروژه‌ی Decision.Web و تنظیمات DefaultConnection آن دریافت کن.

              من در این حالت پیام خطای Update-Database : The term 'Update-Database' is not recognized as the name of a cmdlet را دریافت کردم.
              راه حل: یکبار ویژوال استودیو را بسته و مجددا باز کنید تا کار نصب بسته‌ها و بارگذاری تمام وابستگی‌های آن‌ها به درستی صورت گیرد. این خطا به این معنا است که هرچند NuGet کار نصب EF را انجام داده‌است، اما هنوز اسکریپت‌های پاورشل آن که دستوراتی مانند Update-Database را اجرا می‌کنند، بارگذاری نشده‌اند. راه حل آن بستن و اجرای مجدد ویژوال استودیو است.
              پس از اجرای مجدد ویژوال استودیو و انتخاب default project صحیح (مطابق تصویر فوق)، مجددا دستور Update-Database  فوق را صادر کنید (با پارامترهای ویژه‌ی آن).
              با صدور این دستور، پیام خطای ذیل را دریافت کردم:
               The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework'
              registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded.
              برای رفع آن نیاز است EF را یکبار دیگر نصب کنید:
               PM> Update-Package -Reinstall "EntityFramework" -ProjectName "Decision.DataLayer"
              در ادامه مجددا کل Solution را Build کنید؛ چون Migrations بر اساس اطلاعات اسمبلی‌های کامپایل شده‌ی پروژه کار می‌کند.
              اینبار دستور update-database فوق (با پارامترهای ویژه‌ی آن) بدون مشکل اجرا شد و بانک اطلاعاتی مربوطه تشکیل گردید.




              اکنون برنامه قابل اجرا است و در این حالت است که می‌توان دکمه‌ی F5 را جهت اجرای برنامه فشرد. البته در این حالت بر روی پروژه‌ی Decision.Web کلیک راست کرده و گزینه‌ی set as startup project را نیز انتخاب کنید و سپس F5:



              لطفا سؤالاتی را که مرتبط با «راه اندازی» این پروژه نیستند، در قسمت بازخوردهای اختصاصی آن مطرح کنید.
              اشتراک‌ها
              مدیریت مباحث همزمانی مرتبط با یک Rich Domain Model با استفاده از EFCore و الگوی Aggregate

              In summary, the most important issues here are:

              • The Aggregate’s main task is to protect invariants (business rules, the boundary of immediate consistency)
              • In a multi-threaded environment, when multiple threads are running simultaneously on the same Aggregate, a business rule may be broken
              • A way to solve concurrency conflicts is to use Pessimistic or Optimistic concurrency techniques
              • Pessimistic Concurrency involves the use of a database transaction and a locking mechanism. In this way, requests are processed one after the other, so basically concurrency is lost and it can lead to deadlocks.
              • Optimistic Concurrency technique is based on versioning database records and checking whether the previously loaded version has not been changed by another thread.
              • Entity Framework Core supports Optimistic Concurrency. Pessimistic Concurrency is not supported
              • The Aggregate must always be treated and versioned as a single unit
              • Domain events are an indicator, that state was changed so Aggregate version should be changed as well 
              public class AggregateRootBase : Entity, IAggregateRoot
              {
                  private int _versionId;
              
                  public void IncreaseVersion()
                  {
                      _versionId++;
                  }
              }
              internal sealed class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
              {
                  public void Configure(EntityTypeBuilder<Order> builder)
                  {
                      builder.Property("_versionId").HasColumnName("VersionId").IsConcurrencyToken();
               
                      //...
                  }
              }
              var order = await _ordersContext.Orders.FindAsync(orderId);
              order.AddOrderLine(request.ProductCode); 
              var domainEvents = DomainEventsHelper.GetAllDomainEvents(order);
              if (domainEvents.Any())
              {
                  order.IncreaseVersion();
              }
              await _ordersContext.SaveChangesAsync();


              مدیریت مباحث همزمانی مرتبط با یک Rich Domain Model با استفاده از EFCore و الگوی Aggregate
              نظرات مطالب
              استفاده‌ی گسترده از DateTimeOffset در NET Core.
              یک نکته‌ی تکمیلی: تبدیلگرهای DateTimeOffset برای بانک‌های اطلاعاتی که از آن پشتیبانی نمی‌کنند

              خود EF Core به همراه تبدیلگرهای توکار زیر برای کار ساده‌تر با DateTimeOffset در بانک اطلاعاتی‌هایی مانند SQLite و یا MySQL است:

              DateTimeOffsetToBinaryConverter - DateTimeOffset to binary-encoded 64-bit value (stores it as a long, slight reduction in precision)

              DateTimeOffsetToBytesConverter - DateTimeOffset to byte array (stores it as a 12 byte array, 8 bytes for time, 4 bytes for offset. Full precision.)

              DateTimeOffsetToStringConverter - DateTimeOffset to string (ISO 8601 string including timezone) 

              و برای مثال می‌توان آن‌ها را به صورت زیر و سراسری، به سیستم معرفی کرد:
              protected override void OnModelCreating(ModelBuilder builder)
              {
                  base.OnModelCreating(builder);
              
                  if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
                  {
                      // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
                      // here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
                      // To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
                      // use the DateTimeOffsetToBinaryConverter
                      // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
                      // This only supports millisecond precision, but should be sufficient for most use cases.
                      foreach (var entityType in builder.Model.GetEntityTypes())
                      {
                          var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
                          foreach (var property in properties)
                          {
                              builder
                                  .Entity(entityType.Name)
                                  .Property(property.Name)
                                  .HasConversion(new DateTimeOffsetToBinaryConverter());
                          }
                      }
                  }
              }
              مطالب
              کار با نوع داده‌ی HierarchyID توسط Entity framework
              نوع داده‌ی HierarchyID به همراه SQL Server 2008 برای کار با داده‌هایی با ساختار درختی ارائه شد. در حال حاضر هیچکدام از ORMهای موجود، پشتیبانی رسمی را از این نوع داده به عمل نمی‌آورند؛ اما با توجه به سورس باز بودن Entity framework، یک Fork مستقل از آن تهیه شده‌است و این نوع داده‌ی جدید به همراه متدهای مرتبط با آن، به این Fork اضافه شده‌اند.
              - اصل Fork در اینجا
              - تاریخچه‌ی این Fork غیر رسمی در اینجا
              - بسته‌ی نیوگت آن در اینجا

              چون تیم EF در نگارش فعلی این کتابخانه حاضر به افزودن این نوع جدید نشده‌است، بنابراین بجای بسته‌ی اصلی Entity framework نیاز است بسته‌ی EntityFrameworkWithHierarchyId را نصب کنید.
               PM> install-package EntityFrameworkWithHierarchyId

              یک تذکر مهم:
              چون امضای دیجیتال این بسته، با امضای دیجیتال بسته‌ی اصلی EF یکی نیست، اگر پروژه‌ی شما صرفا از EF استفاده می‌کند، مشکلی نخواهید داشت. اما اگر برای مثال از ASP.NET Identity کامپایل شده‌ی برای کار با EF اصلی استفاده کنید، پیام یافت نشدن DLL مرتبط را دریافت خواهید کرد.


              تعریفی مدلی با خاصیتی از نوع جدید HierarchyId

              public class Employee
              {
                  public int Id { get; set; }
               
                  [Required, MaxLength(100)]
                  public string Name { get; set; }
               
                  [Required]
                  public HierarchyId Node { get; set; } // نوع داده جدید
              }
              در اینجا مدلی را ملاحظه می‌کنید که از نوع داده‌ی جدید HierarchyId استفاده می‌کند. همانطور که عنوان شد این نوع در بسته‌ی EntityFrameworkWithHierarchyId موجود است.


              تعریف Context و مقدار دهی اولیه‌ی آن

              در این حالت Context برنامه به همراه تنظیمات اولیه‌ی Migrations آن یک چنین شکلی را پیدا خواهد کرد:
              public class MyContext : DbContext
              {
                  public DbSet<Employee> Employees { get; set; }
               
                  public MyContext()
                      : base("Connection1")
                  {
                      this.Database.Log = log => Console.WriteLine(log);
                  }
              }
               
              public class Configuration : DbMigrationsConfiguration<MyContext>
              {
                  public Configuration()
                  {
                      AutomaticMigrationsEnabled = true;
                      AutomaticMigrationDataLossAllowed = true;
                  }
               
                  protected override void Seed(MyContext context)
                  {
                      if (context.Employees.Any())
                          return;
               
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees] ADD NodePath as Node.ToString() persisted");
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees] ADD Level AS Node.GetLevel() persisted");
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees] ADD ManagerNode as Node.GetAncestor(1) persisted");
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees] ADD ManagerNodePath as Node.GetAncestor(1).ToString() persisted");
               
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [UK_EmployeeNode] UNIQUE NONCLUSTERED (Node)");
                      context.Database.ExecuteSqlCommand(
                          "ALTER TABLE [dbo].[Employees]  WITH CHECK ADD CONSTRAINT [EmployeeManagerNodeNodeFK] " +
                          "FOREIGN KEY([ManagerNode]) REFERENCES [dbo].[Employees] ([Node])");
               
                      context.Employees.Add(new Employee { Name = "Root", Node = new HierarchyId("/") });
                      context.Employees.Add(new Employee { Name = "Emp1", Node = new HierarchyId("/1/") });
                      context.Employees.Add(new Employee { Name = "Emp2", Node = new HierarchyId("/2/") });
                      context.Employees.Add(new Employee { Name = "Emp3", Node = new HierarchyId("/1/1/") });
                      context.Employees.Add(new Employee { Name = "Emp4", Node = new HierarchyId("/1/1/1/") });
                      context.Employees.Add(new Employee { Name = "Emp5", Node = new HierarchyId("/2/1/") });
                      context.Employees.Add(new Employee { Name = "Emp6", Node = new HierarchyId("/1/2/") });
               
                      base.Seed(context);
                  }
              }
              در اینجا نحوه‌ی تعریف رکوردهای جدید مبتنی بر HierarchyId را مشاهده می‌کنید که توسط آن‌ها تعدادی کارمند، در یک سازمان فرضی ثبت شده‌اند.
              همچنین چند فیلد محاسباتی نیز بر اساس امکانات توکار SQL Server اضافه شده‌اند. متدهایی مانند ToString، GetLevel، GetAncestor و امثال آن جزئی از پیاده سازی توکار SQL Server هستند. همچنین این متدها توسط کتابخانه‌ی EntityFrameworkWithHierarchyId نیز ارائه شده‌اند.


              کوئری نویسی

              مرتب سازی رکوردها بر اساس HierarchyId آن‌ها

              using (var context = new MyContext())
              {
                  Console.WriteLine("\ngetItems OrderByDescending(employee => employee.Node)");
               
                  var employees = context.Employees.OrderByDescending(employee => employee.Node).ToList();
                  foreach (var employee in employees)
                  {
                      Console.WriteLine("{0} {1}", employee.Id, employee.Node);
                  }
               }
              با این خروجی
              SELECT
                  [Extent1].[Id] AS [Id],
                  [Extent1].[Name] AS [Name],
                  [Extent1].[Node] AS [Node]
                  FROM [dbo].[Employees] AS [Extent1]
                  ORDER BY [Extent1].[Node] DESC
              
              
              6 /2/1/
              3 /2/
              7 /1/2/
              5 /1/1/1/
              4 /1/1/
              2 /1/
              1 /


              یافتن یک HierarchyId خاص و سپس یافتن کلیه‌ی فرزندان آن در یک سطح پایین‌تر

              using (var context = new MyContext())
              {
                  Console.WriteLine("\nGetAncestor(1) of /1/");
               
                  var firstItem = context.Employees.Single(employee => employee.Node == new HierarchyId("/1/"));
                  foreach (var item in context.Employees.Where(employee => firstItem.Node == employee.Node.GetAncestor(1)))
                  {
                      Console.WriteLine("{0} {1}", item.Id, item.Name);
                  }
              }
              این کوئری را به این شکل نیز می‌توان عنوان کرد: یافتن یک HierarchyId و سپس یافتن کلیه نودهایی که والدشان (GetAncestor) این HierarchyId است. عدد یک در اینجا مشخص کننده‌ی Level یا سطح است.
              با این خروجی:
              SELECT TOP (2)
                  [Extent1].[Id] AS [Id],
                  [Extent1].[Name] AS [Name],
                  [Extent1].[Node] AS [Node]
                  FROM [dbo].[Employees] AS [Extent1]
                  WHERE cast('/1/' as hierarchyid) = [Extent1].[Node]
              
              SELECT
                  [Extent1].[Id] AS [Id],
                  [Extent1].[Name] AS [Name],
                  [Extent1].[Node] AS [Node]
                  FROM [dbo].[Employees] AS [Extent1]
                  WHERE (@p__linq__0 = ([Extent1].[Node].GetAncestor(1))) OR ((@p__linq__0 IS
              NULL) AND ([Extent1].[Node].GetAncestor(1) IS NULL))
              -- p__linq__0: '/1/' (Type = Object)
              
              4 Emp3
              7 Emp6

              کوئری‌های فوق را می‌توان بجای استفاده از متد GetAncestor، با استفاده از متد IsDescendantOf به شکل زیر نیز نوشت:
              var list = context.Employees.Where(
                        employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")) &&
                                            employee.Node.GetLevel() == 2).ToList();
              با این خروجی SQL (یک کوئری بجای دو کوئری):
              SELECT
                  [Extent1].[Id] AS [Id],
                  [Extent1].[Name] AS [Name],
                  [Extent1].[Node] AS [Node]
                  FROM [dbo].[Employees] AS [Extent1]
                  WHERE (([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1) 
                  AND (2 = ([Extent1].[Node].GetLevel()))


              جابجا کردن نودها توسط متد GetReparentedValue

              در کوئری ذیل، تمامی فرزندان ریشه‌ی /1/ یافت شده و سپس والد آن‌ها به صورت پویا تغییر داده می‌شود:
              var items = context.Employees.Where(employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")))
                  .Select(employee => new
                  {
                      Id = employee.Id,
                      OrigPath = employee.Node,
                      ReparentedValue = employee.Node.GetReparentedValue(new HierarchyId("/1/"), HierarchyId.GetRoot()),
                      Level = employee.Node.GetLevel()
                  }).ToList();
               
              foreach (var item in items)
              {
                  Console.WriteLine("Id:{0}; OrigPath:{1}; ReparentedValue:{2}; Level:{3}", item.Id, item.OrigPath, item.ReparentedValue, item.Level);
              }
              با این خروجی
              SELECT
                  [Extent1].[Id] AS [Id],
                  [Extent1].[Node] AS [Node],
                  [Extent1].[Node].GetReparentedValue(cast('/1/' as hierarchyid), hierarchyid::GetRoot()) AS [C1],
                  [Extent1].[Node].GetLevel() AS [C2]
                  FROM [dbo].[Employees] AS [Extent1]
                  WHERE ([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1
              
              
              Id:2; OrigPath:/1/; ReparentedValue:/; Level:1
              Id:4; OrigPath:/1/1/; ReparentedValue:/1/; Level:2
              Id:5; OrigPath:/1/1/1/; ReparentedValue:/1/1/; Level:3
              Id:7; OrigPath:/1/2/; ReparentedValue:/2/; Level:2

              کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
              HierarcyIdTests.zip
              مطالب
              انتشار پیش نمایش ASP.NET Identity 2.0.0-alpha1
              مایکروسافت در تاریخ 20 دسامبر 2013 پیش نمایش نسخه جدید ASP.NET Identity را معرفی کرد. تمرکز اصلی در این انتشار، رفع مشکلات نسخه 1.0 بود. امکانات جدیدی هم مانند Account Confirmation و Password Reset اضافه شده اند.

              دانلود این انتشار
              ASP.NET Identity را می‌توانید در قالب یک پکیج NuGet دریافت کنید. در پنجره Manage NuGet Packages می‌توانید پکیج‌های Preview را لیست کرده و گزینه مورد نظر را
              نصب کنید. برای نصب پکیج‌های pre-release توسط Package Manager Console از فرامین زیر استفاده کنید.
              • Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.0.0-alpha1 -Pre
              • Install-Package Microsoft.AspNet.Identity.Core -Version 2.0.0-alpha1 -Pre
              • Install-Package Microsoft.AspNet.Identity.OWIN -Version 2.0.0-alpha1 -Pre
              دقت کنید که حتما از گزینه "Include Prerelease" استفاده می‌کنید. برای اطلاعات بیشتر درباره نصب پکیج‌های Pre-release لطفا به این لینک و یا این لینک مراجعه کنید.

              در ادامه لیست امکانات جدید و مشکلات رفع شده را می‌خوانید.

              Account Confirmation
              سیستم ASP.NET Identity حالا از Account Confirmation پشتیبانی می‌کند. این یک سناریوی بسیار رایج است. در اکثر وب سایت‌های امروزی پس از ثبت نام، حتما باید ایمیل خود را تایید کنید. پیش از تایید ثبت نام قادر به انجام هیچ کاری در وب سایت نخواهید بود، یعنی نمی‌توانید Login کنید. این روش مفید است، چرا که از ایجاد حساب‌های کاربری نامعتبر (bogus) جلوگیری می‌کند. همچنین این روش برای برقراری ارتباط با کاربران هم بسیار کارآمد است. از آدرس‌های ایمیل کاربران می‌توانید در وب سایت‌های فروم، شبکه‌های اجتماعی، تجارت آنلاین و بانکداری برای اطلاع رسانی و دیگر موارد استفاده کنید.

              نکته: برای ارسال ایمیل باید تنظیمات SMTP را پیکربندی کنید. مثلا می‌توانید از سرویس‌های ایمیل محبوبی مانند  SendGrid  استفاده کنید، که با Windows Azure براحتی یکپارچه می‌شود و از طرف توسعه دهنده اپلیکیشن هم نیاز به پیکربندی ندارد.

              در مثال زیر نیاز دارید تا یک سرویس ایمیل برای ارسال ایمیل‌ها پیکربندی کنید. همچنین کاربران پیش از تایید ایمیل شان قادر به بازنشانی کلمه عبور نیستند.

              Password Reset
              این هم یک سناریوی رایج و استاندارد است. کاربران در صورتی که کلمه عبورشان را فراموش کنند، می‌توانند از این قابلیت برای بازنشانی آن استفاده کنند. کلمه عبور جدیدی بصورت خودکار تولید شده و برای آنها ارسال می‌شود. کاربران با استفاده از این رمز عبور جدید می‌توانند وارد سایت شوند و سپس آن را تغییر دهند.

              Security Token Provider
              هنگامی که کاربران کلمه عبورشان را تغییر می‌دهند، یا اطلاعات امنیتی خود را بروز رسانی می‌کنند (مثلا حذف کردن لاگین‌های خارجی مثل فیسبوک، گوگل و غیره) باید شناسه امنیتی (security token) کاربر را بازتولید کنیم و مقدار قبلی را Invalidate یا بی اعتبار سازیم. این کار بمنظور حصول اطمینان از بی اعتبار بودن تمام شناسه‌های قبلی است که توسط کلمه عبور پیشین تولید شده بودند. این قابلیت، یک لایه امنیتی بیشتر برای اپلیکیشن شما فراهم می‌کند. چرا که وقتی کاربری کلمه عبورش را تغییر بدهد از همه جا logged-out می‌شود. یعنی از تمام مرورگرهایی که برای استفاده از اپلیکیشن استفاده کرده خارج خواهد شد.

              برای پیکربندی تنظیمات این قابلیت می‌توانید از فایل Startup.Auth.cs استفاده کنید. می‌توانید مشخص کنید که میان افزار OWIN cookie هر چند وقت یکبار باید شناسه امنیتی کاربران را بررسی کند. به لیست زیر دقت کنید.
              // Enable the application to use a cookie to store information for the signed in user
              // and to use a cookie to temporarily store information about a user logging in with a third party login provider
              // Configure the sign in cookie
              app.UseCookieAuthentication(newCookieAuthenticationOptions {
                  AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                  LoginPath = newPathString("/Account/Login"),
                  Provider = newCookieAuthenticationProvider {
                      OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                          validateInterval: TimeSpan.FromSeconds(5),
                          regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                  }
              });

              امکان سفارشی کردن کلید‌های اصلی Users و Roles

              در نسخه 1.0 نوع فیلدهای کلید اصلی در جداول Users و Roles از نوع رشته (string) بود. این بدین معنا است که وقتی از Entity Framework و Sql Server برای ذخیره داده‌های ASP.NET Identity استفاده می‌کنیم داده‌های این فیلد‌ها بعنوان nvarchar ذخیره می‌شوند. درباره این پیاده سازی پیش فرض در فروم هایی مانند سایت StackOverflow بسیار بحث شده است. و در آخر با در نظر گرفتن تمام بازخورد ها، تصمیم گرفته شد یک نقطه توسعه پذیری (extensibility) اضافه شود که توسط آن بتوان نوع فیلدهای اصلی را مشخص کرد. مثلا شاید بخواهید کلیدهای اصلی جداول Users و Roles از نوع int باشند. این نقطه توسعه پذیری مخصوصا هنگام مهاجرت داده‌های قبلی بسیار مفید است، مثلا ممکن است دیتابیس قبلی فیلدهای UserId را با فرمت GUID ذخیره کرده باشد.

              اگر نوع فیلدهای کلید اصلی را تغییر دهید، باید کلاس‌های مورد نیاز برای Claims و Logins را هم اضافه کنید تا کلید اصلی معتبری دریافت کنند. قطعه کد زیر نمونه ای از نحوه استفاده این قابلیت برای تعریف کلیدهای int را نشان می‌دهد.
              de Snippet
              publicclassApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
              {
              }
              publicclassCustomRole : IdentityRole<int, CustomUserRole>
              {
                  public CustomRole() { }
                  public CustomRole(string name) { Name = name; }
              }
              publicclassCustomUserRole : IdentityUserRole<int> { }
              publicclassCustomUserClaim : IdentityUserClaim<int> { }
              publicclassCustomUserLogin : IdentityUserLogin<int> { }
               
              publicclassApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
              {
              }

              پشتیبانی از IQueryable روی Users و Roles

              کلاس‌های UserStore و RoleStore حالا از IQueryable پشتیبانی می‌کنند، بنابراین می‌توانید براحتی لیست کاربران و نقش‌ها را کوئری کنید.
              بعنوان مثال قطعه کد زیر دریافت لیست کاربران را نشان می‌دهد. از همین روش برای دریافت لیست نقش‌ها از RoleManager می‌توانید استفاده کنید.
              //
              // GET: /Users/
              public async Task<ActionResult> Index()
              {
                  return View(await UserManager.Users.ToListAsync());
              }

              پشتیبانی از عملیات Delete از طریق UserManager

              در نسخه 1.0 اگر قصد حذف یک کاربر را داشتید، نمی‌توانستید این کار را از طریق UserManager انجام دهید. اما حالا می‌توانید مانند قطعه کد زیر عمل کنید.
              var user = await UserManager.FindByIdAsync(id);
              
              if (user == null)
              {
                  return HttpNotFound();
              }
              
              var result = await UserManager.DeleteAsync(user);

              میان افزار UserManagerFactory

              شما می‌توانید با استفاده از یک پیاده سازی Factory،  وهله ای از UserManager را از OWIN context دریافت کنید. این الگو مشابه چیزی است که برای گرفتن AuthenticationManager در OWIN context استفاده می‌کنیم.  این الگو همچنین روش توصیه شده برای گرفتن یک نمونه از UserManager به ازای هر درخواست در اپلیکیشن است.
              قطعه کد زیر نحوه پیکربندی این میان افزار در فایل StartupAuth.cs را نشان می‌دهد.
              // Configure the UserManager
              app.UseUserManagerFactory(newUserManagerOptions<ApplicationUserManager>()
              {
                  DataProtectionProvider = app.GetDataProtectionProvider(),
                  Provider = newUserManagerProvider<ApplicationUserManager>()
                  {
                      OnCreate = ApplicationUserManager.Create
                  }
              });
              و برای گرفتن یک وهله از UserManager:
              HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();

              میان افزار DbContextFactory

              سیستم ASP.NET Identity از Entity Framework برای ذخیره داده هایش در Sql Server استفاده می‌کند. بدین منظور، ASP.NET Identity کلاس ApplicationDbContext را رفرنس می‌کند. میان افزار DbContextFactory به ازای هر درخواست در اپلیکیشن یک وهله از ApplicationDbContext را به شما تحویل می‌دهد.
              می توانید پیکربندی لازم را در StartupAuth.cs انجام دهید.
              app.UseDbContextFactory(ApplicationDbContext.Create);

              Samples

              امکانات جدید را می‌توانید در پروژه https://aspnet.codeplex.com پیدا کنید. لطفا به پوشه Identity در سورس کد مراجعه کنید. برای اطلاعاتی درباره نحوه اجرای پروژه هم فایل readme را بخوانید.

              برای مستندات ASP.NET Identity 1.0 هم به  http://www.asp.net/identity  سر بزنید. هنوز مستنداتی برای نسخه 2.0 منتشر نشده، اما بزودی با انتشار نسخه نهایی مستندات و مثال‌های جدیدی به سایت اضافه خواهند شد.

              Known Issues
              در کنار قابلیت‌های جدیدی مانند Account Confirmation و Password Reset، دو خاصیت جدید به کلاس IdentityUser اضافه شده اند: 'Email' و 'IsConfirmed'. این تغییرات الگوی دیتابیسی که توسط ASP.NET Identity 1.0 ساخته شده است را تغییر می‌دهد. بروز رسانی پکیج‌ها از نسخه 1.0 به 2.0 باعث می‌شود که اپلیکیشن شما دیگر قادر به دسترسی به دیتابیس عضویت نباشد، چرا که مدل دیتابیس تغییر کرده. برای بروز رسانی الگوی دیتابیس می‌توانید از Code First Migrations استفاده کنید.

              نکته: نسخه جدید به EntityFramework 6.1.0-alpha1 وابستگی دارد، که در همین تاریخ (20 دسامبر 2013) پیش نمایش شد.  http://blogs.msdn.com/b/adonet/archive/2013/12/20/ef-6-1-alpha-1-available.aspx

              EntityFramework 6.1.0-alpha1 بروز رسانی هایی دارد که سناریوی مهاجرت در ASP.NET Identity را تسهیل می‌کند، به همین دلیل از نسخه جدید EF استفاده شده. تیم ASP.NET هنوز باگ‌های زیادی را باید رفع کند و قابلیت‌های جدیدی را هم باید پیاده سازی کند. بنابراین پیش از نسخه نهایی RTM شاهد پیش نمایش‌های دیگری هم خواهیم بود که در ماه‌های آتی منتشر می‌شوند.

              برای اطلاعات بیشتر درباره آینده ASP.NET Identity به لینک زیر سری بزنید.
              مسیرراه‌ها
              SQL Server
              آخرین تاریخ بروزرسانی 93/10/21


              SQL Server 2005

              SQL Server 2008

              SQL Server 2012

              SQL Serve 2014


              مطالب
              پیاده سازی یک تامین کننده MySQL برای ASP.NET Identity
              در این مقاله جایگزینی پیاده سازی پیش فرض ASP.NET Identity را بررسی می‌کنیم. در ادامه خواهید خواند:

              • جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
              • تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
              • ایجاد یک دیتابیس MySQL روی Windows Azure
              • نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
              • نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
              در انتهای این مقاله یک اپلیکیشن ASP.NET MVC خواهیم داشت که از ASP.NET Identity و تامین کننده سفارشی جدید استفاده می‌کند. دیتابیس اپلیکیشن MySQL خواهد بود و روی Windows Azure میزبانی می‌شود. سورس کد کامل این مثال را هم می‌توانید از این لینک دریافت کنید.


              پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity

              ASP.NET Identity سیستم توسعه پذیری است که می‌توانید بخش‌های مختلف آن را جایگزین کنید.در این سیستم بناهای سطح بالایی مانند Managers و Stores وجود دارند.
              Managers کلاس‌های سطح بالایی هستند که توسعه دهندگان از آنها برای اجرای عملیات مختلف روی ASP.NET Identity استفاده می‌کنند. مدیریت کننده‌های موجود عبارتند از UserManager و RoleManager. کلاس UserManager برای اجرای عملیات مختلف روی کاربران استفاده می‌شود، مثلا ایجاد کاربر جدید یا حذف آنها. کلاس RoleManager هم برای اجرای عملیات مختلف روی نقش‌ها استفاده می‌شود.

              Stores کلاس‌های سطح پایین‌تری هستند که جزئیات پیاده سازی را در بر می‌گیرند، مثلا اینکه موجودیت‌های کاربران و نقش‌ها چگونه باید ذخیره و بازیابی شوند. این کلاس‌ها با مکانیزم ذخیره و بازیابی تلفیق شده اند. مثلا Microsoft.AspNet.Identity.EntityFramework کلاسی با نام UserStore دارد که برای ذخیره و بازیابی User‌ها و داده‌های مربوطه توسط EntityFramework استفاده می‌شود.

              Managers از Stores تفکیک شده اند و هیچ وابستگی ای به یکدیگر ندارند. این تفکیک بدین منظور انجام شده که بتوانید مکانیزم ذخیره و بازیابی را جایگزین کنید، بدون اینکه اپلیکیشن شما از کار بیافتد یا نیاز به توسعه بیشتر داشته باشد. کلاس‌های Manager می‌توانند با هر Store ای ارتباط برقرار کنند. از آنجا که شما از API‌های سطح بالای UserManager برای انجام عملیات CRUD روی کاربران استفاده می‌کنید، اگر UserStore را با پیاده سازی دیگری جایگزین کنید، مثلا AzureTable Storage یا MySql، نیازی به بازنویسی اپلیکیشن نیست.

              در مثال جاری پیاده سازی پیش فرض Entity Framework را با یک  تامین کننده MySQL جایگزین می‌کنیم.

              پیاده سازی کلاس‌های Storage
              برای پیاده سازی تامین کننده‌های سفارشی، باید کلاس هایی را پیاده سازی کنید که همتای آنها در Microsoft.AspNet.Identity.EntityFramework وجود دارند:
              • <UserStore<TUser
              • IdentityUser
              • <RoleStore<TRole
              • IdentityRole
              پیاده سازی پیش فرض Entity Framework را در تصاویر زیر مشاهده می‌کنید.
              Users

              Roles

              در مخزن پیش فرض ASP.NET Identity EntityFramework کلاس‌های بیشتری برای موجودیت‌ها مشاهده می‌کنید.

              • IdentityUserClaim
              • IdentityUserLogin
              • IdentityUserRole
              همانطور که از نام این کلاس‌ها مشخص است، اختیارات، نقش‌ها و اطلاعات ورود کاربران توسط این کلاس‌ها معرفی می‌شوند. در مثال جاری این کلاس‌ها را پیاده سازی نخواهیم کرد، چرا که بارگذاری اینگونه رکوردها از دیتابیس به حافظه برای انجام عملیات پایه (مانند افزودن و حذف اختیارات کاربران) سنگین است. در عوض کلاس‌های backend store اینگونه عملیات را بصورت مستقیم روی دیتابیس اجرا خواهند کرد. بعنوان نمونه متد ()UserStore.GetClaimsAsync را در نظر بگیرید. این متد به نوبه خود متد (userClaimTable.FindByUserId(user.Id را فراخوانی می‌کند که یک کوئری روی جدول مربوطه اجرا می‌کند و لیستی از اختیارات کاربر را بر می‌گرداند.
              public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
              {
                  ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
                  return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
              }
              برای پیاده سازی یک تامین کننده سفارشی MySQL مراحل زیر را دنبال کنید.
              1. کلاس کاربر را ایجاد کنید، که اینترفیس IUser را پیاده سازی می‌کند.
              public class IdentityUser : IUser
              {
                  public IdentityUser(){...}
              
                  public IdentityUser(string userName) (){...}
              
                  public string Id { get; set; }
              
                  public string UserName { get; set; }
              
                  public string PasswordHash { get; set; }
              
                  public string SecurityStamp { get; set; }
              }
              2. کلاس User Store را ایجاد کنید، که اینترفیس‌های IUserStore, IUserClaimStore, IUserLoginStore, IUserRoleStore و IUserPasswordStore را پیاده سازی می‌کند. توجه کنید که تنها اینترفیس IUserStore را باید پیاده سازی کنید، مگر آنکه بخواهید از امکاناتی که دیگر اینترفیس‌ها ارائه می‌کنند هم استفاده کنید.
              public class UserStore : IUserStore<IdentityUser>,
                                       IUserClaimStore<IdentityUser>,
                                       IUserLoginStore<IdentityUser>,
                                       IUserRoleStore<IdentityUser>,
                                       IUserPasswordStore<IdentityUser>
              {
                  public UserStore(){...}
              
                  public Task CreateAsync(IdentityUser user){...}
              
                  public Task<IdentityUser> FindByIdAsync(string userId){...}   
              ...
              }
              3. کلاس Role را ایجاد کنید که اینترفیس IRole را پیاده سازی می‌کند.
              public class IdentityRole : IRole
              {
                  public IdentityRole(){...}
              
                  public IdentityRole(string roleName) (){...}
              
                  public string Id { get; set; }
              
                  public string Name { get; set; }
              }
              4. کلاس Role Store را ایجاد کنید که اینترفیس IRoleStore را پیاده سازی می‌کند. توجه داشته باشید که پیاده سازی این مخزن اختیاری است و در صورتی لازم است که بخواهید از نقش‌ها در سیستم خود استفاده کنید.
              public class RoleStore : IRoleStore<IdentityRole>                        
              {
                  public RoleStore(){...}
              
                  public Task CreateAsync(IdentityRole role){...}
              
                  public Task<IdentityRole> FindByIdAsync(string roleId){...}   
              ....
              }
              کلاس‌های بیشتری هم وجود دارند که مختص پیاده سازی مثال جاری هستند.
              • MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئری‌ها را کپسوله می‌کند. کلاس‌های UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی می‌شوند.
              • RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله می‌کند.
              • UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله می‌کند.
              • UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله می‌کند.
              • UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله می‌کند.
              • UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله می‌کند.

              ایجاد یک دیتابیس MySQL روی Windows Azure

              1. به پورتال مدیریتی Windows Azure وارد شوید.
              2. در پایین صفحه روی NEW+ کلیک کنید و گزینه STORE را انتخاب نمایید.

              در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.

              4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیک‌ترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.

              5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد می‌توانید از قسمت ADD-ONS آن را مدیریت کنید.

              6. همانطور که در تصویر بالا می‌بینید، می‌توانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.

              7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.


              ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL

              ابتدا ابزار MySQL Workbench را نصب کنید.
              1. ابزار مذکور را از اینجا دانلود کنید.
              2. هنگام نصب، گزینه Setup Type: Custom را انتخاب کنید.
              3. در قسمت انتخاب قابلیت ها، گزینه‌های Applications و MySQLWorkbench را انتخاب کنید و مراحل نصب را به اتمام برسانید.
              4. اپلیکیشن را اجرا کرده و روی MySQLConnection کلیک کنید تا رشته اتصال جدیدی تعریف کنید. رشته اتصالی که در مراحل قبل از Azure MySQL Database کپی کردید را اینجا استفاده کنید. بعنوان مثال:
               Connection Name: AzureDB; Host Name: us-cdbr-azure-west-b.cleardb.com; Username: <username>; Password: <password>; Default Schema: IdentityMySQLDatabase 
              5. پس از برقراری ارتباط با دیتابیس، یک برگ Query جدید باز کنید. فرامین زیر را برای ایجاد جداول مورد نیاز کپی کنید.
              CREATE TABLE `IdentityMySQLDatabase`.`users` (
                `Id` VARCHAR(45) NOT NULL,
                `UserName` VARCHAR(45) NULL,
                `PasswordHash` VARCHAR(100) NULL,
                `SecurityStamp` VARCHAR(45) NULL,
                PRIMARY KEY (`id`));
              
              CREATE TABLE `IdentityMySQLDatabase`.`roles` (
                `Id` VARCHAR(45) NOT NULL,
                `Name` VARCHAR(45) NULL,
                PRIMARY KEY (`Id`));
              
              CREATE TABLE `IdentityMySQLDatabase`.`userclaims` (
                `Id` INT NOT NULL AUTO_INCREMENT,
                `UserId` VARCHAR(45) NULL,
                `ClaimType` VARCHAR(100) NULL,
                `ClaimValue` VARCHAR(100) NULL,
                PRIMARY KEY (`Id`),
                FOREIGN KEY (`UserId`)
                  REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);
              
              CREATE TABLE `IdentityMySQLDatabase`.`userlogins` (
                `UserId` VARCHAR(45) NOT NULL,
                `ProviderKey` VARCHAR(100) NULL,
                `LoginProvider` VARCHAR(100) NULL,
                FOREIGN KEY (`UserId`)
                  REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);
              
              CREATE TABLE `IdentityMySQLDatabase`.`userroles` (
                `UserId` VARCHAR(45) NOT NULL,
                `RoleId` VARCHAR(45) NOT NULL,
                PRIMARY KEY (`UserId`, `RoleId`),
                FOREIGN KEY (`UserId`)
                  REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) 
              on delete cascade
              on update cascade,
                FOREIGN KEY (`RoleId`)
                  REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`)
              on delete cascade
              on update cascade);
              6. حالا تمام جداول لازم برای ASP.NET Identity را در اختیار دارید، دیتابیس ما MySQL است و روی Windows Azure میزبانی شده.


              ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider

              2. در گوشه سمت راست پایین صفحه روی دکمه Download Zip کلیک کنید تا کل پروژه را دریافت کنید.
              3. محتوای فایل دریافتی را در یک پوشه محلی استخراج کنید.
              4. پروژه AspNet.Identity.MySQL را باز کرده و آن را کامپایل (build) کنید.
              5. روی نام پروژه کلیک راست کنید و گزینه Add, New Project را انتخاب نمایید. پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را به IdentityMySQLDemo تغییر دهید.

              6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.

              7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگی‌ها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیج‌ها را حذف می‌کنیم.

              8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.

              9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.

              10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.

              11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.

              12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.

              public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase())))
              {
              
              }

              13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.

              <add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />

              مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.


              اجرای اپلیکیشن و اتصال به دیتابیس MySQL

              1. روی پروژه IdentityMySQLDemo کلیک راست کرده و Set as Startup Project را انتخاب کنید.
              2. اپلیکیشن را با Ctrl + F5 کامپایل و اجرا کنید.
              3. در بالای صفحه روی Register کلیک کنید.
              4. حساب کاربری جدیدی بسازید.

              5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.

              6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.

              برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.

              مطالب
              خودکار کردن تعاریف DbSetها در EF Code first
              پیشنیاز:
              تعریف نوع جنریک به صورت متغیر

              مطلبی را چندی قبل در مورد نحوه خودکار کردن افزودن کلاس‌های EntityTypeConfiguration به modelBuilder در این سایت مطالعه کردید. در مطلب جاری به خودکار سازی تعاریف مرتبط با DbSetها خواهیم پرداخت.
              ابتدا مثال کامل زیر را درنظر بگیرید:
              using System;
              using System.Data.Entity;
              using System.Data.Entity.Migrations;
              using System.Linq;
              using System.Reflection;
              
              namespace MyNamespace
              {
                  public abstract class BaseEntity
                  {
                      public int Id { set; get; }
                      public string CreatedBy { set; get; }
                  }
              
                  public class User : BaseEntity
                  {
                      public string Name { get; set; }
                  }
              
                  public class MyContext : DbContext
                  {
                      protected override void OnModelCreating(DbModelBuilder modelBuilder)
                      {
                          var asm = Assembly.GetExecutingAssembly();
                          loadEntities(asm, modelBuilder, "MyNamespace");
                      }
              
                      void loadEntities(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
                      {
                          var entityTypes = asm.GetTypes()
                                                  .Where(type => type.BaseType != null &&
                                                         type.Namespace == nameSpace &&
                                                         type.BaseType.IsAbstract &&
                                                         type.BaseType == typeof(BaseEntity))
                                                  .ToList();
              
                          var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
                          entityTypes.ForEach(type =>
                          {
                              entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
                          });
                      }
                  }
              
                  public class Configuration : DbMigrationsConfiguration<MyContext>
                  {
                      public Configuration()
                      {
                          AutomaticMigrationsEnabled = true;
                          AutomaticMigrationDataLossAllowed = true;
                      }
              
                      protected override void Seed(MyContext context)
                      {
                          context.Set<User>().Add(new User { Name = "name-1" });
                          context.Set<User>().Add(new User { Name = "name-2" });
                          context.Set<User>().Add(new User { Name = "name-3" });
                          base.Seed(context);
                      }
                  }
              
                  public static class Test
                  {
                      public static void RunTests()
                      {
                          Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
                          using (var context = new MyContext())
                          {
                              var user1 = context.Set<User>().Find(1);
                              if (user1 != null)
                                  Console.WriteLine(user1.Name);
                          }
                      }
                  }
              }
              توضیحات:
              همانطور که ملاحظه می‌کنید در این مثال خبری از تعاریف DbSetها نیست. به کمک Reflection تمام مدل‌های برنامه که از نوع کلاس پایه BaseEntity هستند (روشی مرسوم جهت مدیریت خواص تکراری مدل‌ها) یافت شده (در متد loadEntities) و سپس نتیجه حاصل به صورت پویا به متد جنریک Entity ارسال می‌شود. حاصل، افزوده شدن خودکار کلاس‌های مورد نظر به سیستم EF است.
              البته در این حالت چون دیگر کلاس‌های مدل‌ها در MyContext به صورت صریح تعریف نمی‌شوند، نحوه استفاده از آن‌ها را توسط متد Set، در متدهای RunTests و یا Seed، ملاحظه می‌کنید.