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.