Entity Framework'teki birden çok sütun için benzersiz Anahtar kısıtlamaları


244

Önce Entity Framework 5.0 kodu kullanıyorum;

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

Kombinasyonu arasında FirstColumnve SecondColumnbenzersiz olarak yapmak istiyorum .

Misal:

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

Bunu yapacak bir şey var mı?

Yanıtlar:


369

Entity Framework 6.1 ile artık şunları yapabilirsiniz:

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

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

Öznitelikteki ikinci parametre, dizindeki sütunların sırasını belirleyebileceğiniz yerdir.
Daha fazla bilgi: MSDN


12
Bu, veri ek açıklamaları için geçerlidir :), akıcı API'yi kullanmak için cevap istiyorsanız, aşağıdaki Niaher'ın cevabına bakın stackoverflow.com/a/25779348/2362036
tekiegirl

8
Ama yabancı anahtarlar için çalışmasına ihtiyacım var! Bana yardım eder misiniz?
feedc0de

2
@ 0xFEEDC0DE indekslerde yabancı anahtarların kullanımını ele alan cevabımı aşağıda görebilirsiniz.
Kriptolar

1
Bu dizini linq to sql ile nasıl kullanacağınızı gönderebilir misiniz?
Aralık'ta Bluebaron

4
@JJS - Ben özelliklerinden biri yabancı bir anahtar nerede çalışmak için aldım .. herhangi bir olasılıkla anahtar varchar veya nvarchar mı? Uzunluğu benzersiz bir anahtar olarak kullanılabilecek bir sınır var .. stackoverflow.com/questions/2863993/…
Dave Lawrence

129

Sorunu çözmek için üç yol buldum.

EntityFramework Çekirdeğindeki benzersiz dizinler:

İlk yaklaşım:

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

Alternatif Anahtarlar kullanarak EF Core ile Benzersiz Kısıtlamalar oluşturmak için ikinci yaklaşım .

Örnekler

Bir sütun:

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

Birden çok sütun:

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

EF 6 ve altı:


İlk yaklaşım:

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

Bu yaklaşım çok hızlı ve kullanışlıdır, ancak asıl sorun Entity Framework'ün bu değişiklikler hakkında hiçbir şey bilmemesidir!


İkinci yaklaşım:
Bu yazıda buldum ama kendim denemedim.

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

Bu yaklaşımın sorunu şudur: DbMigration gerekir, bu nedenle yoksa ne yaparsınız?


Üçüncü yaklaşım:
Bence bu en iyisi ama bunu yapmak için biraz zaman gerekiyor. Sadece arkasındaki fikri göstereceğim: Bu bağlantıda http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a benzersiz anahtar veri ek açıklamasının kodunu bulabilirsiniz:

[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

Bu yaklaşımın sorunu; Bunları nasıl birleştirebilirim? Örneğin, bu Microsoft uygulamasını genişletmek için bir fikrim var:

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

Daha sonra Microsoft örneğinde açıklandığı gibi IDatabaseInitializer içinde anahtarları verilen tamsayıya göre birleştirebilirsiniz. Yine de dikkat edilmesi gereken bir nokta var: unique özelliği string türündeyse, MaxLength ayarlamanız gerekir.


1
(y) Bu cevabı daha iyi buluyorum. Başka bir şey, üçüncü yaklaşım her zaman en iyisi olmayabilir. (Aslında birincisini seviyorum.) Şahsen varlık sınıflarımda herhangi bir EF yapısının taşınmamasını tercih ediyorum.
Najeeb

1
Muhtemelen, ikinci yaklaşım olmalıdır: CREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})? (bkz. BOL )
AK

2
Aptalca soru: Neden bütün isminize "IX_" ile başlıyorsunuz?
Bastien Vandamme

1
@BastienVandamme iyi bir soru. EF otomatik oluşturma dizini IX_ ile başlar. EF dizininde varsayılan olarak bir kural gibi görünüyor, dizin adı IX_ {özellik adı} olacaktır.
Bassam Alugili

1
Evet olmalı. Akıcı API uygulaması için teşekkürler. Bu konuda ciddi bir belge eksikliği var
JSON

75

Code-First kullanıyorsanız , HasUniqueIndexAnnotation özel uzantısını uygulayabilirsiniz

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

Sonra şöyle kullanın:

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

Bu da bu taşıma işlemiyle sonuçlanacak:

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

Ve sonunda veritabanına şu şekilde çıkın:

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

3
Ama bu dizin kısıtlama değil!
Roman Pokrovskij

3
İkinci kod bloğunuzda ( this.Property(t => t.Email)), sınıfı içeren nedir? (yani: nedir this)
JoeBrockhaus

2
nvm. EntityTypeConfiguration<T>
JoeBrockhaus

5
@RomanPokrovskij: Benzersiz bir dizin ile benzersiz bir kısıtlama arasındaki fark, SQL Server'da kayıtlarının nasıl tutulduğu meselesi gibi görünüyor. Ayrıntılar için technet.microsoft.com/en-us/library/aa224827%28v=sql.80%29.aspx adresine bakın.
Mass Dot Net

1
@niaher Güzel uzatma yönteminizi takdir ediyorum
Mohsen Afshin

18

Bileşik bir anahtar tanımlamanız gerekir.

Veri ek açıklamalarıyla şöyle görünür:

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'i geçersiz kılarken aşağıdakileri belirterek modelBuilder ile de yapabilirsiniz:

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

2
Ama sadece benzersiz olarak istediğim anahtarlar değil Anahtar kimliği olmalı? Yardım için soruları güncelledim!
Bassam Alugili

16

Akıcı API'yı kullanmak için özel bir uzantıya ihtiyacınız olduğunu belirten cevabın, yazı yazılırken doğru olabileceği belirtildi. Artık (EF core 2.1) akıcı API'yı aşağıdaki gibi kullanabilirsiniz:

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

9

Kullanmak için @chuck cevabı tamamlanması kompozit indeksleri ile yabancı anahtarlar .

Yabancı anahtarın değerini tutacak bir özellik tanımlamanız gerekir. Daha sonra bu özelliği dizin tanımının içinde kullanabilirsiniz.

Örneğin, çalışanları olan bir şirketimiz var ve sadece herhangi bir çalışan için benzersiz bir kısıtlama (isim, şirket) var:

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

Şimdi Çalışan sınıfının haritası:

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

Benzersiz dizin ek açıklaması için @niaher uzantısını da kullandığımı unutmayın.


1
Bu örnekte hem Company hem de CompanyId var. Bu, arayan kişinin diğerini değiştirebileceği ve hatalı veriye sahip bir varlığı olabileceği anlamına gelir.
17'de LosManos

1
@LosManos Hangi arayandan bahsediyorsun? Sınıf, bir veritabanındaki verileri temsil eder. Sorgularla değerin değiştirilmesi tutarlılığı sağlayacaktır. İstemci uygulamasının yapabileceklerine bağlı olarak, kontrolleri uygulamanız gerekebilir, ancak bu OP'nin kapsamı değildir.
Kriptolar

4

@Chuck tarafından kabul edilen cevapta, FK durumunda işe yaramayacağını söyleyen bir yorum var.

EF6 davası benim için çalıştı.

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

Uzun zaman. uzun zaman önce çalıştığını varsayalım! güncelleme için teşekkürler @ chuck cevap için bir yorum ekleyin. Bence Chuck uzun zaman önce SO kullanmıyor.
Bassam Alugili

EmployeeID Burada özellik dizine eklenecek için uzunluğunu sınırlamak için bir özniteliğe mi ihtiyacınız var? Başka bir dizin olamaz VARCHAR (MAX) ile oluşturulan? Attribute [StringLength (255)] 'a EmployeeID
Lord Darth Vader

Çalışan kimliği bir GUID'dir. Birçok öğretici guid yerine dize GUID eşlemek için önermek, neden bilmiyorum
dalios

3

Her zaman EntityIdbirincil anahtar olmak istediğinizi varsayalım , bu yüzden bir kompozit anahtarla değiştirmek bir seçenek değildir (sadece kompozit anahtarlarla çalışmak çok daha karmaşık olduğu ve aynı zamanda anlamı olan birincil anahtarlara sahip olmanın çok mantıklı olmadığı için) iş mantığında).

Yapmanız gereken en az şey, veritabanındaki her iki alanda benzersiz bir anahtar oluşturmak ve değişiklikleri kaydederken özellikle benzersiz anahtar ihlali istisnalarını kontrol etmektir.

Ayrıca, değişiklikleri kaydetmeden önce benzersiz değerleri kontrol edebilirsiniz (yapmalısınız). Bunu yapmanın en iyi yolu Any(), aktarılan veri miktarını en aza indirdiği için bir sorgudur:

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

Bu kontrolün tek başına asla yeterli olmadığına dikkat edin. Çek ve fiili taahhüt arasında her zaman bir gecikme vardır, bu nedenle her zaman benzersiz kısıtlama + istisna işlemeye ihtiyacınız olacaktır.


3
Cevap için @GertArnold teşekkürler ama iş katmanı üzerindeki tekliği doğrulamak istemiyorum bu bir veritabanı işi ve bu veritabanında yapılacaktır!
Bassam Alugili

2
Tamam, benzersiz dizine sadık kalın. Ancak, yine de iş katmanındaki dizin ihlalleriyle uğraşmanız gerekecek.
Gert Arnold

1
Bu tür bir istisna aldığımda dışarıdan yakaladım ve belki de hatayı bildirip işlemi bozabilir veya uygulamayı kapatabilirim.
Bassam Alugili

3
Evet, ... buna cevap vermem gerekiyor mu? Başvurunuzun şey bilmiyorum hatırla, ben sadece en iyi yolu, bu özel başa ne olduğunu söyleyemem o onlarla uğraşmak zorunda.
Gert Arnold

2
EF ile benzersiz DB kısıtlamalarına karşı dikkatli olun. Bunu yaparsanız ve sonra benzersiz anahtarın bir parçası olan sütunlardan birinin değerlerini ters çeviren bir güncellemeye sahip olursanız, tüm işlem katmanı eklemediğiniz sürece varlık frameowkr kayıtta başarısız olur. Örneğin: Page nesnesinin bir Elements öğesi koleksiyonu vardır. Her öğede SortOrder bulunur. PageID ve SortOrder birleşiminin benzersiz olmasını istiyorsunuz. Ön uçta, sortorder 1 ve 2 ile öğelerin kullanıcı flip flop sırası. Entity Framework, sortorderları birer birer güncellemeye çalıştığı b / c'yi kaydedemez.
EGP

1

Son zamanlarda 'chuck' önerilen yaklaşımı kullanarak 2 sütun benzersiz bir kompozit anahtar ekledi, teşekkürler @chuck. Sadece bu yaklaşım bana daha temiz görünüyordu:

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; }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.