Unikalne ograniczenia klucza dla wielu kolumn w ramach encji

Najpierw używam kodu 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;}
 }

Chcę, aby kombinacja pomiędzy FirstColumn i SecondColumn była unikalna.

Przykład:

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
Czy w ogóle można to zrobić?
Author: Leniel Maccaferri, 2013-09-19

9 answers

Z Entity Framework 6.1, możesz teraz zrobić to:

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

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

Drugi parametr w atrybucie to miejsce, w którym można określić kolejność kolumn w indeksie.
Więcej informacji: MSDN

 385
Author: charlie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-06 17:05:01

Znalazłem trzy sposoby rozwiązania problemu.

Unikalne indeksy w rdzeniu EntityFramework:

Pierwsze podejście:

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

Drugie podejście do tworzenia unikalnych ograniczeń za pomocą rdzenia EF za pomocą kluczy alternatywnych.

Przykłady

Jedna kolumna:

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

Wiele kolumn:

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

EF 6 i poniżej:


Pierwsze podejście:

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

To podejście jest bardzo szybkie i przydatne, ale głównym problemem jest to, że Entity Framework nie wie nic o tych zmianach!


Drugie podejście:
Znalazłem go w tym poście, ale nie próbowałem sam.

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

Problem tego podejścia jest następujący: wymaga DbMigration więc co zrobić, jeśli go nie masz?


Trzecie podejście:
Myślę, że ten jest najlepszy, ale wymaga trochę czasu, aby to zrobić. Pokażę Ci, co za tym stoi.: W ten link http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a możesz znaleźć kod do unikalnej adnotacji kluczowych danych:

[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

Problem dla tego podejścia; Jak mogę je połączyć? Mam pomysł na rozszerzenie tej implementacji Microsoftu na przykład:

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

Później w IDatabaseInitializer, jak opisano w przykładzie Microsoft, można połączyć klucze zgodnie z podaną liczbą całkowitą. Należy jednak zauważyć jedną rzecz: jeśli unikalna właściwość jest wpisz string następnie musisz ustawić MaxLength.

 158
Author: Bassam Alugili,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-10 12:20:59

Jeśli używasz kodu-First, możesz zaimplementować niestandardowe rozszerzenie 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);
    }
}

Następnie użyj go tak:

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

Co spowoduje migrację:

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

I ostatecznie kończy się w bazie jako:

CREATE UNIQUE NONCLUSTERED INDEX [UQ_User_EmailPerApplication] ON [dbo].[User]
(
    [Email] ASC,
    [ApplicationId] ASC
)
 77
Author: niaher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-04 16:33:11

ODPOWIEDŹ niahera stwierdzająca, że aby korzystać z fluent API, potrzebujesz niestandardowego rozszerzenia, mogła być poprawna w momencie pisania tego tekstu. Możesz teraz (EF core 2.1) używać fluent API w następujący sposób:

modelBuilder.Entity<ClassName>()
            .HasIndex(a => new { a.Column1, a.Column2}).IsUnique();
 23
Author: GilShalit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-16 07:35:14

Musisz zdefiniować klucz złożony.

Z adnotacjami do danych wygląda to tak:

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

Możesz to również zrobić za pomocą modelBuilder, gdy nadpisujesz OnModelCreating, podając:

modelBuilder.Entity<Entity>().HasKey(x => new { x.FirstColumn, x.SecondColumn });
 20
Author: Admir Tuzović,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-19 08:52:50

Uzupełnianie odpowiedzi @ chuck za używanie indeksów złożonych z kluczami obcymi .

Musisz zdefiniować właściwość, która będzie zawierać wartość klucza obcego. Następnie można użyć tej właściwości wewnątrz definicji indeksu.

Na przykład, mamy firmę z pracownikami i tylko my mamy unikalne ograniczenie (nazwa, firma) dla każdego pracownika:

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

Teraz odwzorowanie klasy pracownika:

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

Zauważ, że używałem również rozszerzenia @ niaher dla unikalna adnotacja indeksu.

 9
Author: Kryptos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 15:05:40

W zaakceptowanej odpowiedzi przez @ chuck, jest komentarz mówiący, że to nie zadziała w przypadku FK.

U mnie zadziałało, przypadek 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; }
}
 4
Author: dalios,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-10 12:05:51

Zakładam, że zawsze chcesz, aby EntityId był kluczem podstawowym, więc zastąpienie go kluczem złożonym nie jest opcją (choćby dlatego, że klucze złożone są znacznie bardziej skomplikowane w pracy z i, ponieważ nie jest zbyt rozsądne posiadanie kluczy podstawowych, które również mają znaczenie w logice biznesowej).

Powinieneś przynajmniej utworzyć unikalny klucz na obu polach w bazie danych i dokładnie sprawdzić wyjątki naruszenia unikalnych kluczy podczas zapisywania zmian.

Dodatkowo ty może (powinien) sprawdzić unikalne wartości przed zapisaniem zmian. Najlepiej jest to zrobić za pomocą zapytania Any(), ponieważ minimalizuje ilość przesyłanych danych:

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

Uważaj, że sam czek nigdy nie wystarczy. Zawsze jest pewne opóźnienie między sprawdzeniem a rzeczywistym zatwierdzeniem, więc zawsze będziesz potrzebował unikalnego ograniczenia + obsługi wyjątków.

 3
Author: Gert Arnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-19 09:47:01

Ostatnio dodano klucz kompozytowy o wyjątkowości 2 kolumn przy użyciu podejścia zalecanego przez 'chuck', dzięki @ chuck. Tylko to wygladalo jak dla mnie czystsze:

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; }
 3
Author: Shoeb Hasan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-29 20:52:12