EF Core 中是不是有用于唯一约束的数据注释(代码优先)?

Posted

技术标签:

【中文标题】EF Core 中是不是有用于唯一约束的数据注释(代码优先)?【英文标题】:Is there a data annotation for unique constraint in EF Core (code first)?EF Core 中是否有用于唯一约束的数据注释(代码优先)? 【发布时间】:2018-09-06 15:48:15 【问题描述】:

我想知道 Entity Framework Core 2 代码优先方法中是否有唯一约束的数据注释?

【问题讨论】:

在 issue #10864 上告诉我们您想要它。 @bricelam,谢谢,我做到了。 奇怪的是,当它已经是 EF6 的一部分时,他们删除了此功能。您可以通过数据注释属性几乎完成所有事情,这很烦人,但仍有一些事情您必须退回到流利的 API。 有更多的事情只能通过 fluent API 来完成,因为 fluent API 比注解更加灵活和富有表现力。当一个映射构造中涉及多个实体和/或属性时,注释只不过是意大利面条式编程。 如果可以选择升级您的 .NET Core 版本,解决方案如下:docs.microsoft.com/en-us/ef/core/modeling/… 【参考方案1】:

EF Core 中,您只能在 Fluent API 中使用扩展方法 HasAlternateKey没有数据注释来实现唯一约束

这篇 MS 文档文章 - Alternate Keys (Unique Constraints) - 将解释如何使用以及存在哪些进一步的可能性。

上面链接中的一个简短示例:

class MyContext : DbContext

    public DbSet<Car> Cars  get; set; 

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.Entity<Car>()
            .HasAlternateKey(c => c.LicensePlate)
            .HasName("AlternateKey_LicensePlate");
    


class Car

    public int CarId  get; set; 
    public string LicensePlate  get; set; 
    public string Make  get; set; 
    public string Model  get; set; 

也可以定义一个唯一索引。因此,在EF Core中可以使用fluent API的扩展方式HasIndex或者带有属性[Index]的数据注解方式。

在这篇 MS 文档文章 - Indexes - 您将找到更多关于如何使用的信息。

这是一个使用 fluent API 的唯一索引示例:

class MyContext : DbContext

    public DbSet<Blog> Blogs  get; set; 

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.Entity<Blog>()
            .HasIndex(b => b.Url)
            .IsUnique();
    


public class Blog

    public int BlogId  get; set; 
    public string Url  get; set; 

这里是相同的示例,但带有数据注释

[Index(nameof(Url), IsUnique = true)]
public class Blog

    public int BlogId  get; set; 
    public string Url  get; set; 


2021 年 9 月 10 日更新

添加了如何与数据注释一起使用的附加信息,因为它现在在 EF Core 中可用;

2021 年 9 月 24 日更新

修复了属性示例中缺少 IsUnique 属性的问题

【讨论】:

.HasIndex 和 IsUnique 不适用于 nvarchar(string) 类型的列。 据我所知,如果 NVARCHAR 或 VARCHAR 的长度小于 900 字节,则可以使用它。您可以为具有 fluent API 或属性的列定义它。上面的示例代码直接来自链接的 MS Doc 文章。如果您设置最大长度,例如32 对于 GUID 而不是 [N]VARCHAR(MAX) 它应该可以工作。也许这个答案对你也有帮助 ***.com/a/27687748【参考方案2】:

我编写了一个 Attribute 类,它可以让您装饰 EF Core Entity 类属性以生成唯一键(无需 Fluent API)。

using System;
using System.ComponentModel.DataAnnotations;

