I have an EF 4.1 model, two tables are generated PERSON and ADDRESS from my database.
//This method works
public void Update(IPerson person)
{
var personDb = _dataContext.PERSON.SingleOrDefault(x => x.ID == person.Id);
Mapper.Map<Person, PERSON>((Person)person, personDb);
_dataContext.SaveChanges();
}
But when I remove the .Ignore() in Automapper mapping, I get this exception :
The EntityCollection could not be initialized because the relationship manager for the object to which the EntityCollection belongs is already attached to an ObjectContext. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.
I'd like when I added an new address to the existing addresses save the person and address.
Any idea ?
Thanks,
public void AutomapperInit()
{
Mapper.CreateMap<Person, PERSON>()
.ForMember(x => x.ADDRESS, opt => opt.Ignore());
Mapper.CreateMap<PERSON, Person>()
.ForMember(dest => dest.Address, option => option.MapFrom(src => src.ADDRESS.Select(address => Mapper.Map<ADDRESS, Address>(address)).ToList()));
Mapper.CreateMap<Address, ADDRESS>();
Mapper.CreateMap<ADDRESS, Address>()
.ForMember(dest => dest.Rue, option => option.MapFrom(src => src.STREET));
}
public interface IPerson
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
ICollection<IAddress> Address { get; set; }
}
public interface IAddress
{
string Rue { get; set; }
string Number { get; set; }
int PersonId { get; set; }
}
class Person : IPerson
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<IAddress> Address { get; set; }
}
class Address : IAddress
{
public string Rue { get; set; }
public string Number { get; set; }
public int PersonId { get; set; }
}
Does
var personDb = Mapper.Map<Person, PERSON>((Person)person, personDb);
_dataContext.Attach(personDb);
_dataContext.SaveChanges();
give the results you expect, or am I misinterpreting your question?
Related
I'm trying to join three tables in a view model. It works with two tables but crashes when I add a third. Here are the models and the controller. The models section_detail, phone, and department were generated by Entity Framework.
EmployeeViewModel was created by copying properties from the other models. I've abbreviated some of the models shown here with:
public partial class section_detail
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public virtual department department { get; set; }
public virtual phone phone { get; set; }
}
public partial class phone
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public phone()
{
this.section_detail = new HashSet<section_detail>();
}
public int phone_id { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
. . .
public string activity_code { get; set; }
public string function_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
public virtual BudgetUnit BudgetUnit { get; set; }
}
public partial class department
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public department()
{
this.section_detail = new HashSet<section_detail>();
}
public int dept_id { get; set; }
public string description { get; set; }
public string cost_center_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
}
public class EmployeeViewModel
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
public Nullable<byte> page_code { get; set; }
public string cost_center_code { get; set; }
public string print_descrip { get; set; }
public Nullable<int> phone_id { get; set; }
public Nullable<int> employee_id { get; set; }
public static explicit operator EmployeeViewModel(List<section_detail> v)
{
throw new NotImplementedException();
}
public string first_name { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public string description { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
public string BU { get; set; }
}
Controller:
private vcpds_test1Entities db = new vcpds_test1Entities();
// GET: EmployeeList
public ActionResult Index()
{
List<section_detail> employeeList = db.section_detail.ToList();
List<EmployeeViewModel> employeeVMList = employeeList.Where(emp => emp.page_code == 3)
.Select(emp => new EmployeeViewModel
{
last_name = emp.last_name,
first_name = emp.first_name,
employee_id = emp.employee_id,
phone_nbr = "(" + emp.phone.area_code + ") " + emp.phone.phone_nbr.Substring(0, 3) + "-" + emp.phone.phone_nbr.Substring(3, 4),
BU = emp.phone.BU,
description = emp.department.description,
page_code = emp.page_code
}).OrderBy(emp => emp.last_name).ThenBy(emp => emp.first_name).ToList();
return View(employeeVMList);
}
I get these messages:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
VCPDS2.Models.section_detail.department.get returned null.
If I comment out description = emp.department.description from the controller, then it will return data from the section_detail and phone tables. I've checked the database and the relationships seem ok. I've tried refreshing the models from the database with no change.
It's possible that a emp doesn't have a department so it in itself is null. Description can't be a property of a null. So, what you can simply do is check if it is null first by using null operator:
...
//description = emp.department.description,
description = emp.department?.description ?? "",
...
Basically, if department itself is null, it will stop checking right there, and the ?? shortcut is to use the statement on the right side which is "" if the statement on the left is null.
If you were not expecting an emp not to have a department, you may need to revise your query
Quick edit: You probably need to use an Include in your query so it can bring the department's properties (for description):
List<section_detail> employeeList = db.section_detail
.Include(x => x.department)
.ToList();
In my MVC5 project I use automapper to map my viewmodels to my models. But it seems that I'm doing something wrong, because not all my properties are mapped.
Here is my View Model
public class PlanboardViewModel
{
public int Id { get; set; }
[Display(Name = "Titel")]
public string Title { get; set; }
[Display(Name = "Omschrijving")]
public string Description { get; set; }
[Display(Name = "Verzoektype")]
public int AbsenceTypeId { get; set; }
public List<PlanboardEventMapViewModel> EventMap { get; set; }
public List<PlanboardEventDetail> EventDetails { get; set; }
public List<PlanboardRequest> PlanboardRequests { get; set; }
}
I use a separate class for my profiles:
public class PlanboardMappingProfile : Profile
{
protected override void Configure()
{
//CreateMap<AbsenceType, AbsenceTypeViewModel>();
//.ForAllMembers(opt => opt.Condition(s => !s.IsSourceValueNull));
CreateMap<PlanboardViewModel, Planboard>()
.ForMember(dest => dest.CSVHPlanboardEventDetail, opt => opt.MapFrom(src => src.EventDetails))
.ForMember(dest => dest.CSVHPlanboardEventMap, opt => opt.MapFrom(src => src.EventMap))
.ForMember(dest => dest.CSVHPlanboardRequest, opt => opt.MapFrom(src => src.PlanboardRequests));
CreateMap<PlanboardEventMapViewModel, PlanboardEventMap>();
}
}
In my repository I have the following code:
public int Create(PlanboardViewModel planboardViewModel)
{
try {
// map the viewmodel to the planboard model
// Map the planboards to the view model
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<PlanboardMappingProfile>();
cfg.CreateMap<PlanboardViewModel, Planboard>();
cfg.CreateMap<PlanboardEventMapViewModel, PlanboardEventMap>();
});
IMapper mapper = config.CreateMapper();
Planboard planboard = mapper.Map<Planboard>(planboardViewModel);
// Some more code here
When I submit the page and use the debugger, It shows that planboardViewModel has a list of values for PlanboardEventMapViewModel, PlanboardEventDetail, PlanboardRequest. The system is not telling me that there are any errors. When I check planboard after the mapping, it does not show any values for CSVHPlanboardEventDetail, CSVHPlanboardEventMap and CSVHPlanboardRequest.
EDIT
My PlanboardModel is created DB First EF6:
public partial class Planboard
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Planboard()
{
this.CSVHPlanboardEventDetail = new HashSet<PlanboardEventDetail>();
this.CSVHPlanboardEventMap = new HashSet<PlanboardEventMap>();
this.CSVHPlanboardRequest = new HashSet<PlanboardRequest>();
}
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int StatusID { get; set; }
public Nullable<int> AbsenceTypeID { get; set; }
public virtual AbsenceType AbsenceTypes { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardEventDetail> CSVHPlanboardEventDetail { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardEventMap> CSVHPlanboardEventMap { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardRequest> CSVHPlanboardRequest { get; set; }
}
I'm trying to use Automapper for a many-to-many relationship and entity framework.
This is my entity:
public class ZChangeUnits
{
[Key]
public string CCode1 { get; set; }
[ForeignKey("CCode1")]
public virtual ZUnits ZUnits1 { get; set; }
[Key]
public string CCode2 { get; set; }
[ForeignKey("CCode2")]
public virtual ZUnits ZUnits2 { get; set; }
public decimal NZarib { get; set; }
}
And this is ZUnit entity:
[Key]
public string CCode { get; set; }
public string CTitle { get; set; }
public string CMGCode { get; set; }
public virtual ZCodes ZCodes { get; set; }
I want to map them to this ViewModel, probably I should remove Titles:
public class ChangeUnitsViewModel
{
public string CCode1 { get; set; }
public string ZUnitsCTitle1 { get; set; }
public string CCode2 { get; set; }
public string ZUnitsCTitle2 { get; set; }
public decimal NZarib { get; set; }
}
And this is the mapping I'm currently using:
Mapper.CreateMap<ZChangeUnits, ChangeUnitsViewModel>()
.ForMember(d => d.ZUnitsCTitle1,
o => o.MapFrom(s => s.ZUnits1.CTitle))
.ForMember(d => d.ZUnitsCTitle2,
o => o.MapFrom(s => s.ZUnits2.CTitle));
But it doesn't seem valid and gives the Missing type map configuration or unsupported mapping. exception.
Can you please guide me about how I can use Automapper to perform a basic CRUD operation on this class? Any useful link, I have no idea how to use it.
To make this really complete, this is my controller!
var data = exchangeRepo.GetAll().ToList();
var d = AutoMapper.Mapper.Map<ChangeUnitsViewModel>(data);
i have a view model like this:
public class CityModel
{
public int Id { get; set; }
[Required]
public int ProvinceId { get; set; }
[Required]
public string Caption { get; set; }
public string Description { get; set; }
}
and an entity:
public class City : BaseEntity
{
public int ProvinceId { get; set; }
public string Caption { get; set; }
public string Description { get; set; }
public virtual Province Province { get; set; }
}
and a BaseEntity:
public abstract class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedOn { set; get; }
public string CreatedBy { set; get; }
public DateTime ModifiedOn { set; get; }
public string ModifiedBy { set; get; }
}
i want to map one object of type CityModel to on object of type City In Edit Action using AutoMapper ,
[HttpPost]
public ActionResult Edit(CityModel model)
{
if (ModelState.IsValid)
{
var entity = _cityRepository.GetCity(model.Id);
entity = model.ToEntity();
var operationStatus = _cityRepository.Edit(entity);
if (operationStatus.IsSuccess) operationStatus = _cityRepository.Save();
if (operationStatus.IsSuccess)
RedirectToAction("Index");
}
ViewBag.ProvinceId = new SelectList(_provinceRepository.Provinces, "Id", "Caption", model.ProvinceId);
return View(model);
}
The ToEntity is :
public static City ToEntity(this CityModel model)
{
return Mapper.DynamicMap<CityModel, City>(model);
}
and finally create map from CityModel To City Uses this Code:
Mapper.CreateMap<CityModel, City>()
.ForMember(des => des.Caption, op => op.MapFrom(src => src.Caption.ToPersianContent()))
.ForMember(des => des.Description, op => op.MapFrom(src => src.Description.ToPersianContent()));
when i want to map from CityModel To City , get City From Database. inherited data in city object are correct :
and After Mapping , i want to keep original inherited mapping and automapper ignore mapping this base properties, but null and default values are set to this properties:
I believe you are calling the wrong overload in "ToEntity". You have to pass in the existing entity:
DynamicMap<TSource, TDestination>(TSource source, TDestination destination)
I have two model classes in one-to-one relationship:
class Person
{
public int PersonID { get; set; }
public int DetailPersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DetailPerson DetailPerson { get; set; }
}
class DetailPerson
{
public int DetailPersonID { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
and the code for the edit page view:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.PersonID)
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
#Html.LabelFor(m => m.DetailPerson.Address)
#Html.TextBoxFor(m => m.DetailPerson.Address)
#Html.LabelFor(m => m.DetailPerson.PhoneNumber)
#Html.TextBoxFor(m => m.DetailPerson.PhoneNumber)
<input type="submit" value="Edit">
}
The EF scaffold uses this code to update data:
db.Entry(person).State = System.Data.EntityState.Modified;
db.saveChanges();
When I submit the edit form, I got an error like this:
A foreign key value cannot be inserted because a corresponding primary key value does not exist. [ Foreign key constraint name = FK_dbo.People_dbo.DetailPersons_DetailPersonID ]
But if I do this:
Person p = db.Persons.Find(person.PersonID);
p.DetailPerson = person.DetailPerson;
p.FirstName = person.FirstName;
p.LastName = person.LastName;
db.saveChanges();
update data success without error
I want to know why the first way causse an error,
when I set the breakpoint at the line containing EntityState.Modified,
but the foreign key value ( DetailPersonID ) is 0.
Then, I added #Html.HiddenFor(m => m.DetailPersonID) on the edit form.
I got another error:
A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
I still update database on the other way,
I am just curious why the first way which is EF standart to update data got an error.
You shouldn't use two classes if there is a one-to-one relationship since EF will normalize the db into the corresponding form anyway. Combine your classes like this.
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
if you must have separate classes (not recommended), do it like this:
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int DetailPersonID { get; set; }
public virtual DetailPerson DetailPerson { get; set; }
}
public class DetailPerson
{
public int PersonID { get; set; }
public virtual Person Person { get; set; }
public int DetailPersonID { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
Then when you retrieve your object, do it like this:
// include the detail when retrieving the parent
Person person = db.People.Include(p=>p.DetailPerson).Single(p=>p.PersonId == whateverIdYou Need);
// modify whatever you like
person.DetailPerson.Address = "my new address";
// then your previous statement will work
db.Entry(person).State = System.Data.EntityState.Modified;
db.saveChanges();