Entity Framework의 여러 열에 대한 고유 키 제약 조건


243

Entity Framework 5.0 Code First를 사용하고 있습니다.

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를 사용하기위한 답변을 원한다면 stackoverflow.com/a/25779348/2362036
— tekiegirl

8
그러나 외래 키에서 작동해야합니다! 도와주세요?
— feedc0de

2
@ 0xFEEDC0DE 인덱스에서 외래 키 사용을 다루는 아래 답변을 참조하십시오.
— Kryptos

1
이 인덱스를 linq와 함께 SQL에 사용하는 방법을 게시 할 수 있습니까?
— Bluebaron

4
@JJS-속성 중 하나가 외래 키 인 곳에서 작동하도록했습니다. 어쩌면 키가 varchar 또는 nvarchar입니까? 고유 키로 사용할 수있는 길이에는 제한이 있습니다 .. stackoverflow.com/questions/2863993/…
— Dave Lawrence

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

나중에 Microsoft 예제에 설명 된대로 IDatabaseInitializer에서 지정된 정수에 따라 키를 결합 할 수 있습니다. 한 가지 주목할 점은 다음과 같습니다. 고유 속성이 문자열 유형 인 경우 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_ {property name}입니다.
— Bassam Alugili

1
그렇습니다. Fluent API 구현에 감사드립니다. 이것에 대한 문서가 부족하다
— JSON

75

Code-First를 사용 하는 경우 사용자 정의 확장 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;}
 }

다음을 지정하여 OnModelCreating을 재정의 할 때 modelBuilder로이를 수행 할 수도 있습니다.

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

2
그러나 나는 키가 Id이어야하는 고유 한 키가 아닌 키가 아닌가? 도움을 요청 해 주셔서 감사합니다.
— Bassam Alugili

16

유창한 API를 사용하려면 사용자 정의 확장이 필요하다는 niaher의 답변은 작성 당시에 정확했을 수 있습니다. 이제 (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; }
}

이제 Employee 클래스의 매핑 :

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
이 예에서는 Company 및 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; }
}

장기. 오래 전에 작동했다고 말합시다! 업데이트 주셔서 감사합니다 @ 척 답변에 의견을 추가하십시오. 나는 척이 오래 전에 SO를 사용하지 않는다고 생각합니다.
— Bassam Alugili

EmployeeID Here 특성이 색인화되기 위해 길이를 제한하는 속성이 필요합니까? 인덱스를 가질 수없는 VARCHAR (MAX)로 작성하지 않았습니까? EmployeeID에 속성 [StringLength (255)] 추가
— 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
EF의 DB 고유 제약 조건에주의하십시오. 이 작업을 수행 한 다음 고유 키의 일부인 열 중 하나의 값을 플립 플롭하는 업데이트가 발생하면 전체 트랜잭션 계층을 추가하지 않으면 엔티티 frameowkr이 저장되지 않습니다. 예를 들어 : Page 객체에는 하위 요소 컬렉션이 있습니다. 각 요소에는 SortOrder가 있습니다. PageID와 SortOrder의 콤보를 고유하게 만들고 싶습니다. 프론트 엔드에서 사용자 플립은 정렬 순서 1과 2를 가진 요소의 순서를 퍼 룹니다. Entity Framework는 정렬 순서를 한 번에 하나씩 업데이트하려고 시도하는 저장 b / c에 실패합니다.
— EGP

1

최근 'chuck'이 권장하는 접근 방식을 사용하여 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.