شروع به کار با 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، این سه مورد اهمیتی ندارند و بیشتر روشن‌تر شدن مقصود، هدف اصلی آن‌ها است.
  • #
    ‫۷ سال و ۱۰ ماه قبل، شنبه ۲۲ آبان ۱۳۹۵، ساعت ۱۹:۲۳
    چطور میشود sequenceی نوشت بدین صورت که فرض کنید مدلی داریم با سه فیلد Id , ForeignKeyId , SomeSequence که میخواهیم بر مبنای ForeignKeyId یک عدد یکتا برای SomeSequence ایجاد شود.
    یعنی با ForeignKeyId = 1 مقدار‌های SomeSequence برابر 1و2و3و... و 
    با ForeignKeyId = 2 مقدار‌های SomeSequenceی مجزا از قبلی و برابر با 1و2و3و... ساخته شود.
    به شکل زیر. ممنون
     SomeSequence  ForeignKeyId  Id
     1  1  1
     2  1  2
     1  2  3
     2  2  4
    • #
      ‫۷ سال و ۱۰ ماه قبل، شنبه ۲۲ آبان ۱۳۹۵، ساعت ۲۳:۳۱
      برای این حالت خاص (تولید sequence بر اساس مقادیر یک ستون دیگر) باید از window functions استفاده کرد:
      CREATE TABLE #Table1
      (
         [Id] [int] IDENTITY(1,1) NOT NULL,
         [ForeignKeyId] [int] NOT NULL,
      )
      
      Insert Into #Table1 VALUES
      (1), (1), (1), (2), (2), (2), (3), (3), (3)
      
      Select [Id], [ForeignKeyId] ,
        ROW_NUMBER() OVER(PARTITION BY [ForeignKeyId] ORDER BY [ForeignKeyId]) [SomeSequence]
       from #Table1
      
      Drop table #Table1
      با این خروجی:


      برای استفاده‌ی آن در EF Core، عبارت ذیل را
      ROW_NUMBER() OVER(PARTITION BY [ForeignKeyId] ORDER BY [ForeignKeyId]) [SomeSequence]
      مانند مثال HasComputedColumnSql ذکر شده‌ی در مطلب فوق، استفاده کنید.
    • #
      ‫۶ سال و ۱ ماه قبل، شنبه ۲۰ مرداد ۱۳۹۷، ساعت ۱۳:۴۹
      نمونه ای از نحوه پیاده سازی ROW_NUMBER در EF:
      using System;
      using System.Linq;
       
      public class Test
      {
      public static void Main()
      {
      var beatles = (new[]
                  {
                      new {id = 1, inst = "guitar", name = "john"},
                      new {id = 2, inst = "guitar", name = "george"},
                      new {id = 3, inst = "guitar", name = "paul"},
                      new {id = 4, inst = "drums", name = "ringo"},
                      new {id = 5, inst = "drums", name = "pete"}
                  });
       
                  var result = beatles
                      .GroupBy(g => g.inst)
                      .Select(c => c.OrderBy(o => o.id).Select((v, i) => new { i, v }).ToList())
                      .SelectMany(c => c)
                      .Select(c => new { c.v.id, c.v.inst, c.v.name, rn = c.i + 1 })
                      .ToList();
       
                  Console.WriteLine("id | inst \t| name  \t| rn");
                  foreach (var row in result)
                  {
                      Console.WriteLine($"{row.id}  | {row.inst}\t| {row.name}  \t| {row.rn}");
                  }     
      }
      }
      با این خروجی:
      id | inst | name  | rn
      1  | guitar| john  | 1
      2  | guitar| george  | 2
      3  | guitar| paul  | 3
      4  | drums| ringo  | 1
      5  | drums| pete  | 2
  • #
    ‫۴ سال و ۸ ماه قبل، دوشنبه ۲۳ دی ۱۳۹۸، ساعت ۰۱:۰۶
    در ادامه مبحث Sequence ‌گفته شده  به این روش الگوریتم Hi-Low هم می‌گویند.
    ایا منظور شما اینه که به Sequence ‌الگوریتم Hi-Lo میگویند؟ مگر نه اینکه در Hi-Lo  آیدی‌ها قبل از Insert در دیتابیس مقدار میگیرند؛ ولی در Sequence که قبل از Insert شدن، آیدی‌ها مقدار نمیگیرند؟
    آیا منظور شما متفاوت بوده یا بنده اشتباه برداشت کرده ام؟
    • #
      ‫۴ سال و ۸ ماه قبل، دوشنبه ۲۳ دی ۱۳۹۸، ساعت ۰۳:۰۸
      زمانی که وهله جدیدی از یک موجودیت توسط متد Add یا ترجیحا در این شرایط خاص با استفاده از متد AddAsync به Context اضافه شود، برای مقدار دهی شناسه آن، یک کوئری برای دریافت مقدار بعدی از Sequence معرفی شده اجرا خواهد شد. این فرآیند قبل از فراخوانی SaveChanges می‌باشد. از الگوریتم HiLo در اینجا برای کاهش تعداد کوئری‌ها برای دریافت شناسه تک تک وهله‌ها و رفع یکسری مشکلات دیگر که در استفاده مستقیم از Sequence‌ها وجود دارد، کمک گرفته شده است. 
      • #
        ‫۴ سال و ۷ ماه قبل، سه‌شنبه ۲۴ دی ۱۳۹۸، ساعت ۲۰:۰۵
        ممنون اقای ربال عزیز
        در تکمیل پاسخ و رفع ابهامی که برای خودم پیش اومده بود باید بگم استفاده از Sequence در حالت عادی باعث استفاده از الگوریتم HiLo نمی‌شود مگر زمانی که در کنار متد UseHiLo (متد قبلی آن ForSqlServerUseSequenceHiLo منسوخ شده است) به کاربرده شود.
        طرز کار به این صورت است توسط کد زیر
        entity.Property(e => e.SequenceId).HasDefaultValueSql("NEXT VALUE FOR [OrderNumbers]")
        میتوان مقدار Sequence تعریف شده (OrderNumbers) را بر روی یک خاصیت خاص (مثلا SequenceId) معرفی کرد و مقدار این خاصیت به صورت خودکار از مقدار بعدی Sequence تعریف شده پر خواهد شد، ولی نه قبل از Insert بلکه بعد از آن (بعد از متد SaveChanges)، پس به خودی خود از HiLo استفاده نمیکند.
        در عوض قابلیت HiLo این امکان را می‌دهد یک خاصیت قبل از Insert شدن (بعد از فراخوانی متد Add و قبل از SaveChanges) از دیتابیس مقدار بگیرد.
        برای استفاده از این حالت، توسط متد UseHiLo و تعیین نام Sequence تعریف شده، مقدار فیلد قبل از Insert شدن از دیتابیس واکشی می‌شود
        modelBuilder.HasSequence<int>("OrderNumbers").StartsAt(1000).IncrementsBy(5);
        modelBuilder.UseHiLo("OrderNumbers");
        اما چند نکته در این بین وجود دارد :
        1- فراخوانی متد UseHiLo بر روی modelBuilder باعث میشود تمام خواصی که به صورت ValueGenerated.OnAdd (توسط [DatabaseGenerated(DatabaseGeneratedOption.Identity)] یا متد ValueGeneratedOnAdd) نشانه گذاری شده اند اعمال شود، نه یک پروپرتی خاص
        2- جهت اعمال HiLo بر روی یک پروپرتی خاص باید به صورت زیر عمل کنید
        entity.Property(p => p.SequenceId).UseHiLo("OrderNumbers");

        3- اگر نام Sequence ایی که به متد UseHiLo پاس داده می‌شود وجود داشته باشد از همان استفاده می‌کند، در غیر این صورت یک Sequence جدید با آن نام و مقدار پیشفرض StartsAt برابر 0 و IncrementsBy برابر 10 ایجاد میکند.
        4- زمانی که از HiLo استفاده می‌شود در اولین درخواست Add به دیتابیس، مقدار بازه Sequence توسط دستور (SELECT NEXT VALUE FOR [OrderNumbers]) واکشی شده و هم برای آن entity و هم برای entity‌های بعدی، مادامی که بازه Sequence به آخر نرسد، دیگر Select ایی به دیتابیس زده نمی‌شود و مقادیر فیلد‌ها از بین بازه Sequence به صورت خودکار پر می‌شود.
        مثلا اگر یک Sequence با StartAt برابر با 1000 و IncrementsBy برابر با 5 باشد، در اولین Add مقدار Sequence را خوانده (مثلا 1000 است) سپس تا 5 entity بعدی مقدار فیلد‌ها را " یکی یکی" و نه "5تا 5تا" مقدار دهی میکند (مثلا 1000، 1001، 1002، 1003، 1004) سپس برای entity شش ام، مجددا به دیتابیس درخواست زده و مقدار بعدی Sequence را واکشی میکند (مثلا مقدار بعدی می‌شود 1005) و ادامه ماجرا ...
        مقدار بازه Sequence-based HiLo کش می‌شود و با Dispose شدن DbContext، صفر نخواهد شد و از بین نخواهد رفت، پس نگرانی ایی بابت طول عمر Scoped بودن DbContext در بحث DI وجود نخواهد داشت.

    • #
      ‫۴ سال و ۸ ماه قبل، دوشنبه ۲۳ دی ۱۳۹۸، ساعت ۰۳:۳۳
      تنظیم یک Sequence به صورت زیر
      modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared")
                 .StartsAt(1000).IncrementsBy(5);
      که از عدد 1000 شروع شده و واحد افزایش آن 5 است، سبب تولید یک CREATE SEQUENCE جدید در بانک اطلاعاتی می‌شود و به صورت مستقل با کوئری select next value for shared.OrderNumbers، قابل بازیابی است. هر بار فراخوانی این کوئری سبب تولید اعداد 1000، 1005 و ... می‌شود. یعنی قبل از insert، بازه‌ی مورد استفاده و اعداد آن کاملا مشخص است. بعد یکی از روش‌های استفاده از این Sequence، مقدار دهی Id جدول است:
      modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
           {
             entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
           });
      و به صورت زیر جهت تعریف جدول مورد استفاده قرار می‌گیرد:
      CREATE TABLE PrimaryKeyWithSequence
      (
          ID int PRIMARY KEY CLUSTERED
              DEFAULT (NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]),
          Field1 nvarchar(300) NULL
      ) ;
      این یک روش مقدار دهی Id در سمت دیتابیس با یک سری اعداد مشخص است.
      اما برای حالت batch و مشخص بودن Id آن‌ها، از همین روش با متد خاص ForSqlServerUseSequenceHiLo استفاده می‌شود:
      modelbuilder.HasSequence<int>("DBSequenceHiLo")
                        .StartsAt(1000).IncrementsBy(5);
      modelbuilder.ForSqlServerUseSequenceHiLo("DBSequenceHiLo");
      که یک چنین SEQUENCE ای را ایجاد می‌کند:
      CREATE SEQUENCE [dbo].[DBSequenceHiLo] 
       AS [int]
       START WITH 1000
       INCREMENT BY 5
       MINVALUE -2147483648
       MAXVALUE 2147483647
       CACHE 
      GO
      بعد در این حالت با داشتن یک چنین insertهایی :
      using (var dataContext = new SampleDBContext())
      {
          dataContext.Categories.Add(new Category() { CategoryName = "Clothing" });
          dataContext.Categories.Add(new Category() { CategoryName = "Footwear" });
          dataContext.Categories.Add(new Category() { CategoryName = "Accessories" });
          dataContext.SaveChanges();
          dataContext.Products.Add(new Product() { ProductName = "TShirts" });
          dataContext.Products.Add(new Product() { ProductName = "Shirts" });
          dataContext.Products.Add(new Product() { ProductName = "Causal Shoes" });
          dataContext.SaveChanges();
      }
      ابتدا یکبار Idهای مورد نیاز سه insert اول، دریافت می‌شوند:

      و سپس کوئری insert اصلی، دارای idهای از پیش تعیین شده‌ای می‌شود که اینبار نیازی نیست تا یکی یکی رکوردها insert شوند و id بعدی محاسبه شود:

  • #
    ‫۲ سال و ۴ ماه قبل، چهارشنبه ۷ اردیبهشت ۱۴۰۱، ساعت ۱۳:۲۱
    ساده شدن امکان تعریف ایندکس‌ها با Attributes از EF-Core 5x

    ویژگی جدید Index که در اسمبلی Microsoft.EntityFrameworkCore.Abstractions واقع شده‌است، امکان تعریف انواع و اقسام ایندکس‌ها را میسر می‌کند. این ویژگی باید به خود کلاس اعمال شود و نه تک تک خواص. چند مثال:
    الف) تعریف ایندکس بر روی خاصیت Url یک کلاس
    [Index(nameof(Url))]
    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
    }
    که امکان تعریف نام سفارشی آن نیز میسر است:
    [Index(nameof(Url), Name = "Index_Url")]

    ب) ایندکس‌های ترکیبی
    [Index(nameof(FirstName), nameof(LastName))]
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    ج) ایندکس‌های منحصربفرد
    [Index(nameof(Url), IsUnique = true)]
    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
    }