Ràng buộc khóa duy nhất cho nhiều cột trong Entity Framework


243

Tôi đang sử dụng Mã Entity Framework 5.0 trước;

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

Tôi muốn kết hợp giữa FirstColumnSecondColumnlà duy nhất.

Thí dụ:

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

Có cách nào để làm điều đó?

Câu trả lời:


368

Với Entity Framework 6.1, bây giờ bạn có thể làm điều này:

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

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

Tham số thứ hai trong thuộc tính là nơi bạn có thể chỉ định thứ tự của các cột trong chỉ mục.
Thêm thông tin: MSDN


12
Điều này đúng với chú thích dữ liệu :), nếu bạn muốn câu trả lời cho việc sử dụng API trôi chảy, hãy xem câu trả lời của Niaher bên dưới stackoverflow.com/a/25779348/2362036
tekiegirl

8
Nhưng tôi cần nó làm việc cho các khóa ngoại! Bạn có thể giúp tôi được không?
feedc0de

2
@ 0xFEEDC0DE xem câu trả lời của tôi bên dưới giải quyết việc sử dụng khóa ngoại trong các chỉ mục.
Kryptos

1
Bạn có thể đăng làm thế nào để sử dụng chỉ số này với linq để sql?
Bluebaron

4
@JJS - Tôi đã làm cho nó hoạt động trong đó một trong các thuộc tính là khóa ngoại .. có khi nào khóa của bạn là varchar hay nvarchar không? Có giới hạn về độ dài có thể được sử dụng làm khóa duy nhất .. stackoverflow.com/questions/2863993/ Kẻ
Dave Lawrence

129

Tôi tìm thấy ba cách để giải quyết vấn đề.

Các chỉ mục duy nhất trong EntityFramework Core:

Cách tiếp cận đầu tiên:

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

Cách tiếp cận thứ hai để tạo các ràng buộc duy nhất với EF Core bằng cách sử dụng các phím thay thế.

Ví dụ

Một cột:

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

Nhiều cột:

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

EF 6 trở xuống:


Cách tiếp cận đầu tiên:

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

Cách tiếp cận này rất nhanh và hữu ích nhưng vấn đề chính là Entity Framework không biết gì về những thay đổi đó!


Cách tiếp cận thứ hai:
Tôi đã tìm thấy nó trong bài viết này nhưng tôi đã không tự mình thử.

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

Vấn đề của phương pháp này là như sau: Nó cần DbMigration, vậy bạn sẽ làm gì nếu không có nó?


Cách tiếp cận thứ ba:
Tôi nghĩ rằng đây là cách tốt nhất nhưng nó cần một chút thời gian để làm điều đó. Tôi sẽ chỉ cho bạn ý tưởng đằng sau nó: Trong liên kết này http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a bạn có thể tìm thấy mã cho chú thích dữ liệu khóa duy nhất:

[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

Vấn đề cho phương pháp này; Làm thế nào tôi có thể kết hợp chúng? Tôi có một ý tưởng để mở rộng triển khai Microsoft này chẳng hạn:

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

Sau này trong IDatabaseInitializer như được mô tả trong ví dụ của Microsoft, bạn có thể kết hợp các khóa theo số nguyên đã cho. Một điều cần phải lưu ý: Nếu thuộc tính duy nhất là kiểu chuỗi thì bạn phải đặt MaxLạng.


1
(y) Tôi thấy câu trả lời này tốt hơn. Một điều nữa, cách tiếp cận thứ ba có thể không nhất thiết là tốt nhất. (Tôi thực sự thích cái đầu tiên.) Cá nhân tôi thích không có bất kỳ tạo tác EF nào được mang vào các lớp thực thể của tôi.
Najeeb

1
Có thể, cách tiếp cận thứ hai nên là : CREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})? (xem BOL )
AK

2
Câu hỏi ngu ngốc: Tại sao bạn bắt đầu tất cả tên của mình bằng "IX_"?
Bastien Vandamme

