简体   繁体   中英

Entity Framework validation error on non-required object field

I have an ASP.NET MVC website that is passing ViewModels up to a WebAPI Service in order to perform CRUD operations on objects in the database using Entity Framework code first.

One of my Domain Entity Objects is a Business, which has business information, an address, a primary contact, and secondary contact. The thing is, the secondary contact object is not required since not all companies will have a secondary contact. I have setup the ID field as nullable, but when I attempt to save a new business record into the database that doesn't have a secondary contact object, it is giving me entity validation errors that the Secondary Contact record fields are required.

Does anyone know how to stop this error so I can save the business entity in the database without a secondary contact?

Below is the related code. I am using automapper to map between my viewmodels and models.

Domain Business Object

    public class Business
{
    public Guid PrimaryContactId { get; set; }
    [ForeignKey("PrimaryContactId")]
    [Required]
    public virtual Contact PrimaryContact { get; set; }

    public Guid? SecondaryContactId { get; set; }
    [ForeignKey("SecondaryContactId")]
    public virtual Contact SecondaryContact { get; set; }

    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string CompanyName { get; set; }

    [MaxLength(100)]
    public string CompanyWebsite { get; set; }

    [Required]
    [MaxLength(100)]
    public string Industry { get; set; }

    [Required]
    public NumberOfEmployees NumberOfEmployees { get; set; }

    [Required]
    public CommunicationPreference CommunicationPreference { get; set; }

    public Guid AddressId { get; set; }

    [ForeignKey("AddressId")]
    [Required]
    public virtual Address Address { get; set; }
}

Business View Model

public class BusinessViewModel
{
    public Guid PrimaryContactId { get; set; }
    public PrimaryContactViewModel PrimaryContact { get; set; }

    public Guid? SecondaryContactId { get; set; }
    public SecondaryContactViewModel SecondaryContact { get; set; }

    public Guid Id { get; set; }

    [DisplayName("Company Name")]
    [Required]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public string CompanyName { get; set; }

    [DisplayName("Company Website")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public string CompanyWebsite { get; set; }

    [DisplayName("Industry")]
    [Required]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public string Industry { get; set; }

    [DisplayName("Number of Employees")]
    [Required]
    public NumberOfEmployees NumberOfEmployees { get; set; }

    [DisplayName("Communication Preference")]
    [Required]
    public CommunicationPreference CommunicationPreference { get; set; }

    public Guid AddressId { get; set; }
    [Required]
    public AddressViewModel Address { get; set; }
}

Domain Contact Object

    public class Contact
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    public Salutations? Prefix { get; set; }

    [Required]
    [MaxLength(100)]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(100)]
    public string LastName { get; set; }

    [Required]
    [MaxLength(100)]
    public string JobTitle { get; set; }

    [Required]
    [DataType(DataType.EmailAddress)]
    [MaxLength(100)]
    public string Email { get; set; }

    [Required]
    [MaxLength(100)]
    public string Phone { get; set; }

    [MaxLength(100)]
    public string PhoneExtension { get; set; }
}

Base Contact View Model

    public class BaseContactViewModel
{
    public Guid Id { get; set; }

    [DisplayName("Primary Contact Prefix")]
    public virtual Salutations Prefix { get; set; }

    [DisplayName("Primary Contact First Name")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string FirstName { get; set; }

    [DisplayName("Primary Contact Last Name")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string LastName { get; set; }

    [DisplayName("Primary Contact Job Title")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string JobTitle { get; set; }

    [DisplayName("Primary Contact Email Address")]
    [DataType(DataType.EmailAddress)]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string Email { get; set; }

    [DisplayName("Primary Contact Phone")]
    [DataType(DataType.PhoneNumber)]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string Phone { get; set; }

    [DisplayName("Primary Contact Extension")]
    [MinLength(3, ErrorMessage = "Your {0} must be at least {1} characters long")]
    [MaxLength(100, ErrorMessage = "Your {0} must be no more than {1} characters")]
    public virtual string PhoneExtension { get; set; }
}

Secondary Contact View Model

public class SecondaryContactViewModel : BaseContactViewModel
{

    [DisplayName("Secondary Contact Prefix")]
    public override Salutations Prefix { get; set; }

    [DisplayName("Secondary Contact First Name")]
    public override string FirstName { get; set; }

    [DisplayName("Secondary Contact Last Name")]
    public override string LastName { get; set; }

    [DisplayName("Secondary Contact Job Title")]
    public override string JobTitle { get; set; }

    [DisplayName("Secondary Contact Email Address")]
    public override string Email { get; set; }

    [DisplayName("Secondary Contact Phone")]
    public override string Phone { get; set; }

    [DisplayName("Secondary Contact Extension")]
    public override string PhoneExtension { get; set; }

}

Method that does the EF saving

public async Task<bool> CreateBusinessAsync(BusinessViewModel businessViewModel)
    {
        try
        {
            // TODO: Move mapping to some common place?
            Mapper.CreateMap<BusinessViewModel, Business>();
            Mapper.CreateMap<BaseContactViewModel, Contact>();
            Mapper.CreateMap<AddressViewModel, Address>();


            Business business = Mapper.Map<Business>(businessViewModel);

            //TODO: See why EntityFramework isn't automatically putting in GUIDs
            business.Id = Guid.NewGuid();
            business.Address.Id = Guid.NewGuid();
            business.PrimaryContact.Id = Guid.NewGuid();


            // Attach the objects so they aren't saved as new entries
            db.States.Attach(business.Address.State);
            db.Countries.Attach(business.Address.Country);

            //TODO: See why entity framework isn't automatically saving records
            var bus = db.Businesses.Add(business);
            var primary = db.Contacts.Add(business.PrimaryContact);

            if (!String.IsNullOrEmpty(business.SecondaryContact.FirstName))
            {
                business.SecondaryContact.Id = Guid.NewGuid();
                db.Contacts.Add(business.SecondaryContact);
            }
            else
            {
                business.SecondaryContact = null;
            }


            var address = db.Addresses.Add(business.Address);

            int rowsAffected = await db.SaveChangesAsync();


            if (bus != null && rowsAffected > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        catch (Exception e)
        {
            //TODO: Add exception logger
            string error = e.Message;

            return false;
        }

    }

Please let me know if it would be useful to see any other classes. Thanks.

I had a similar issue before and the way I fixed it was by taking the foreign key data annotation off of the property and using fluent-api. Using fluent-api I just labeled the correct side of the relationship as optional.

You can just make the data type to nullable based on documentation

public class Foo {
// ...... Some property
[ForeignKey("tagId")]
public ICollection<SomeModel>? data {get; set;}
}

This will make the property not validated and return validated result. Using Model.IsValid

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM