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

问题描述 投票:0回答:5

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

c# ef-code-first entity-framework-core ef-core-2.0
5个回答
29
投票

EF Core 中,您只能在 Fluent API 中使用扩展方法

HasAlternateKey
没有数据注释来实现唯一约束

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

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

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 doc 文章 - 索引 - 您将找到如何使用的更多信息。

这里是一个使用 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-09-10

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

更新2021-09-24

  • 修复了属性示例中缺失的 IsUnique 属性

24
投票

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

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

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

0
投票

我更喜欢对唯一索引进行唯一约束。
大多数数据库管理工具直接在表信息中显示唯一约束,而不仅仅是索引列表。
为了可读性,我也更喜欢注释而不是 Fluent api。
所以我写了自己的自定义类级别AttributeConvention,可以这样使用:

[UniqueConstraint(nameof(Author), nameof(Book), Name = "UQ_Author_Book")]
// name is optional
internal class Books
{
    public required string Author { get; set; }
    public required string Book { get; set; }
}

实施:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class UniqueConstraintAttribute(params string[] propertyNames) : Attribute
{
    private string? _name;

    public IReadOnlyList<string> PropertyNames { get; } = [.. propertyNames];

    [DisallowNull]
    public string? Name
    {
        get => _name;
        set => _name = value;
    }
}

public class UniqueConstraintAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) :
    TypeAttributeConventionBase<UniqueConstraintAttribute>(dependencies)
{
    protected override void ProcessEntityTypeAdded(
        IConventionEntityTypeBuilder entityTypeBuilder,
        UniqueConstraintAttribute attribute,
        IConventionContext<IConventionEntityTypeBuilder> context)
    {
        var properties = entityTypeBuilder.Metadata.FindProperties(attribute.PropertyNames) ?? [];
        if(attribute.PropertyNames.FirstOrDefault(name => !properties.Any(p => p.Name == name)) is string nameNotFound)
            throw new Exception($"UniqueConstraint column `{nameNotFound}` can't be found in {entityTypeBuilder.Metadata.Name}");
        if (properties.Count == 0)
            throw new Exception("No UniqueConstraint columns were set");

        if(entityTypeBuilder.HasKey(properties, fromDataAnnotation: true) is IConventionKeyBuilder KeyBuilder &&
            attribute.Name is string name)
            KeyBuilder.HasName(name);
    }
}

现在您必须在 DbContext.ConfigureConventions 回调中添加自定义约定:

internal class DbContext : Microsoft.EntityFrameworkCore.DbContext
{
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Conventions.Add(sp => new UniqueConstraintAttributeConvention(
            sp.GetRequiredService<ProviderConventionSetBuilderDependencies>()));

}

ConfigureConventions
自 EF Core 6 起可用。
在 EF Core 6 之前,通过 IDbContextOptionsExtension 添加自定义约定要复杂得多,这在 this 帖子中进行了描述。

IConventionEntityTypeBuilder
缺少
HasAlternateKey
,但我模仿了它的行为,只是用
HasKey()
添加常规键。


-2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.