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.