You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Contact的CRUD中允许City为null,同时保留City自身的Required特性

This is the cleanest approach because it decouples your domain entities from presentation/validation logic. You define separate view models for each use case, so City's validation rules only apply when you're directly working with City entities.

Step 1: Create ViewModels for Contact/ContactAddress

// For editing Contacts
public class ContactEditViewModel
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Name field is required and must not be empty.")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Name must be 3-50 characters long.")]
    public string Name { get; set; }

    public ICollection<ContactAddressEditViewModel> ContactAddresses { get; set; }
}

// For editing ContactAddresses as part of a Contact
public class ContactAddressEditViewModel
{
    public int Id { get; set; }

    [Required(ErrorMessage = "AddressLine field is required and must not be empty.")]
    [StringLength(70, MinimumLength = 3, ErrorMessage = "AddressLine must be 3-70 characters long.")]
    public string AddressLine { get; set; }

    public int? CityId { get; set; }
    // No City property here—we only need the ID for association
}

Step 2: Map ViewModels to Domain Entities in Your Controller

public async Task<IActionResult> UpdateContact(ContactEditViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        return View(viewModel);
    }

    // Fetch the existing Contact with its addresses
    var contact = await _context.Contacts
        .Include(c => c.ContactAddresses)
        .FirstOrDefaultAsync(c => c.Id == viewModel.Id);
    
    if (contact == null) return NotFound();

    // Update basic Contact properties
    contact.Name = viewModel.Name;

    // Update ContactAddresses (use AutoMapper for cleaner code if you want)
    foreach (var vmAddress in viewModel.ContactAddresses)
    {
        var existingAddress = contact.ContactAddresses.FirstOrDefault(a => a.Id == vmAddress.Id);
        if (existingAddress != null)
        {
            existingAddress.AddressLine = vmAddress.AddressLine;
            existingAddress.CityId = vmAddress.CityId;
            // We don't touch the City object here—let EF handle the foreign key association
        }
        // Add new addresses if needed
        else
        {
            contact.ContactAddresses.Add(new ContactAddress
            {
                AddressLine = vmAddress.AddressLine,
                CityId = vmAddress.CityId
            });
        }
    }

    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

This way, validation only runs against the view models (which don't include the City object for ContactAddress), so City.Name's Required rule never gets triggered during Contact updates. For City's own CRUD, you still use the City entity directly, keeping its validation rules intact.


2. Add Conditional Validation to City.Name

If you don't want to use view models, you can create a custom validation attribute that only enforces the Required rule when City is being validated on its own (not as a related entity).

Custom Validation Attribute

public class RequiredWhenSelfValidatedAttribute : RequiredAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Only enforce the required rule if we're validating a City entity directly
        if (validationContext.ObjectInstance is City)
        {
            return base.IsValid(value, validationContext);
        }
        // Skip validation if City is a related entity (like in ContactAddress)
        return ValidationResult.Success;
    }
}

Update the City Class

public class City
{
    [Key]
    public int Id { get; set; }

    [RequiredWhenSelfValidated(ErrorMessage = "Name field is required and must not be empty.")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Name must be 3-50 characters long.")]
    public string Name { get; set; }

    [StringLength(10, MinimumLength = 3, ErrorMessage = "ZipCode must be 3-10 characters long.")]
    public string ZipCode { get; set; }
}

This checks if the validation is happening on a City instance directly (e.g., when creating/updating a City) or as a related object (e.g., in a ContactAddress). It only applies the Required rule in the former case.


3. Disable Recursive Validation (Last Resort)

You can turn off recursive validation for entities, which means the validator won't check related objects like ContactAddress or City when validating a Contact. This is simpler but riskier, as it disables all cross-entity validation.

Option A: Configure in DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Disable auto-inclusion and recursive validation for collections
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        var collectionProperties = entityType.ClrType.GetProperties()
            .Where(p => p.PropertyType.IsGenericType 
                        && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>));
        
        foreach (var prop in collectionProperties)
        {
            modelBuilder.Entity(entityType.ClrType)
                .Navigation(prop.Name)
                .AutoInclude(false);
        }
    }
}

Option B: Manually Validate Without Recursion

// When validating a Contact manually
var validationContext = new ValidationContext(contact);
// Add a flag to skip recursive validation (you'd need to handle this in custom validators if needed)
validationContext.Items["SkipRecursive"] = true;

var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(contact, validationContext, validationResults, validateAllProperties: true);

Only use this if you're sure you don't need any validation for related entities in the Contact workflow.


Final Recommendation

Go with Option 1 (ViewModels). It's the most maintainable approach, keeps your domain entities clean, and avoids messy conditional logic in validation rules.

内容的提问来源于stack exchange,提问作者GAC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 08:52:32