ข้อ จำกัด คีย์เฉพาะสำหรับหลายคอลัมน์ใน Entity Framework


244

ฉันใช้รหัสเอนทิตี Framework 5.0 ก่อน

public class Entity
 {
   [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public string EntityId { get; set;}
   public int FirstColumn  { get; set;}
   public int SecondColumn  { get; set;}
 }

ฉันต้องการผสมผสานระหว่างFirstColumnและSecondColumnเป็นเอกลักษณ์

ตัวอย่าง:

Id  FirstColumn  SecondColumn 
1       1              1       = OK
2       2              1       = OK
3       3              3       = OK
5       3              1       = THIS OK 
4       3              3       = GRRRRR! HERE ERROR

อย่างไรก็ตามมีการทำเช่นนั้น?

คำตอบ:


369

ด้วย Entity Framework 6.1 คุณสามารถทำสิ่งนี้ได้:

[Index("IX_FirstAndSecond", 1, IsUnique = true)]
public int FirstColumn { get; set; }

[Index("IX_FirstAndSecond", 2, IsUnique = true)]
public int SecondColumn { get; set; }

พารามิเตอร์ที่สองในแอตทริบิวต์เป็นที่ที่คุณสามารถระบุลำดับของคอลัมน์ในดัชนี
ข้อมูลเพิ่มเติม: MSDN


12
นี่เป็นข้อมูลที่ถูกต้องสำหรับคำอธิบายประกอบข้อมูล :) หากคุณต้องการคำตอบสำหรับการใช้ API อย่างคล่องแคล่วโปรดดูคำตอบของ Niaher ด้านล่างstackoverflow.com/a/25779348/2362036
tekiegirl

8
แต่ฉันต้องการมันทำงานกับกุญแจต่างประเทศ! คุณสามารถช่วยฉันได้ไหม?
feedc0de

2
@ 0xFEEDC0DE ดูคำตอบของฉันด้านล่างซึ่งกล่าวถึงการใช้คีย์ต่างประเทศในดัชนี
Kryptos

1
คุณสามารถโพสต์วิธีใช้ดัชนีนี้ด้วย linq ถึง sql ได้หรือไม่
Bluebaron

4
@JJS - ฉันได้มันไปทำงานที่หนึ่งในคุณสมบัติที่เป็นกุญแจต่างประเทศ .. โดยโอกาสใด ๆ ที่เป็นกุญแจสำคัญของคุณ varchar หรือ nvarchar? มีการจำกัดความยาวที่สามารถใช้เป็นคีย์ที่ไม่ซ้ำกันได้ .. stackoverflow.com/questions/2863993/…
เดฟลอเรนซ์

129

ฉันพบวิธีแก้ปัญหาสามวิธี

ดัชนีที่ไม่ซ้ำกันใน EntityFramework Core:

วิธีแรก:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Entity>()
   .HasIndex(p => new {p.FirstColumn , p.SecondColumn}).IsUnique();
}

วิธีที่สองในการสร้างข้อ จำกัด ที่ไม่ซ้ำกับ EF Core โดยใช้คีย์สำรอง

ตัวอย่าง

หนึ่งคอลัมน์:

modelBuilder.Entity<Blog>().HasAlternateKey(c => c.SecondColumn).HasName("IX_SingeColumn");

หลายคอลัมน์:

modelBuilder.Entity<Entity>().HasAlternateKey(c => new [] {c.FirstColumn, c.SecondColumn}).HasName("IX_MultipleColumns");

EF 6 และต่ำกว่า:


วิธีแรก:

dbContext.Database.ExecuteSqlCommand(string.Format(
                        @"CREATE UNIQUE INDEX LX_{0} ON {0} ({1})", 
                                 "Entitys", "FirstColumn, SecondColumn"));

วิธีนี้รวดเร็วและมีประโยชน์มาก แต่ปัญหาหลักคือ Entity Framework ไม่รู้อะไรเกี่ยวกับการเปลี่ยนแปลงเหล่านั้น!


