EF Core(Code First)中是否有用于唯一约束的数据注释?

24

我想知道在Entity Framework Core 2的Code First方法中是否有用于唯一约束的数据注释?


1
请告诉我们您希望在问题#10864上解决它。 - bricelam
@bricelam,谢谢,我已经做了。 - Elnoor
很奇怪他们在EF6中已经有这个功能了,为什么要将其删除。虽然你几乎可以通过数据注释属性完成所有操作,但仍有一些事情需要使用流畅的API。 - Steven Rands
有更多的事情只能通过流畅的API完成,因为流畅的API比注释更灵活、更具表现力。当一个映射结构涉及多个实体和/或属性时,注释只会变成意大利面式编程。 - Gert Arnold
如果升级您的.NET Core版本是一个选项,这里有解决方案:https://learn.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations#index-uniqueness - Daan
@Daan 不是为了我自己,而是为了其他人,我认为如果您将它作为答案并附带快速示例粘贴,那将非常有帮助。 - Elnoor
4个回答

26

EF Core 中,你只能在流畅的API中使用扩展方法HasAlternateKey。没有数据注释来实现唯一约束条件。

这篇微软文档 - 备用键(唯一约束条件) - 将解释如何使用以及存在哪些进一步的可能性。

这是来自上述链接的一个简短的例子:

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中,您可以使用流畅的API扩展方法HasIndex或数据注释方式来使用属性[Index]

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

这里是使用流畅的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-09-10

  • 添加了如何使用数据注释的额外信息,因为它现在在EF Core中可用;

更新于2021-09-24

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

1
.HasIndex和IsUnique在nvarchar(字符串)类型的列上不起作用。 - Johar Zaman
据我所知,如果长度小于900字节,您可以使用NVARCHAR或VARCHAR。您可以使用流畅的API或属性为列定义它。上面的示例代码直接来自链接的MS Doc文章。如果您为GUID设置最大长度(例如32),而不是[N]VARCHAR(MAX),它应该可以工作。也许这个答案对您有帮助stackoverflow.com/a/27687748。 - ChW

22

现在有一个基于代码的注释来更新。

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

其他答案中没有人建议这个,真有趣。 - Ali Poustdouzan
2
直到 .NET 5,EF Core [Index] 特性才被引入:https://learn.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations#index-uniqueness - Justin Tubbs

11

我编写了一个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 属性。此外,在创建复合唯一键时,它会抛出异常,因为它尝试创建重复的索引(每个复合索引成员都会创建一个)。虽然它向我展示了如何让我的代码工作,但也有这些问题。 - Jargon
1
如果您正在使用单个属性UniqueKey(或多个属性UniqueKey),则必须使用基于零的Order值--对于每个被UniqueKeyAttribute修饰的属性。 如果您没有正确使用Order,那么我可以想象出现异常的情况。请正确使用基于零的Order,并在消极方面表现得更加克制。 - Justin Tubbs
您是不是想说 OnModelCreating 而不是 OnModelGenerating? - D. Kermott
@D.Kermott 的文档说明需要在 OnModelCreating() 中调用类似 IsUnique() 的方法,因此 Justin 肯定是指 OnModelCreating。 - ViRuSTriNiTy
FYI,已添加IsValid()重写。 - ViRuSTriNiTy
我该如何处理名称?我有一个已存在的数据库和键,不想再次运行迁移。 - Donald N. Mafa

-2
DbContext.cs 文件中,在 OnModelCreating(modelBuilder) 方法内的最后一个 ForEach 中,我使用了 .OrderBy(o => o.Order)

2
我不想给你点踩,你最好删除这个回答。它与问题无关。 - Elnoor
谢谢你的关注,但我认为按正确顺序拥有多个键很重要,不是吗?否则,拥有这个属性有什么意义(除了0)呢? - ypelissier
2
这与问题有什么关系? - Elnoor
我无法在使用Attribute时添加评论,所以我在这里添加了它,这与问题有关,不是吗? - ypelissier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接