I am developing an ASP.Net MVC 3 Web application using Entity Framework 4.1 and also Automapper to map properties from my Objects to ViewModels and vice-versa.
I have the following class called Shift
public partial class Shift
{
public Shift()
{
this.Locations = new HashSet<ShiftLocation>();
}
public int shiftID { get; set; }
public string shiftTitle { get; set; }
public System.DateTime startDate { get; set; }
public System.DateTime endDate { get; set; }
public string shiftDetails { get; set; }
public virtual ICollection<ShiftLocation> Locations { get; set; }
}
And a ViewModel called ViewModelShift
public class ViewModelShift
{
public int shiftID { get; set; }
[DisplayName("Shift Title")]
[Required(ErrorMessage = "Please enter a Shift Title")]
public string shiftTitle { get; set; }
[DisplayName("Start Date")]
[Required(ErrorMessage = "Please select a Shift Start Date")]
public DateTime startDate { get; set; }
[DisplayName("End Date")]
[Required(ErrorMessage = "Please select a Shift End Date")]
public DateTime endDate { get; set; }
[DisplayName("Shift Details")]
[Required(ErrorMessage = "Please enter detail about the Shift")]
public string shiftDetails { get; set; }
[DisplayName("Shift location")]
[Required(ErrorMessage = "Please select a Shift Location")]
public int locationID { get; set; }
public SelectList LocationList { get; set; }
}
I then have the following code in a Controller
[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
if (ModelState.IsValid)
{
Shift shift = _shiftService.GetShiftByID(model.shiftID);
shift = Mapper.Map<ViewModelShift, Shift>(model);
}
}
Which works fine, when the variable 'shift' is first populated with Shift details, lazy loading also loads the related collection of 'Locations'.
However, once the mapping has taken place, shift.Locations then equals to 0. Is there anyway to setup AutoMapper, that it just maps over the properties in the ViewModel class to the shift without removing the collection of Locations?
Thanks as ever everyone.
Automapper has the ability to designate options for specific members of a created map.
https://github.com/AutoMapper/AutoMapper/wiki/Configuration-validation
Try this:
Mapper.CreateMap<ViewModelShift, Shift>()
.ForMember(dest => dest.Locations, opt => opt.Ignore());
Related
I have two Controllers and their views. The first controller is the applicant controller, In this applicant controller, I have the "add applicant" form. This form link is given to the Second controller(vacancycontroller) view. Now I want to get data on the vacancy positions when I click on the button. Is this possible?
public class Application : IVacancy, IStage
{
[Key]
public int? Id { get; set; }
[Required]
[MinLength(2)]
[MaxLength(50)]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
public string Phone { get; set; }
public string Gender { get; set; }
public string Address { get; set; }
public int? Experience { get; set; }
public string Status { get; set; }
public string Resume { get; set; }
[Display(Name = "Vacancy")]
public int? VacancyId { get; set; }
[Display(Name = "Stage")]
public int? StageId { get; set; }
public DateTime DateCreated { get; set; }
[ForeignKey("VacancyId")]
public virtual Vacancy Vacancy { get; set; }
}
This is my application entity.
public interface IVacancy
{
public int? VacancyId { get; set; }
public Vacancy Vacancy { get; set; }
}
[Table("vacancy")]
public class Vacancy
{
[Key]
public int? Id { get; set; }
[Required]
[MinLength(2)]
[MaxLength(50)]
public string Position { get; set; }
[Required]
public string Skills { get; set; }
}
And this is vacancy entity.
When I open the vacancy page and click on add applicant, In add applicant form, VaacancyId Text box fills with the Position of that particular row.This is vacancy page img
I don't really understand what's happening here. From my research I gather that entity is trying to guess the name of a foreign key and it's not there, so it's throwing an "Invalid Column Name" error. The problem is that the model/table in question does not have any foreign keys. So I'm really confused.
Here's the code that throws the error:
foreach (TechnologyProjectPlanModel result in results)
{
//get approvers for plan
int id = result.Id;
try
{
List<ApprovalModel> approvers = db.ApprovalModels.Where(m => m.FormId == result.Id).Select(m => m).ToList(); //ERROR HERE
if (approvers != null)
{
result.Approvers = approvers.ToList();
}
}
catch (Exception e)
{
}
}
Here's the ApprovalModel:
public class ApprovalModel
{
[Key]
public int Id { get; set; }
public int ApprovalProcessId { get; set; }
public int FormId { get; set; }
public int UserId { get; set; }
public bool? Approved { get; set; }
}
And here's the TechnologyProjectPlanModel referenced in the foreach loop:
public class TechnologyProjectPlanModel
{
[Key]
public int Id { get; set; }
public int FormId { get; set; }
public int UserId { get; set; }
public string FormType { get; set; }
public int Status { get; set; }
public int Hidden { get; set; }
public DateTime DateSubmitted { get; set; }
public DateTime DateFinalized { get; set; }
public List<QuoteUploadsModel> Quotes { get; set; }
public List<ApprovalModel> Approvers { get; set; }
[Required]
[Display(Name = "Please Select Your School")]
public string School { get; set; }
[Required]
[Display(Name = "Requestor")]
public string Requestor { get; set; }
[Display(Name = "Title")]
public string Title { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
[Display(Name = "Phone Number")]
[Phone]
public string PhoneNumber { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email Address")]
[EmailAddress]
public string Email { get; set; }
[Required]
[Display(Name = "Project Title")]
public string ProjectTitle { get; set; }
[Display(Name = "Requested Completion Date")]
public DateTime RequestedCompletionDate { get; set; }
[Required]
[Display(Name = "Project Description")]
public string ProjectDescription { get; set; }
[Display(Name = "Teacher/Room Number")]
public string TeacherGroup { get; set; }
[Display(Name = "1")]
public bool Grade1 { get; set; }
[Display(Name = "2")]
public bool Grade2 { get; set; }
[Display(Name = "3")]
public bool Grade3 { get; set; }
[Display(Name = "4")]
public bool Grade4 { get; set; }
[Display(Name = "5")]
public bool Grade5 { get; set; }
[Display(Name = "6")]
public bool Grade6 { get; set; }
[Display(Name = "7")]
public bool Grade7 { get; set; }
[Display(Name = "8")]
public bool Grade8 { get; set; }
[Display(Name = "9")]
public bool Grade9 { get; set; }
[Display(Name = "10")]
public bool Grade10 { get; set; }
[Display(Name = "11")]
public bool Grade11 { get; set; }
[Display(Name = "12")]
public bool Grade12 { get; set; }
[Display(Name = "Kindergarten")]
public bool Kindergarten { get; set; }
[Display(Name = "Describe how this plan will be continued if events cause programs or equipment to no longer be available. For example, if equipment purchased needs repair what funding source will be used for repair or replacement? For programs that have annual subscription fees, what funds will be used to continue the program from year to year?")]
public string Sustainability { get; set; }
public bool MultipleFundingSource { get; set; }
[Required(ErrorMessage="*")]
[Display(Name = "Funding Source")]
public string FundingSource1 { get; set; }
[Required(ErrorMessage = "*")]
public string FundingSource2 { get; set; }
[Required(ErrorMessage = "*")]
public string FundingSource3 { get; set; }
[Required(ErrorMessage = "*")]
public string FundingSource4 { get; set; }
[Required(ErrorMessage = "*")]
public string FundingSource5 { get; set; }
[Required]
[Display(Name = "Total Estimated Project Costs:")]
public float TotalEstimatedProjectCosts { get; set; }
//----------------------Additional Information
[Display(Name = "Additional Comments:")]
public string AdditionalComments { get; set; }
[Display(Name = "Additional Supporting Documents:")]
public string AdditionalSupportingDocuments { get; set; }
}
The exact error is:
An error occurred while executing the command definition. See the inner exception for details.
Inner Exception:
Invalid column name 'TechnologyProjectPlanModel_Id'.
That column name isn't referenced anywhere in my code, so it must be inferring it from something.
Any ideas? Thanks!
Entity Framework relies on conventions to determine what it thinks your DB looks like. In this case, it thinks the ApprovalModel table should have a foreign key to the TechnologyProjectPlanModel table. Trimming down your entities to the relevant fields, it becomes apparent why it thinks this:
public class ApprovalModel
{
}
public class TechnologyProjectPlanModel
{
public List<ApprovalModel> Approvers { get; set; }
}
In database terms, the relationship that would most likely exist in order for there to be multiple ApprovalModel's per TechnologyProjectPlanModel would be for ApprovalModel's to have a foreign key to TechnologyProjectPlanModel's.
How does TechnologyProjectPlanModel.Approvers get set? If it doesn't have anything to do with Entity Framework and you don't want it to try populating this property via it's conventions, you can explicitly tell it not to make that assumption by telling it the property is not mapped like so:
[NotMapped]
public List<ApprovalModel> Approvers { get; set; }
If you do have this relationship, you need to give EF some more context so it doesn't make best guess assumptions. For example, if the foreign key does exist, place it in ApprovalModel along with the relevant navigation property that can further tell EF what your DB looks like:
public class ApprovalModel
{
public int TechnologyProjectPlanModelId { get; set; }
// ForeignKey attribute usually not necessary unless you need to tell EF
// about a property that doesn't follow the usual "{OtherEntityName}Id"
// naming convention.
[ForeignKey("TechnologyProjectPlanModelId")]
public TechnologyProjectPlanModel TechnologyProjectPlanModel { get; set; }
}
public class TechnologyProjectPlanModel
{
public List<ApprovalModel> Approvers { get; set; }
}
You know if you do the mapping correctly, that block of code is unnecessary because EF will retrieve results.Approvers for you.
You can use the Attribute based mapping directly on your model or you can use the Code First Configuration to map your foreign key properly.
Once you've properly mapped the foreign key, the block of code you pasted is unnecessary, EF does the load for you.
I have seen plenty of examples of this error occuring, for a wide variety of causes and I have gone through all the causes I can see, but still i get the error, so I am wondering if some one can give some information about what this error actually means, so i can try finding the cause. Here is some code:
Controller:
[HttpPost]
public ActionResult Edit(ProfileViewModel model)
{
if (ModelState.IsValid)
{
var person = new UserAttribute();
person = Mapper.Map<ProfileViewModel, UserAttribute>(model);
db.UserAttribute.Add(person);
db.SaveChanges();
}
View Model
public class ProfileViewModel
{
[Display(Name = "First Name")]
[StringLength(20)]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(30)]
[Required]
public string LastName { get; set; }
[Display(Name = "Gender")]
[Required]
public string Gender { get; set; }
[Display(Name = "Date of Birth")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
public DateTime DOB { get; set; }
[Display(Name = "Hair Color")]
public string HairColor { get; set; }
[Display(Name = "Eye Color")]
public string EyeColor { get; set; }
[Display(Name = "Body Type")]
public string Weight { get; set; }
[Display(Name = "Height")]
public string HeightFeet { get; set; }
public string HeightInches { get; set; }
public int UserId { get; set; }
public IEnumerable<SelectListItem> WeightList { get; set; }
public IEnumerable<SelectListItem> EyeColorList { get; set; }
public IEnumerable<SelectListItem> HairColorList { get; set; }
public IEnumerable<SelectListItem> HeightFeetList { get; set; }
public IEnumerable<SelectListItem> HeightInchesList { get; set; }
public IEnumerable<SelectListItem> GenderList { get; set; }
}
UserAttribute model:
public int ProfileId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public System.DateTime DOB { get; set; }
public string HairColor { get; set; }
public string EyeColor { get; set; }
public string HeightFeet { get; set; }
public string Weight { get; set; }
public int UserId { get; set; }
public string HeightInches { get; set; }
Mapping config:
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x => x.AddProfile<ViewToDomainMapProfile>());
Mapper.Initialize(x => x.AddProfile<DomainToViewMapProfile>());
}
}
public class ViewToDomainMapProfile : Profile
{
public override string ProfileName
{
get { return "ViewToDomainMapProfile"; }
}
protected override void Configure()
{
Mapper.CreateMap<ProfileViewModel, UserAttribute>()
.ForSourceMember(x => x.GenderList, y => y.Ignore())
.ForSourceMember(x => x.HairColorList, y => y.Ignore())
.ForSourceMember(x => x.EyeColorList, y => y.Ignore())
.ForSourceMember(x => x.WeightList, y => y.Ignore())
.ForSourceMember(x => x.HeightFeetList, y => y.Ignore())
.ForSourceMember(x => x.HeightInchesList, y => y.Ignore());
}
}
and the config is called in the global asax:
AutoMapperConfiguration.Configure();
Using Mapper.AssertConfigurationIsValid(); produces the following exception:
AutoMapper.AutoMapperConfigurationException :
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
==============================================================================================
ProfileViewModel -> UserAttribute (Destination member list)
----------------------------------------------------------------------------------------------
ProfileId
So, you need to add mapping for ProfileId.
Overall, it's a good practice to use Mapper.AssertConfigurationIsValid(); either in your unit tests (you have them, right?), or after your mapper configuration. It'll display detailed information for such a misconfigurations.
For the viewmodel => userattribute
I noticed that ProfileId is a destination property, but not a source property.
public int ProfileId { get; set; }
Do you need to add code to ingore this destination member?
Other:
I might also suggest using or customizing the automapper to map properties that present that match by name exclusively.
Also, when possible, please avoid model names ending in the word Attritribute as by convention this is used almost exclusively for actual attributes. (my apologies for nitpicking)
Using this model:
public class Cases
{
//case data model for call center
//implement lists for all related child tables too
[Key]
public int CasesID { get; set; }
public string CaseNumber { get; set; }
[Required(ErrorMessage = "Customer is Required")]
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
[MaxLength(50)]
public string UserName { get; set; } //get user name from the aspnet membership
[Required(ErrorMessage = "Case Category is Required")]
public int CaseCategoryID { get; set; }
[Required(ErrorMessage = "Technician is Required")]
public int TechnicianID { get; set; }
public virtual Technician Technicians { get; set; }
[Required(ErrorMessage = "Engine Model is Required")]
public int EngineModelID { get; set; }
public virtual EngineModel EngineModel { get; set; }
[MaxLength(50)]
public string BMSWorkorder { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Status is Required")]
public string CaseStatus { get; set; }
[MaxLength(50)]
public string OpenedBy { get; set; }
[Required(ErrorMessage = "Opened Date is Required")]
[DataType(DataType.DateTime)]
public DateTime? OpenedDate { get; set; }
[MaxLength(50)]
public string ClosedBy { get; set; }
[DataType(DataType.DateTime)]
public DateTime? ClosedDate { get; set; }
[MaxLength(50)]
[Required(ErrorMessage="Caller First Name is Required")]
public string CallerFirstName { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Caller Last Name is Required")]
public string CallerLastName { get; set; }
[MaxLength(100)]
public string AdditionalContact { get; set; }
[MaxLength(10)]
[Required(ErrorMessage = "Qualified is Required")]
public string Qualified { get; set; }
public string Description { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "ESN is Required")]
public string ESN { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Mileage is Required")]
public string Mileage { get; set; }
[DataType(DataType.Date)]
public DateTime? DateInService { get; set; }
[MaxLength(50)]
public string ESTR { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "EDS is Required")]
public string EDS { get; set; }
[MaxLength(50)]
public string GensetSerialNumber { get; set; }
[MaxLength(50)]
public string GensetModelNumber { get; set; }
//child Case Notes records
public virtual ICollection<CaseNotes> CaseNotes { get; set; }
//child case attachment records
public virtual ICollection<Attachment> Attachments { get; set; }
//child case complaint records
public virtual ICollection<CaseComplaint> CaseComplaint { get; set; }
//tracking fields
public DateTime? CreatedOn { get; set; }
[MaxLength(50)]
public string CreatedBy { get; set; }
public DateTime? ModifiedOn { get; set; }
[MaxLength(50)]
public string ModifiedBy { get; set; }
}
I am wondering why even though only some of the properties are marked required, the modelstate does not get set valid unless all properties have values when saving.
Am I doing something wrong?
EDIT
Here are my razor elements for the dropdownlist fields in question:
#Html.DropDownList("Qualified", String.Empty)
#Html.ValidationMessageFor(model => model.Qualified)
#Html.DropDownList("EngineModelID", String.Empty)
#Html.ValidationMessageFor(model => model.EngineModelID)
#Html.DropDownList("CaseCategoryID", String.Empty)
#Html.ValidationMessageFor(model => model.CaseCategoryID)
The EngineModelID and CaseCategoryID properties must be nullable integers on your view model if you want to allow empty values. Oooops, you are not using view models.
ASP.NET MVC automatically makes non-nullable types required. You could disable this explicitly in your Application_Start:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
But if you want to do the things properly you should use view models.
The following is absolutely horrible:
#Html.DropDownList("CaseCategoryID", String.Empty)
I guess you have stuffed a SelectList in a ViewBag.CaseCategoryID so the CaseCategoryID does 2 things at the same time: it represents a list and a selected scalar value.
With view models you would use the strongly typed version of those helpers:
#Html.DropDownListFor(x => x.CaseCategoryID, Model.CaseCategories)
where CaseCategories will be an IEnumerable<SelectListItem> property on your view model that the controller would populate.
I'm using FluentHTML (from MvcContrib) to layout my HTML mark-up. I want to use eager unobtrusive client-validation provided by jquery.validate library. I got everything working correctly except for properties of nested ViewModels. Example:
public class RegisterPageViewModel
{
[Required(ErrorMessage = "First Name cannot be empty.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name cannot be empty.")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email cannot be empty.")]
[RegularExpression(#"^\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "Invalid Email Format.")]
public string Email { get; set; }
[Required(ErrorMessage = "Password field cannot be empty.")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required(ErrorMessage = "You have to confirm your password.")]
[Compare("Password", ErrorMessage = "Passwords must match.")]
[DataType(DataType.Password)]
public string PasswordConfirm { get; set; }
[Required]
public AddressDto Address { get; set; }
public bool TermsOfUse { get; set; }
[Required(ErrorMessage = "Nickname is required")]
public string Nickname { get; set; }
public string MiddleName { get; set; }
[Required(ErrorMessage = "Birthdate is required")]
public string Birthdate { get; set; }
public string Phone { get; set; }
[Required(ErrorMessage = "Mobile number is required")]
public string Mobile { get; set; }
public string RakimMelieh { get; set; }
public string ReferralNickname { get; set; }
}
It works perfectly for ALL of the properties except for those inside the Address property, although I decorated the properties of the AddressDto with validation attributes as well:
public class AddressDto
{
public int Id { get; set; }
[Required(ErrorMessage = "Address Name is required.")]
public string Name { get; set; }
public string Country { get; set; }
public string District { get; set; }
public string City { get; set; }
public string Area { get; set; }
[Required(ErrorMessage = "Address Details are required.")]
public string Details { get; set; }
public bool IsDefault { get; set; }
}
The same thing happens for other view models as well where there are nested view models inside them. One thing I noticed when inspecting the input fields FireBug is that they always have the valid class on them, even when they're not really valid (according to the annotations decorating their properties).
Any thoughts how I can solve this problem?