วิธีที่สอง:
ฉันพบมันในโพสต์นี้ แต่ฉันไม่ได้ลองด้วยตัวเอง

CreateIndex("Entitys", new string[2] { "FirstColumn", "SecondColumn" },
              true, "IX_Entitys");

ปัญหาของวิธีนี้มีดังต่อไปนี้: มันต้องการ DbMigration ดังนั้นคุณจะทำอย่างไรถ้าคุณไม่มี


วิธีที่สาม:
ฉันคิดว่านี่เป็นวิธีที่ดีที่สุด แต่ต้องใช้เวลาพอสมควร ฉันจะแสดงให้คุณเห็นแนวคิดเบื้องหลัง: ในลิงก์นี้http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a คุณสามารถค้นหารหัสสำหรับคำอธิบายประกอบข้อมูลคีย์ที่ไม่ซ้ำกัน:

[UniqueKey] // Unique Key 
public int FirstColumn  { get; set;}
[UniqueKey] // Unique Key 
public int SecondColumn  { get; set;}

// The problem hier
1, 1  = OK 
1 ,2  = NO OK 1 IS UNIQUE

ปัญหาสำหรับแนวทางนี้ ฉันจะรวมพวกเขาได้อย่างไร ฉันมีความคิดที่จะขยายการใช้งานของ Microsoft เช่น:

[UniqueKey, 1] // Unique Key 
public int FirstColumn  { get; set;}
[UniqueKey ,1] // Unique Key 
public int SecondColumn  { get; set;}

ในภายหลังใน IDatabaseInitializer ตามที่อธิบายไว้ในตัวอย่างของ Microsoft คุณสามารถรวมคีย์ตามจำนวนเต็มที่กำหนด สิ่งหนึ่งที่จะต้องมีการจดบันทึกแม้ว่า: ถ้าคุณสมบัติที่เป็นเอกลักษณ์ของสตริงประเภทแล้วคุณจะต้องตั้งค่า MaxLength


1
(y) ฉันพบคำตอบนี้ดีกว่า อีกวิธีหนึ่งวิธีที่สามอาจไม่ดีที่สุด (ฉันชอบอันแรกจริง ๆ ) โดยส่วนตัวแล้วฉันไม่ต้องการให้มีสิ่งประดิษฐ์ของ EF เข้ามาในคลาสเอนทิตี้ของฉัน
Najeeb

1
อาจเป็นไปได้แนวทางที่สองควรเป็น: CREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})? (ดู BOL )
AK

2
คำถามงี่เง่า: ทำไมคุณเริ่มต้นชื่อทั้งหมดด้วย "IX_"
Bastien Vandamme

1
@BastienVandamme เป็นคำถามที่ดี ดัชนีสร้างอัตโนมัติโดย EF เริ่มต้นด้วย IX_ ดูเหมือนว่าจะเป็นการประชุมในดัชนี EF โดยค่าเริ่มต้นชื่อดัชนีจะเป็น IX_ {ชื่อคุณสมบัติ}
Bassam Alugili

1
ใช่มันควรจะเป็น ขอบคุณสำหรับการใช้ API ของ Fluent มีการขาดเอกสารที่ร้ายแรงเกี่ยวกับเรื่องนี้
JSON

75

หากคุณใช้รหัส - แรกคุณสามารถใช้ส่วนขยายที่กำหนดเองHasUniqueIndexAnnotation

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration.Configuration;

internal static class TypeConfigurationExtensions
{
    public static PrimitivePropertyConfiguration HasUniqueIndexAnnotation(
        this PrimitivePropertyConfiguration property, 
        string indexName,
        int columnOrder)
    {
        var indexAttribute = new IndexAttribute(indexName, columnOrder) { IsUnique = true };
        var indexAnnotation = new IndexAnnotation(indexAttribute);

        return property.HasColumnAnnotation(IndexAnnotation.AnnotationName, indexAnnotation);
    }
}