1
@BastienVandamme đó là một câu hỏi hay. chỉ số tự động tạo bởi EF bắt đầu bằng IX_. Nó dường như là một quy ước trong chỉ mục EF theo mặc định, tên chỉ mục sẽ là IX_ {tên thuộc tính}.
Bassam Alugili

1
Có nó nên được. Cảm ơn bạn đã triển khai API thành thạo. Thiếu tài liệu nghiêm trọng về vấn đề này
JSON

75

Nếu bạn đang sử dụng Code-First , bạn có thể triển khai tiện ích mở rộng tùy chỉnh HasUnique IndexAnnotation

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);
    }
}

Sau đó sử dụng nó như vậy:

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);

Điều này sẽ dẫn đến việc di chuyển này:

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");
}

Và cuối cùng kết thúc trong cơ sở dữ liệu như:

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

3
Nhưng đó là chỉ số không ràng buộc!
Roman Pokrovskij

3
Trong khối mã thứ hai của bạn ( this.Property(t => t.Email)), lớp chứa gì? (tức là: cái gì this)
JoeBrockhaus

2
nvm. EntityTypeConfiguration<T>
JoeBrockhaus

5
@RomanPokrovskij: Sự khác biệt giữa một chỉ mục duy nhất và một ràng buộc duy nhất dường như là vấn đề về cách các bản ghi của nó được duy trì trong SQL Server. Xem technet.microsoft.com/en-us/l Library / aa224827% 28v = sql.80% 29.aspx để biết chi tiết.
Mass Dot Net

1
@niaher Tôi đánh giá cao phương pháp mở rộng tốt đẹp của bạn
Mohsen Afshin

18

Bạn cần xác định một khóa tổng hợp.

Với chú thích dữ liệu, nó trông như thế này:

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;}
 }

Bạn cũng có thể làm điều này với modelBuilder khi ghi đè OnModelCreating bằng cách chỉ định:

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

2
Nhưng khóa không phải là khóa tôi chỉ muốn chúng là Khóa duy nhất phải là Id? Tôi đã cập nhật các câu hỏi cảm ơn để được giúp đỡ!
Bassam Alugili

16

Câu trả lời từ niaher nói rằng để sử dụng API trôi chảy, bạn cần một tiện ích mở rộng tùy chỉnh có thể đã chính xác tại thời điểm viết. Bây giờ bạn có thể (lõi EF 2.1) sử dụng API thông thạo như sau:

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

9

Hoàn thành câu trả lời @chuck để sử dụng các chỉ số tổng hợp với khóa ngoại .

Bạn cần xác định một thuộc tính sẽ giữ giá trị của khóa ngoại. Sau đó, bạn có thể sử dụng thuộc tính này trong định nghĩa chỉ mục.

Ví dụ: chúng tôi có công ty với nhân viên và chỉ có chúng tôi có một ràng buộc duy nhất về (tên, công ty) cho bất kỳ nhân viên nào:

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; }
}

Bây giờ ánh xạ của lớp Nhân viên:

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);
    }
}

Lưu ý rằng tôi cũng đã sử dụng phần mở rộng @niaher cho chú thích chỉ mục duy nhất.


1
Trong ví dụ này, bạn có cả Company và CompanyId. Điều này có nghĩa là người gọi có thể thay đổi cái này nhưng không phải cái khác và có một thực thể với dữ liệu không chính xác.
LosManos

1
@LosManos Bạn đang nói về người gọi nào? Lớp đại diện cho dữ liệu trong cơ sở dữ liệu. Thay đổi giá trị thông qua các truy vấn sẽ đảm bảo tính nhất quán. Tùy thuộc vào những gì ứng dụng khách có thể làm, bạn có thể cần thực hiện kiểm tra nhưng đó không phải là phạm vi của OP.
Kryptos

4

Trong câu trả lời được chấp nhận bởi @chuck, có một bình luận nói rằng nó sẽ không hoạt động trong trường hợp của FK.

nó hoạt động với tôi, trường hợp của 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; }
}

