EF Core 中是否有唯一约束的数据注释(代码优先)?
Is there a data annotation for unique constraint in EF Core (code first)?
我想知道 Entity Framework 核心 2 代码优先方法中是否有唯一约束的数据注释?
在 EF Core 中,您只能在 fluent API 中使用扩展方法 HasAlternateKey
。 没有数据注释实现唯一约束。
这篇 MS 文档文章 - Alternate Keys (Unique Constraints) - 将解释如何使用以及存在哪些进一步的可能性。
上面 link 中的一个简短示例:
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中可以使用fluentAPI的扩展方式HasIndex
或者属性[Index]
的数据标注方式[=]。 18=]
在此 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 属性
我已经编写了一个属性 class,可以让您修饰 EF 核心实体 class 属性以生成唯一键(没有 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 class 中,添加此私有方法:
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 class 中的用法:
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; }
}
您甚至可以跨多个属性使用它来在 table 的多个列中形成一个唯一键。 (注意使用“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; }
}
在 DbContext.cs
文件中,在 OnModelCreating(modelBuilder)
方法中,在最后一个 ForEach
上,我有 .OrderBy(o => o.Order)
要更新,现在有代码优先注释。
[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; }
}
我想知道 Entity Framework 核心 2 代码优先方法中是否有唯一约束的数据注释?
在 EF Core 中,您只能在 fluent API 中使用扩展方法 HasAlternateKey
。 没有数据注释实现唯一约束。
这篇 MS 文档文章 - Alternate Keys (Unique Constraints) - 将解释如何使用以及存在哪些进一步的可能性。
上面 link 中的一个简短示例:
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中可以使用fluentAPI的扩展方式HasIndex
或者属性[Index]
的数据标注方式[=]。 18=]
在此 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 属性
我已经编写了一个属性 class,可以让您修饰 EF 核心实体 class 属性以生成唯一键(没有 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 class 中,添加此私有方法:
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 class 中的用法:
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; }
}
您甚至可以跨多个属性使用它来在 table 的多个列中形成一个唯一键。 (注意使用“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; }
}
在 DbContext.cs
文件中,在 OnModelCreating(modelBuilder)
方法中,在最后一个 ForEach
上,我有 .OrderBy(o => o.Order)
要更新,现在有代码优先注释。
[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; }
}