จากนั้นใช้งานเช่น:

this.Property(t => t.Email)
    .HasColumnName("Email")
    .HasMaxLength(250)
    .IsRequired()
    .HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 0);

this.Property(t => t.ApplicationId)
    .HasColumnName("ApplicationId")
    .HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 1);

ซึ่งจะส่งผลในการโยกย้ายนี้:

public override void Up()
{
    CreateIndex("dbo.User", new[] { "Email", "ApplicationId" }, unique: true, name: "UQ_User_EmailPerApplication");
}

public override void Down()
{
    DropIndex("dbo.User", "UQ_User_EmailPerApplication");
}

และท้ายที่สุดก็อยู่ในฐานข้อมูลเช่น:

CREATE UNIQUE NONCLUSTERED INDEX [UQ_User_EmailPerApplication] ON [dbo].[User]
(
    [Email] ASC,
    [ApplicationId] ASC
)

3
แต่นั่นเป็นดัชนีที่ไม่ จำกัด !
Roman Pokrovskij

3
ในบล็อคโค้ดที่สองของคุณ ( this.Property(t => t.Email)) บล็อกนั้นมีคลาสอะไร? (เช่นคืออะไรthis)
JoeBrockhaus

2
NVM EntityTypeConfiguration<T>
JoeBrockhaus

5
@RomanPokrovskij: ความแตกต่างระหว่างดัชนีที่ไม่ซ้ำกันและข้อ จำกัด ที่ไม่ซ้ำกันดูเหมือนจะเป็นเรื่องของวิธีการเก็บรักษาบันทึกไว้ใน SQL Server ดูtechnet.microsoft.com/en-us/library/aa224827%28v=sql.80%29.aspxสำหรับรายละเอียด
Mass Dot Net

1
@niaher ฉันขอขอบคุณวิธีการต่อที่ดีของคุณ
Mohsen Afshin

18

คุณต้องกำหนดคีย์ผสม

ด้วยคำอธิบายประกอบข้อมูลดูเหมือนว่านี้:

public class Entity
 {
   public string EntityId { get; set;}
   [Key]
   [Column(Order=0)]
   public int FirstColumn  { get; set;}
   [Key]
   [Column(Order=1)]
   public int SecondColumn  { get; set;}
 }

คุณยังสามารถทำสิ่งนี้กับ modelBuilder เมื่อแทนที่ OnModelCreating โดยการระบุ:

modelBuilder.Entity<Entity>().HasKey(x => new { x.FirstColumn, x.SecondColumn });

2
แต่กุญแจไม่ใช่ฉันแค่ต้องการให้มันเป็นเอกลักษณ์กุญแจควรเป็น Id หรือไม่? ฉันได้อัพเดทข้อความขอบคุณสำหรับความช่วยเหลือ!
Bassam Alugili

16

คำตอบจาก niaher ระบุว่าการใช้ API ได้อย่างคล่องแคล่วคุณต้องมีส่วนขยายที่กำหนดเองอาจถูกต้องในขณะที่เขียน ตอนนี้คุณสามารถ (EF core 2.1) ใช้ API ได้อย่างคล่องแคล่วดังต่อไปนี้:

modelBuilder.Entity<ClassName>()
            .HasIndex(a => new { a.Column1, a.Column2}).IsUnique();

9

เสร็จสิ้นการตอบ @chuck สำหรับการใช้ดัชนีคอมโพสิตที่มีปุ่มต่างประเทศ

คุณต้องกำหนดคุณสมบัติที่จะเก็บค่าของคีย์ต่างประเทศ จากนั้นคุณสามารถใช้คุณสมบัตินี้ภายในนิยามดัชนี

ตัวอย่างเช่นเรามี บริษัท ที่มีพนักงานและมีเพียงข้อ จำกัด เฉพาะ (ชื่อ บริษัท ) สำหรับพนักงานทุกคน:

class Company
{
    public Guid Id { get; set; }
}

