Entity Frameworkの複数の列に対する一意のキー制約


243

私は最初にEntity 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;}
 }

私は間の組み合わせを作りたいFirstColumnSecondColumnユニークとして。

例:

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

とにかくそれをすることはありますか?

回答:


368

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

属性の2番目のパラメーターは、インデックス内の列の順序を指定できる場所です。
詳細:MSDN


12
あなたは以下のNiaherの答えを参照流暢なAPIを使用するための答えをしたい場合、これは、:)データ注釈のための正しいstackoverflow.com/a/25779348/2362036
tekiegirl

8
しかし、私はそれが外部キーに対して機能する必要があります!手伝って頂けますか?
feedc0de 2015

2
@ 0xFEEDC0DEインデックスでの外部キーの使用に対処する以下の私の回答を参照してください。
Kryptos、2015年

1
このインデックスをlinq to sqlで使用する方法を投稿できますか?
Bluebaron

4
@JJS-プロパティの1つが外部キーである場所で機能しました。たぶん、あなたのキーはvarcharまたはnvarcharですか?一意のキーとして使用できる長さに制限があります。.stackoverflow.com/questions/2863993
Dave Lawrence

129

私は問題を解決する3つの方法を見つけました。

EntityFramework Coreの一意のインデックス:

最初のアプローチ:

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

第二のアプローチ代替キーを使用してEF Coreで一意の制約を作成する。

1列:

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がこれらの変更について何も認識していないことです。


2番目のアプローチ:
この投稿で見つけましたが、自分で試したわけではありません。

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

このアプローチの問題は次のとおりです。DbMigrationが必要なので、それがない場合はどうしますか?


3番目のアプローチ:
これは最良の方法だと思いますが、それを行うには時間がかかります。その背後にあるアイデアをお見せします。このリンク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で、指定した整数に従ってキーを組み合わせることができます。ただし、1つ注意する必要があります。一意のプロパティが文字列型の場合は、MaxLengthを設定する必要があります。


1
(y)私はこの答えをよりよく見つけます。もう1つは、3番目のアプローチが必ずしも最良であるとは限りません。(私は最初のものが好きです。)個人的には、EFアーティファクトがエンティティークラスに持ち越されないようにしています。
Najeeb 2017

1
おそらく、2番目のアプローチは次のようになりますCREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})。(BOLを参照 )
AK

2
愚かな質問:なぜあなたはすべての名前を「IX_」で始めるのですか?
バスティアンヴァンダム

1
@BastienVandammeいい質問ですね。EFによる自動生成インデックスは、IX_で始まります。デフォルトではEFインデックスの規則のようですが、インデックス名はIX_ {プロパティ名}になります。
バッサムアルギリ

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
2番目のコードブロック(this.Property(t => t.Email))では、それを含むクラスは何ですか?(つまり:とは何かthis
JoeBrockhaus、2015

2
nvm。EntityTypeConfiguration<T>
JoeBrockhaus、2015

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 2013

16

ナイアハーからの返答は、流れるようなAPIを使用するにはカスタム拡張が必要であると書いている時点では正しい可能性があると述べています。これで(EFコア2.1)次のようにFluent 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のスコープではありません。
クリプトス2017年

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回答にコメントを追加してください。チャックはSOを使わないうちに久しぶりだと思います。
Bassam Alugili

プロパティEmployeeID Hereには、インデックスを作成するためにその長さを制限する属性が必要ですか?そうでなければ、インデックスを持つことができないVARCHAR(MAX)で作成されますか?属性[StringLength(255)]をEmployeeIDに追加
ロードダースベイダー

EmployeeIDはGUIDです。多くのチュートリアルでは、GUIDをGUIDではなく文字列にマップすることを提案しています。理由は
わかりませ

3

私はあなたが常にEntityId主キーになりたいと思っているので、それを複合キーに置き換えることはオプションではありません(複合キーは操作がはるかに複雑であり、主キーにも意味があることはあまり意味がないためです)ビジネスロジックで)。

少なくとも、データベースの両方のフィールドに一意のキーを作成し、変更を保存するときに、一意のキー違反の例外を確認する必要があります。

さらに、変更を保存する前に、一意の値を確認する必要があります(すべきです)。これを行う最善の方法はAny()、転送データの量を最小限に抑えるため、クエリによる方法です。

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

このチェックだけでは十分ではないことに注意してください。チェックと実際のコミットの間には常に遅延があるため、常に一意の制約+例外処理が必要になります。


3
答えてくれて@GertArnoldに感謝しますが、ビジネスレイヤーの一意性を検証したくないので、これはデータベースジョブであり、これはデータベースで行われます。
Bassam Alugili 2013

2
では、一意のインデックスを使用してください。しかし、とにかくビジネス層でのインデックス違反に対処する必要があります。
Gert Arnold

1
外部からこの種の例外を受け取ったときにキャッチし、エラーを報告してプロセスを中断するか、アプリケーションをシャットダウンします。
Bassam Alugili 2013

3
はい、...私はそれに応答する必要がありますか?私は最善の方法は唯一、これらの例外に対処するために何であるかを伝えることはできません、私はあなたのアプリケーションの何も知らない覚えていることをあなたがそれらに対処する必要があります。
Gert Arnold

2
EFのDB固有の制約に注意してください。これを行った後、一意のキーの一部である列の1つの値をフリップフロップする更新を行う場合、トランザクションレイヤー全体を追加しない限り、エンティティframeowkrは保存に失敗します。たとえば、Pageオブジェクトには、要素の子コレクションがあります。各要素にはSortOrderがあります。PageIDとSortOrderの組み合わせを一意にする必要があります。フロントエンドでは、ユーザーが並べ替え順序1および2の要素の順序を逆にします。EntityFrameworkは、並べ替え順序を一度に1つずつ更新しようとするb / cの保存に失敗します。
EGP 2014年

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.