/// <summary>
/// Used on an EntityFramework Entity class to mark a property to be used as a Unique Key
/// </summary>
[AttributeUsageAttribute(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class UniqueKeyAttribute : ValidationAttribute

    /// <summary>
    /// Marker attribute for unique key
    /// </summary>
    /// <param name="groupId">Optional, used to group multiple entity properties together into a combined Unique Key</param>
    /// <param name="order">Optional, used to order the entity properties that are part of a combined Unique Key</param>
    public UniqueKeyAttribute(string groupId = null, int order = 0)
    
        GroupId = groupId;
        Order = order;
    

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    
        // we simply return success as no actual data validation is needed because this class implements a "marker attribute" for "create a unique index"
        return ValidationResult.Success;
    

    public string GroupId  get; set; 
    public int Order  get; set; 

在您的 DbContext.cs 文件中,在 OnModelCreating(modelBuilder) 方法中,添加以下内容:

// Iterate through all EF Entity types
foreach (var entityType in modelBuilder.Model.GetEntityTypes())

    #region Convert UniqueKeyAttribute on Entities to UniqueKey in DB
    var properties = entityType.GetProperties();
    if ((properties != null) && (properties.Any()))
    
        foreach (var property in properties)
        
            var uniqueKeys = GetUniqueKeyAttributes(entityType, property);
            if (uniqueKeys != null)
            
                foreach (var uniqueKey in uniqueKeys.Where(x => x.Order == 0))
                
                    // Single column Unique Key
                    if (String.IsNullOrWhiteSpace(uniqueKey.GroupId))
                    
                        entityType.AddIndex(property).IsUnique = true;
                    
                    // Multiple column Unique Key
                    else
                    
                        var mutableProperties = new List<IMutableProperty>();
                        properties.ToList().ForEach(x =>
                        
                            var uks = GetUniqueKeyAttributes(entityType, x);
                            if (uks != null)
                            
                                foreach (var uk in uks)
                                
                                    if ((uk != null) && (uk.GroupId == uniqueKey.GroupId))
                                    
                                        mutableProperties.Add(x);
                                    
                                
                            
                        );
                        entityType.AddIndex(mutableProperties).IsUnique = true;
                    
                
            
        
    
    #endregion Convert UniqueKeyAttribute on Entities to UniqueKey in DB

同样在你的 DbContext.cs 类中,添加这个私有方法:

private static IEnumerable<UniqueKeyAttribute> GetUniqueKeyAttributes(IMutableEntityType entityType, IMutableProperty property)

    if (entityType == null)
    
        throw new ArgumentNullException(nameof(entityType));
    
    else if (entityType.ClrType == null)
    
        throw new ArgumentNullException(nameof(entityType.ClrType));
    
    else if (property == null)
    
        throw new ArgumentNullException(nameof(property));
    
    else if (property.Name == null)
    
        throw new ArgumentNullException(nameof(property.Name));
    
    var propInfo = entityType.ClrType.GetProperty(
        property.Name,
        BindingFlags.NonPublic |
        BindingFlags.Public |
        BindingFlags.Static |
        BindingFlags.Instance |
        BindingFlags.DeclaredOnly);
    if (propInfo == null)
    
        return null;
    
    return propInfo.GetCustomAttributes<UniqueKeyAttribute>();

在 Entity.cs 类中的用法:

public class Company

    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid CompanyId  get; set; 

    [Required]
    [UniqueKey(groupId: "1", order: 0)]
    [StringLength(100, MinimumLength = 1)]
    public string CompanyName  get; set; 

您甚至可以在多个属性中使用它来跨表中的多个列形成唯一键。 (注意使用“groupId”,然后是“order”)

public class Company

    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid CompanyId  get; set; 

    [Required]
    [UniqueKey(groupId: "1", order: 0)]
    [StringLength(100, MinimumLength = 1)]
    public string CompanyName  get; set; 

    [Required]
    [UniqueKey(groupId: "1", order: 1)]
    [StringLength(100, MinimumLength = 1)]
    public string CompanyLocation  get; set; 

【讨论】:

这个答案存在多个问题。如果Order 不是0,则跳过UniqueKey 属性。此外,它在创建复合唯一键时抛出异常,因为它试图创建重复索引(复合索引的每个成员一个)。虽然它向我展示了如何让我的代码正常工作,所以我猜…… 如果您使用单个属性 UniqueKey(或多个属性 UniqueKey),您必须使用从零开始的 Order 值 - 对于每个使用 UniqueKeyAttribute 修饰的属性。如果您没有正确使用 Order,那么我可以想象您在哪里引发了异常。正确使用从零开始的顺序,对消极性表现出更多的克制。 您的意思是 OnModelCreating 而不是 OnModelGenerating? @D.Kermott 文档指出像 IsUnique() 这样的调用需要在 OnModelCreating() 中调用,因此 Justin 肯定是指 OnModelCreating。 FYI IsValid() 添加了覆盖【参考方案3】:

为了更新,现在有一个代码优先注释。

[Index(nameof(MyProperty), IsUnique = true)] // using Microsoft.EntityFrameworkCore
public class MyClass

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid Id  get; set; 

    [StringLength(255), Required]
    public string MyProperty  get; set; 

【讨论】:

有趣的是没有人在其他答案中提出这个建议 在 .NET 5 之前,EF Core [Index] 属性不存在:docs.microsoft.com/en-us/ef/core/modeling/…【参考方案4】:

DbContext.cs 文件中,在OnModelCreating(modelBuilder) 方法中,在最后一个ForEach 上,我有.OrderBy(o =&gt; o.Order)

【讨论】:

我不想投反对票,你最好删除这个答案。与问题无关 感谢您的关注,但我认为以正确的顺序拥有多个键很重要,不是吗?否则,拥有这个属性有什么意义(0除外)? 跟这个问题有什么关系? 我不能对Attribute的使用添加注释,所以我在这里添加了它,是不是和问题有关?

以上是关于EF Core 中是不是有用于唯一约束的数据注释(代码优先)?的主要内容,如果未能解决你的问题,请参考以下文章

在 EF Core 中生成复合唯一约束/索引

EF Core / Sqlite 一对多关系在唯一索引约束上失败

如何首先将 EF Core 代码与 azure synapse 一起使用

如何在EF CodeFirst中使用唯一约束

在 EF Core 映射上添加唯一索引似乎不起作用

SQLite EF Core - '外键约束失败'