实体框架中多列的唯一键约束

时间:2013-09-19 08:01:20

标签: entity-framework ef-code-first constraints multiple-columns unique-key

我正在使用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;}
 }

我想将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

有没有这样做?

11 个答案:

答案 0 :(得分:328)

使用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

答案 1 :(得分:93)

我找到了解决问题的三种方法。

EntityFramework核心中的唯一索引:

第一种方法:

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

这种方法非常快速且有用,但主要问题是实体框架对这些更改一无所知!


第二种方法:
  我在这篇文章中找到了它,但我没有亲自尝试过。

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中,您可以根据给定的整数组合键。 但有一点需要注意:如果unique属性是string类型,那么你必须设置MaxLength。

答案 2 :(得分:71)

如果您使用的是代码优先,则可以实现自定义扩展程序 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 :(得分:15)

您需要定义复合键。

使用数据注释,它看起来像这样:

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

答案 4 :(得分:8)

使用复合索引外键完成@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扩展名进行唯一索引注释。

答案 5 :(得分:6)

对于那些寻找 2021 年解决方案的人来说,已接受答案的工作版本现在应如下所示:

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

这样注释应该存在于模型上,而不是单个列上。另请注意 nameof() 语法。

这个答案来自官方文档:https://docs.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations

答案 6 :(得分:3)

从niaher那里得到的答案表明,要使用fluent API,您需要自定义扩展名,在撰写本文时可能是正确的。您现在可以(EF core 2.1)如下使用fluent API:

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

答案 7 :(得分:3)

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

答案 8 :(得分:2)

我假设你总是希望EntityId成为主键,所以用复合键替换它不是一个选项(只是因为复合键使用和因为拥有在业务逻辑中也有意义的主键是不明智的。)

您应该做的最少的事情是在数据库中的两个字段上创建一个唯一键,并在保存更改时专门检查唯一键冲突异常。

此外,您可以(应该)在保存更改之前检查唯一值。最好的方法是通过Any()查询,因为它可以最大限度地减少传输的数据量:

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

请注意,仅此检查是不够的。检查和实际提交之间总会有一些延迟,因此您总是需要唯一约束+异常处理。

答案 9 :(得分: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; }

答案 10 :(得分:0)

我想添加我的答案,因为提供的解决方案对我没有帮助。就我而言,其中一列是外键引用。

旧型号:

public class Matrix
{
    public int ID { get; set; }

    public MachineData MachineData { get; set; }

    public MachineVariant MachineVariant { get; set; }
}

注意 MachineVariant 是一个枚举,而 MachineData 是一个引用。

尝试使用@Bassam Alugili 提供的解决方案:

modelBuilder.Entity<Matrix>()
   .HasIndex(sm => new { sm.MachineData, sm.DoughVariant }).IsUnique(true);

没用。所以我为 machineData 外键添加了一个 ID 列,如下所示:

public class Matrix
{
    public int ID { get; set; }

    public MachineData MachineData { get; set; }

    [ForeignKey("MachineData")]
    public int MachineDataID { get; set; }

    public MachineVariant MachineVariant { get; set; }
}

并将模型构建器代码更改为:

modelBuilder.Entity<Matrix>()
   .HasIndex(sm => new { sm.MachineDataID, sm.DoughVariant }).IsUnique(true);

这导致了所需的解决方案