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

Posted

技术标签:

【中文标题】实体框架中多列的唯一键约束【英文标题】:Unique Key constraints for multiple columns in Entity Framework 【发布时间】:2013-09-24 05:10:20 【问题描述】:

我使用的是 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

有没有办法做到这一点?

【问题讨论】:

【参考方案1】:

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

旧型号:

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

这导致了所需的解决方案

【讨论】:

【参考方案2】:

对于那些寻找 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

【讨论】:

最后一件事:如果您尝试运行 ef update 命令并且修改后的表中有现有行破坏了这个新索引,SQL(或者在我的情况下是 PostgreSQL)将会对您大喊大叫! (正如人们所期望的那样) 请注意,此答案适用于 EF Core,在 EF6.x 中不可用【参考方案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; 

【讨论】:

很久了。可以说它已经工作了很长时间!感谢您的更新,请在@chuck 答案中添加评论。我觉得 Chuck 是不久前不使用 SO 了。 这里的EmployeeID属性是否需要一个属性来限制它的长度才能被索引?否则它是用不能有索引的 VARCHAR(MAX) 创建的?将属性 [StringLength(255)] 添加到 EmployeeID EmployeeID 是一个 GUID。许多教程建议将 GUID 映射到字符串而不是 guid,我不知道为什么【参考方案4】:

最近使用 '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; 

【讨论】:

【参考方案5】:

niaher 的回答说,要使用 fluent API,您需要一个自定义扩展,在撰写本文时可能是正确的。您现在可以(EF 核心 2.1)使用 fluent API,如下所示:

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

【讨论】:

不适用于导航属性 -> System.InvalidOperationException: 'Organization' cannot be used as a property on entity type 'Association' because it is configured as a navigation.. @LIvanov 我发现只需将导航属性标识符添加到模型中即可轻松解决此问题。在我的情况下,它遵循约定,因此只需添加机构 ID。然后.HasIndex(x =&gt; new x.InstitutionId, x.ExternalId )。产生migrationBuilder.CreateIndex( name: "IX_Customers_InstitutionId_ExternalId", table: "Customers", columns: new[] "InstitutionId", "ExternalId" , unique: true);【参考方案6】:

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

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。

【讨论】:

(y) 我觉得这个答案更好。另一件事,第三种方法不一定是最好的。 (实际上我喜欢第一个。)我个人更喜欢不将任何 EF 工件转移到我的实体类中。 可能,第二种方法应该是:CREATE UNIQUE INDEX ix_1_2 ON 0 (1, 2)? (见BOL) 愚蠢的问题:为什么你的名字都以“IX_”开头? @BastienVandamme 这是一个好问题。 EF 的自动生成索引以 IX_ 开头。默认情况下,这似乎是 EF 索引中的约定,索引名称将是 IX_property name。 是的,应该是。感谢 Fluent API 的实现。严重缺乏这方面的文档【参考方案7】:

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

【讨论】:

在本例中,您同时拥有 Company 和 CompanyId。这意味着调用者可以更改一个但不能更改另一个,并且拥有一个包含错误数据的实体。 @LosManos 你在说哪个来电者?该类表示数据库中的数据。通过查询更改值将确保一致性。根据您的客户端应用程序可以做什么,您可能需要实施检查,但这不是 OP 的范围。【参考方案8】:

如果您使用 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
)

【讨论】:

但那是索引而不是约束! 在您的第二个代码块 (this.Property(t =&gt; t.Email)) 中,包含什么类? (即:this 是什么) 非虚拟机。 EntityTypeConfiguration&lt;T&gt; @RomanPokrovskij:唯一索引和唯一约束之间的区别似乎在于它的记录在 SQL Server 中的维护方式。详情请见technet.microsoft.com/en-us/library/aa224827%28v=sql.80%29.aspx。 @niaher 我很欣赏你的扩展方法【参考方案9】:

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

【讨论】:

这对数据注释来说是正确的 :),如果你想知道使用 fluent API 的答案,请参阅下面的 Niaher 的答案***.com/a/25779348/2362036 但我需要它为外键工作!你能帮帮我吗? @0xFEEDC0DE 请参阅下面的答案,该答案解决了在索引中使用外键的问题。 @JJS - 我让它在其中一个属性是外键的情况下工作。你的键是varchar还是nvarchar?可以用作唯一键的长度是有限制的。***.com/questions/2863993/… 使用 System.ComponentModel.DataAnnotations.Schema;【参考方案10】:

我假设您总是希望 EntityId 成为主键,所以用复合键替换它不是一个选项(如果只是因为复合键使用起来要复杂得多 因为拥有在业务逻辑中也有意义的主键不是很明智)。

您至少应该在数据库中的两个字段上创建一个唯一键,并在保存更改时专门检查唯一键违规异常。

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

if (context.Entities.Any(e => e.FirstColumn == value1 
                           && e.SecondColumn == value2))

    // deal with duplicate values here.

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

【讨论】:

感谢@GertArnold 的回答,但我不想验证业务层的唯一性,这是一项数据库工作,应在数据库中完成! 好的,那么坚持唯一索引。但无论如何,您都必须处理业务层中的索引违规问题。 当我收到这种异常时,我会从外部捕获,然后可能会报告错误并中断进程或关闭应用程序。 是的,...我需要对此作出回应吗?请记住,我对您的应用程序一无所知,我无法告诉您处理这些异常的最佳方法是什么,只有 您必须处理它们。 警惕 EF 的 DB 唯一约束。如果您这样做,然后您最终获得了一个更新,该更新会触发作为唯一键一部分的列之一的值,那么实体 frameowkr 将在保存时失败,除非您添加整个事务层。例如:页面对象有一个元素的子集合。每个元素都有 SortOrder。您希望 PageID 和 SortOrder 的组合是唯一的。在前端,用户使用 sortorder 1 和 2 翻转元素的顺序。Entity Framework 将失败保存 b/c,它试图一次更新一个 sortorders。【参考方案11】:

您需要定义一个复合键。

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

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

【讨论】:

但不是我只希望它们作为唯一键的键应该是 Id 吗?我已更新问题感谢您的帮助!

以上是关于实体框架中多列的唯一键约束的主要内容,如果未能解决你的问题,请参考以下文章

通过实体框架更新时如何绕过唯一键约束(使用 dbcontext.SaveChanges())

CoreData(IOS)多列的唯一约束?

主键,子外键查询

建表的约束

将房间数据库中的唯一约束添加到多列

主键,外键