Thời gian dài. Hãy nói rằng nó đã được làm việc trước thời gian dài! cảm ơn bạn đã cập nhật xin vui lòng thêm một bình luận cho câu trả lời @chuck. Tôi nghĩ Chuck trước đây không sử dụng SO.
Bassam Alugili

Có thuộc tính EmployeeID Ở đây cần một thuộc tính để giới hạn độ dài của nó để được lập chỉ mục không? Khác được tạo bằng VARCHAR (MAX) mà không thể có chỉ mục? Thêm thuộc tính [Chuỗi dài (255)] vào EmployeeID
Lord Darth Vader

EmployeeID là một GUID. Nhiều hướng dẫn đề xuất ánh xạ GUID thành chuỗi thay vì hướng dẫn, tôi không biết tại sao
dalios

3

Tôi giả sử bạn luôn muốn EntityId là khóa chính, vì vậy thay thế nó bằng khóa tổng hợp không phải là một tùy chọn (nếu chỉ vì các phím tổng hợp phức tạp hơn nhiều để làm việc vì nó không hợp lý khi có các khóa chính cũng có ý nghĩa trong logic kinh doanh).

Ít nhất bạn nên làm là tạo một khóa duy nhất trên cả hai trường trong cơ sở dữ liệu và kiểm tra cụ thể các trường hợp ngoại lệ vi phạm khóa duy nhất khi lưu thay đổi.

Ngoài ra, bạn có thể (nên) kiểm tra các giá trị duy nhất trước khi lưu thay đổi. Cách tốt nhất để làm điều đó là bằng một Any()truy vấn, bởi vì nó giảm thiểu lượng dữ liệu được truyền:

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

Coi chừng rằng kiểm tra này không bao giờ là đủ. Luôn có một số độ trễ giữa kiểm tra và cam kết thực tế, vì vậy bạn sẽ luôn cần ràng buộc duy nhất + xử lý ngoại lệ.


3
Cảm ơn @GertArnold cho câu trả lời nhưng tôi không muốn xác thực tính duy nhất trên lớp doanh nghiệp, đây là một công việc cơ sở dữ liệu và điều này sẽ được thực hiện trong cơ sở dữ liệu!
Bassam Alugili

2
OK, dính vào chỉ số duy nhất rồi. Nhưng dù sao bạn cũng sẽ phải đối phó với các vi phạm chỉ mục trong lớp kinh doanh.
Gert Arnold

1
từ bên ngoài khi tôi nhận được loại ngoại lệ này, tôi sẽ nắm bắt và có thể báo cáo lỗi và phá vỡ quy trình hoặc tắt ứng dụng.
Bassam Alugili

3
Phải, ... tôi có cần phải đáp lại không? Hãy nhớ rằng tôi không biết gì về ứng dụng của bạn, tôi không thể nói cho bạn biết cách tốt nhất để đối phó với những ngoại lệ này, chỉ bạn phải đối phó với chúng.
Gert Arnold

2
Hãy cảnh giác với các ràng buộc duy nhất của DB với EF. Nếu bạn làm điều này và sau đó bạn sẽ có một bản cập nhật lật các giá trị của một trong các cột là một phần của khóa duy nhất, thực thể frameowkr sẽ thất bại khi lưu trừ khi bạn thêm toàn bộ lớp giao dịch. Ví dụ: Đối tượng trang có một tập hợp các phần tử con. Mỗi phần tử có SortOrder. Bạn muốn kết hợp của PageID và SortOrder là duy nhất. Ở giao diện người dùng, người dùng sẽ lật thứ tự các phần tử với sắp xếp 1 và 2. Khung thực thể sẽ không lưu được b / c khi nó cố gắng cập nhật sắp xếp từng phần một.
EGP

1

Gần đây đã thêm một khóa tổng hợp với tính duy nhất của 2 cột bằng cách sử dụng phương pháp 'chuck' được đề xuất, cảm ơn @chuck. Chỉ có điều này tiếp cận trông sạch sẽ hơn với tôi:

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; }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.