class Employee
{
    public Guid Id { get; set; }
    [Required]
    public String Name { get; set; }
    public Company Company  { get; set; }
    [Required]
    public Guid CompanyId { get; set; }
}

ตอนนี้การแมปของคลาสพนักงาน:

class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap ()
    {
        ToTable("Employee");

        Property(p => p.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        Property(p => p.Name)
            .HasUniqueIndexAnnotation("UK_Employee_Name_Company", 0);
        Property(p => p.CompanyId )
            .HasUniqueIndexAnnotation("UK_Employee_Name_Company", 1);
        HasRequired(p => p.Company)
            .WithMany()
            .HasForeignKey(p => p.CompanyId)
            .WillCascadeOnDelete(false);
    }
}

โปรดทราบว่าฉันยังใช้ส่วนขยาย @niaher สำหรับคำอธิบายประกอบดัชนีที่ไม่ซ้ำกัน


1
ในตัวอย่างนี้คุณมีทั้ง บริษัท และ CompanyId ซึ่งหมายความว่าผู้โทรสามารถเปลี่ยนได้ แต่ไม่ใช่ผู้อื่นและมีเอนทิตีที่มีข้อมูลไม่ถูกต้อง
LosManos

1
@LosManos คุณกำลังพูดถึงผู้โทรคนไหน? คลาสแสดงข้อมูลในฐานข้อมูล การเปลี่ยนค่าผ่านการสืบค้นจะทำให้มั่นใจได้ว่ามีความสอดคล้อง ขึ้นอยู่กับสิ่งที่แอปพลิเคชันไคลเอนต์ของคุณสามารถทำได้คุณอาจต้องใช้การตรวจสอบ แต่นั่นไม่ใช่ขอบเขตของ OP
Kryptos

4

ในคำตอบที่ยอมรับโดย @chuck มีความคิดเห็นที่บอกว่าจะไม่ทำงานในกรณีของ FK

มันใช้งานได้สำหรับฉันกรณีของ EF6 .Net4.7.2

public class OnCallDay
{
     public int Id { get; set; }
    //[Key]
    [Index("IX_OnCallDateEmployee", 1, IsUnique = true)]
    public DateTime Date { get; set; }
    [ForeignKey("Employee")]
    [Index("IX_OnCallDateEmployee", 2, IsUnique = true)]
    public string EmployeeId { get; set; }
    public virtual ApplicationUser Employee{ get; set; }
}

เวลานาน. สมมติว่ามันใช้งานได้ไม่นาน! ขอบคุณสำหรับการอัปเดตโปรดเพิ่มความคิดเห็นใน @chuck answer ฉันคิดว่าชัคเป็นเวลานานไม่ได้ใช้ดังนั้น
Bassam Alugili

คุณสมบัติ EmployeeID ที่นี่ต้องการแอททริบิวเพื่อจำกัดความยาวของมันเพื่อให้ถูกทำดัชนีหรือไม่? สร้างขึ้นด้วย VARCHAR (MAX) ซึ่งไม่สามารถมีดัชนีได้หรือไม่ เพิ่มแอตทริบิวต์ [StringLength (255)] ไปยัง EmployeeID
Lord Darth Vader

EmployeeID เป็น GUID กวดวิชาจำนวนมากแนะนำให้แมป GUID กับสตริงแทนที่จะเป็น guid ฉันไม่รู้ว่าทำไม
dalios

3

ฉันคิดว่าคุณต้องการEntityIdเป็นคีย์หลักเสมอดังนั้นการแทนที่ด้วยคีย์คอมโพสิตไม่ใช่ตัวเลือก (ถ้าเพียงเพราะคอมโพสิตคีย์นั้นซับซ้อนกว่าที่จะทำงานด้วยและเพราะมันไม่สมเหตุสมผลมากที่จะมีคีย์หลักที่มีความหมายเช่นกัน ในตรรกะทางธุรกิจ)

