在我的ASP.NET核心web应用程序中,我与某些字段进行了类,如果另一个字段具有特定值,则这些字段是必需的。例如,我有一个类person
,其中包含与雇用有关的字段,如职务,雇主名称和工作开始日期。只有当枚举字段Person.EmploymentStatus
等于Employed
时,才需要这些字段。为了做到这一点,我创建了自己的RequireDifAttribute
,如果选择的属性具有默认值,并且父属性等于条件值,则它将在其中设置该属性无效。对于Person
类,字段JobTitle
,Employer
和WorkStartingDate
具有[RequiredIf]
属性,其中父属性为EmploymentStatus
,条件值为Employed
。下面是person模型类:
[DisplayColumn(nameof(PersonName))]
public class Person
{
[Key]
[Display(Name = "ID")]
public int PersonId { get; set; }
[Required]
[StringLength(128)]
[Display(Name = "Person Name", ShortName = "Name")]
public string PersonName { get; set; }
[Required]
[Display(Name = "Employment Status")]
public PersonEmploymentStatus EmploymentStatus { get; set; }
// This field is required if employment status equals employed
[RequiredIf(nameof(EmploymentStatus), PersonEmploymentStatus.Employed)]
[Display(Name = "Job Title")]
[StringLength(128)]
public string JobTitle { get; set; }
// This field is required if employment status equals employed
[RequiredIf(nameof(EmploymentStatus), PersonEmploymentStatus.Employed)]
[Display(Name = "Employer")]
[StringLength(128)]
public string Employer { get; set; }
// This field is required if employment status equals employed
[RequiredIf(nameof(EmploymentStatus), PersonEmploymentStatus.Employed)]
[Display(Name = "Work Starting Date")]
public DateTime? WorkStartingDate { get; set; }
}
以下是RequiredifAttribute
的定义:
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
/// <summary>
/// Required attribute that depends on a specific value of another property in the same instance
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfAttribute : RequiredAttribute
{
// Value of the property that will make the selected property to be required
private object _propertyConditionalValue;
/// <summary>
/// Name of the property to check its value
/// </summary>
public string ParentPropertyName { get; set; }
/// <summary>
/// Initializes attribute with the parent property and value to check if the selected property is populated or not
/// </summary>
/// <param name="propertyName">Name of the parent property</param>
/// <param name="propertyConditionalValue">Value to check if the parent property is equal to this</param>
public RequiredIfAttribute(string propertyName, object propertyConditionalValue) =>
(ParentPropertyName, _propertyConditionalValue) = (propertyName, propertyConditionalValue);
/// <inheritdoc />
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// Get the parent property
PropertyInfo parentProp = context.ObjectType.GetProperty(ParentPropertyName);
// Get the value of the parent property
object parentPropertyValue = parentProp?.GetValue(context.ObjectInstance);
// Check if the value of the parent property is equal to the conditional value that will require
// the selected property to be populated, and if the selected property is not populated, then return invalid result
if (_propertyConditionalValue.Equals(parentPropertyValue) && value == default)
{
// Display name of the parent property
string parentPropDisplayName = parentProp.Name;
// Try to get the display attribute from the parent property, if it has any
DisplayAttribute displayAttribute = parentProp.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
// Use the name from the display attribute instead
parentPropDisplayName = displayAttribute.Name ?? displayAttribute.ShortName ?? parentPropDisplayName;
}
// Calculate error message
string errorMessage = $"When {parentPropDisplayName} is {_propertyConditionalValue}, {context.DisplayName} is required.";
// Return invalid result
return new ValidationResult(errorMessage);
}
// Otherwise, return a valid result
return ValidationResult.Success;
}
}
这适用于ASP.NET核心web应用程序。如果用户在表单中选择“Employed”,而将其余字段保留为空,则在UI中会显示一条错误消息,类似于“当Employed Status为Employed时,需要职称”。
但是,这些字段在数据库中应该是可以为空的。如果用户处于“失业”或“自营职业”状态,则数据库中的“雇主”等字段应为空值。问题是,当我使用add-migration
PowerShell脚本添加迁移时,它将那些字段设置为不可为空。迁移是这样的:
...
migrationBuilder.CreateTable(
name: "Person",
columns: table => new
{
PersonId = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
PersonName = table.Column<string>(maxLength: 128, nullable: false),
EmploymentStatus = table.Column<byte>(nullable: false),
JobTitle = table.Column<string>(maxLength: 128, nullable: false),
Employer = table.Column<string>(maxLength: 128, nullable: false),
WorkStartingDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Person", x => x.PersonId);
});
...
像Job Title这样的字段的参数“nullable”等于false,而我需要它们等于truejobtitle=table.column
。当字段为空且雇用状态为失业或自营职业时,这会使应用程序在SqlException下崩溃。它指出“无法将值NULL插入到表'RequiredCascadingAttributeTestContext-EF4BFD77-387D-4CB1-B197-58F1999C04C7.DBO.Person‘中的列'E雇主’中;列不允许NULL.insert失败。语句已终止。”
我知道我可以更改迁移的代码,但是我有很多字段使用自定义的[RequiredIf]
属性。每次添加新迁移时,都会显示一组Alter column语句,使字段不可为NULL。那么,如何使EF Core避免在迁移中将具有[RequiredIf]
属性的字段设置为非空值呢?我真的找不到办法来完成这件事。
谢了。
对于RequiredIfAttribute,您需要实现ValidationAttribute而不是RequiredAtrribute,因为对于EF,它已经有了一些您不想使用的行为(在本例中,将字段设置为不可为空)。
因此它应该看起来像
public class RequiredIfAttribute : ValidationAttribute
在将更改保存到实际数据库之前,会调用验证,这样就可以在那里检查它,您已经在代码中这样做了