อย่างน้อยที่สุดคุณควรทำคือสร้างคีย์เฉพาะบนทั้งสองฟิลด์ในฐานข้อมูลและตรวจสอบข้อยกเว้นการละเมิดคีย์ที่ไม่ซ้ำเมื่อทำการบันทึกการเปลี่ยนแปลง

นอกจากนี้คุณสามารถ (ควร) ตรวจสอบค่าที่ไม่ซ้ำก่อนบันทึกการเปลี่ยนแปลง วิธีที่ดีที่สุดในการทำเช่นนั้นก็คือการAny()สืบค้นเพราะมันจะลดปริมาณการถ่ายโอนข้อมูล:

if (context.Entities.Any(e => e.FirstColumn == value1 
                           && e.SecondColumn == value2))
{
    // deal with duplicate values here.
}

ระวังว่าการตรวจสอบนี้เพียงอย่างเดียวไม่เพียงพอ มีเวลาแฝงอยู่ระหว่างการตรวจสอบและการส่งมอบจริงดังนั้นคุณจะต้องมีข้อ จำกัด ที่เป็นเอกลักษณ์ + การจัดการข้อยกเว้น


3
ขอบคุณ @GertArnold สำหรับคำตอบ แต่ฉันไม่ต้องการที่จะตรวจสอบความไม่ซ้ำกันในชั้นธุรกิจนี้เป็นงานฐานข้อมูลและสิ่งนี้จะต้องทำในฐานข้อมูล!
Bassam Alugili

2
ตกลงติดกับดัชนีที่ไม่ซ้ำกันแล้ว แต่คุณจะต้องจัดการกับการละเมิดดัชนีในเลเยอร์ธุรกิจ แต่อย่างใด
Gert Arnold

1
จากภายนอกเมื่อฉันได้รับข้อยกเว้นเช่นนี้ฉันจะตรวจจับและอาจรายงานข้อผิดพลาดและทำลายกระบวนการหรือปิดแอปพลิเคชัน
Bassam Alugili

3
ใช่ ... ฉันต้องตอบกลับหรือไม่ โปรดจำไว้ว่าฉันไม่รู้ใบสมัครของคุณฉันไม่สามารถบอกคุณได้ว่าวิธีที่ดีที่สุดคือการจัดการกับข้อยกเว้นเหล่านี้เฉพาะที่คุณต้องจัดการกับพวกเขา
Gert Arnold

2
ระวังข้อ จำกัด ที่ไม่ซ้ำกับ DB ด้วย EF ถ้าคุณทำสิ่งนี้และจากนั้นคุณก็จบลงด้วยการปรับปรุงที่ flip-flop ค่าของหนึ่งในคอลัมน์ที่เป็นส่วนหนึ่งของคีย์ที่ไม่ซ้ำกันนิติบุคคล frameowkk จะล้มเหลวในการบันทึกถ้าคุณเพิ่มชั้นการทำธุรกรรมทั้งหมด ตัวอย่างเช่น: วัตถุหน้ามีชุดย่อยขององค์ประกอบ แต่ละองค์ประกอบมี SortOrder คุณต้องการคอมโบของ PageID และ SortOrder ให้ไม่ซ้ำกัน ส่วนหน้าผู้ใช้ flip flops ลำดับขององค์ประกอบที่มี sortorder 1 และ 2 Entity Framework จะล้มเหลวในการบันทึก b / c มันพยายามที่จะปรับปรุง sortorders ทีละครั้ง
EGP

1

เมื่อเร็ว ๆ นี้เพิ่มคีย์คอมโพสิตที่มีเอกลักษณ์ของ 2 คอลัมน์โดยใช้วิธีที่แนะนำ 'ชัค' ขอขอบคุณ @chuck วิธีนี้ทำให้ฉันดูสะอาดกว่า:

public int groupId {get; set;}

[Index("IX_ClientGrouping", 1, IsUnique = true)]
public int ClientId { get; set; }

[Index("IX_ClientGrouping", 2, IsUnique = true)]
public int GroupName { get; set; }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.