I'm using AutoMapper to map Domain objects to ViewModel objects in my controller. Everything was working fine until I tried adding double? properties. I've started getting the following error:
Missing type map configuration or unsupported mapping.
Mapping types:
Address -> AddressModel
Domain.Address -> Web.Models.AddressModel
Destination path:
AccountAddressModel.Address.Address
Source value:
Domain.Address
My Address class and AddressModel class both have two properties called Longitude and Latitude. These properties (in both classes) are defined as double?. If I comment out these properties, everything works fine. If I make all of these properties simply double, then everything works fine. It's only double? that causes the problem.
I'm using AutoMapper 2.2.1 downloaded via NuGet.
I've read in other posts that this problem with nullables was supposed to be fixed. This leads me to believe I might be doing something different so I'm going to post my code to see if anyone can see something that might be an issue:
DOMAIN MODELS
public class AccountAddress
{
public int Id { get; set; }
public int AccountId { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
...
}
public class Address : IUserTrackingEntity
{
public int Id { get; set; }
public string Addressee { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
...
public double? Longitude { get; set; }
public double? Latitutde { get; set; }
}
VIEW MODELS
public class AccountAddressEditModel
{
public string AccountName { get; set; }
public AccountAddressModel Address { get; set; }
public IList<Country> CountriesList { get; set; }
...
}
public class AccountAddressModel
{
public AccountAddressModel()
{
Address = new AddressModel();
}
public int AccountId { get; set; }
public int AddressId { get; set; }
public int Id { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public int Id { get; set; }
public string Addressee { get; set; }
[Required(ErrorMessage="A street address is required.")]
public string Street1 { get; set; }
public string Street2 { get; set; }
...
public double? Longitude { get; set; }
public double? Latitude { get; set; }
}
MAPPING CODE IN MY CONTROLLER
//Get an AccountAddress
address = _context.AccountAddresses.SingleOrDefault(ad => ad.Id == 12345);
model = new AccountAddressEditModel();
Mapper.CreateMap<AccountAddress, AccountAddressModel>();
Mapper.CreateMap<AccountAddress, AddressModel>();
Mapper.Map(address, model.AccountAddress);
Has anyone else experienced this or found a solution for this?
The line
Mapper.CreateMap<AccountAddress, AddressModel>();
should be changed to
Mapper.CreateMap<Address, AddressModel>();
This because you are mapping the Address class to the AddressModel class.
Related
I am using ValueInjecter to map domain classes to my view models. My domain classes are complex. To borrow an example from this question:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
// VIEW MODEL
public class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int PersonId { get; set; }
public int AddressId { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
I have looked at FlatLoopInjection, but it expects the view model classes to be prefixed with nested domain model type like so:
public class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Id { get; set; }
public int AddressId { get; set; }
public string AddressCity { get; set; }
public string AddressState { get; set; }
public string AddressZip { get; set; }
}
The OP in the linked question altered his view models to match the convention expected by FlatLoopInjection. I don't want to do that.
How can I map my domain model to the original unprefixed view model? I suspect that I need to override FlatLoopInjection to remove the prefix, but I am not sure where to do this. I have looked at the source for FlatLoopInjection but I am unsure if I need to alter the Match method or the SetValue method.
you don't need flattening, add the map first:
Mapper.AddMap<Person, PersonViewModel>(src =>
{
var res = new PersonViewModel();
res.InjectFrom(src); // maps properties with same name and type
res.InjectFrom(src.Address);
return res;
});
and after that you can call:
var vm = Mapper.Map<PersonViewModel>(person);
I have data structure for organisation As follows:
public class Organisation
{
public int OrganisationId { get; set; }
[Required]
public string OrganisationCode { get; set; }
[Required]
public string Name { get; set; }
public ICollection<OrganisationSite> Sites { get; set; }
public string TelNo { get; set; }
public int? AddressId { get; set; }
public Address Address { get; set; }
}
An address is stored as its' own entity:
public class Address
{
public int AddressId { get; set; }
[Required]
public string HouseNoName { get; set; }
public string Street { get; set; }
public string Locality { get; set; }
public string TownCity { get; set; }
public string County { get; set; }
[Required]
[RegularExpression("^[a-zA-Z]{1,2}[0-9][0-9A-Za-z]{0,1} {0,1}[0-9][A-Za-z]{2}$", ErrorMessage="Postcode must be recognisable UK postal code")]
public string Postcode { get; set; }
[Required]
public int AddressTypeId { get; set; }
public AddressType Type { get; set; }
}
Address will be used in many places thoughout an application so sounds like I need a partial view that I can reuse? In my OrganisationController I am going to make a trip to my OrganisationRepository and include the Address entity(entity framework):
public ActionResult Edit(int id = 1)
{
var organisation = _db.Organisation.Include("Address").Single(og => og.OrganisationId == id);
if (organisation == null)
{
return HttpNotFound();
}
return View(organisation);
}
How do I render a partial address view in my Organisation Edit view? I have had a quick look and seen reference to EditorTemplates, is this the approach I need to take, if so do you know of any decent tutorials as the ones I've seen appear fairly deep?
I can't understand what i'm doing wrong. Every time I'm getting this error:
The entity or complex type 'BusinessLogic.CompanyWithDivisionCount' cannot be constructed in a LINQ to Entities query.
I need to get info from 'Company' table and divisions count of each company from 'Division' table, and then make PagedList. Here is my 'Company' table:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using BusinessLogic.Services;
using BusinessLogic.Models.ValidationAttributes;
namespace BusinessLogic.Models
{
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
}
Here is my domain model:
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
public class CompanyWithDivisionCount: Company // I'm using this
{
public int DivisionCount { get; set; }
}
Here is my controller:
public ActionResult CompaniesList(int? page)
{
var pageNumber = page ?? 1;
var companies = companyService.GetCompaniesWithDivisionsCount2();
var model = companies.ToPagedList(pageNumber, PageSize);
return View(model);
}
And here is my service part:
public IQueryable<CompanyWithDivisionCount> GetCompaniesWithDivisionsCount2()
{
return (from c in dataContext.Companies.AsQueryable()
select new CompanyWithDivisionCount
{
Id = c.Id,
Name = c.Name,
Status = c.Status,
EffectiveDate = c.EffectiveDate,
URL = c.URL,
EAP = c.EAP,
EAPCredentials = c.EAPCredentials,
Comments = c.Comments,
DivisionCount = (int)dataContext.Divisions.Where(b => b.CompanyName == c.Name).Count()
});
}
}
Thanks for help!!!
Creator of PagedList here. This has nothing to do with PagedList, but rather is an Entity Framework issue (I'm no expert on Entity Framework, so can't help you there). To confirm that this is true, write a unit test along the following lines:
[Test]
public void ShouldNotThrowAnException()
{
//arrange
var companies = companyService.GetCompaniesWithDivisionsCount2();
//act
var result = companies.ToList();
//assert
//if this line is reached, we win! no exception on call to .ToList()
}
I would consider changing you data model if possible so that instead of relating Companies to Divisions by name strings, instead use a properly maintained foreign key relationship between the two objects (Divisions should contain a CompanyID foreign key). This has a number of benefits (including performance and data integrity) and will almost certainly make your life easier moving forward if you need to make further changes to you app (or if any company ever decides that it may re-brand it's name).
If you create a proper foreign key relationship then your domain model could look like
public class Company
{
...
public virtual ICollection<Division> Divisions{ get; set; }
public int DivisionCount
{
get
{
return this.Divisions.Count()
}
}
...
}
I have multiple classes that I need to map into 1 class:
This is the source that I'm mapping from(view model):
public class UserBM
{
public int UserId { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string State { get; set; }
public int CountryId { get; set; }
public string Country { get; set; }
}
This is how the destination class is(domain model):
public abstract class User
{
public int UserId { get; set; }
public virtual Location Location { get; set; }
public virtual int? LocationId { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string State { get; set; }
public virtual int CountryId { get; set; }
public virtual Country Country { get; set; }
}
This is how my automapper create map currently looks:
Mapper.CreateMap<UserBM, User>();
Based on the documents on automapper codeplex site, this should be automatic but it doesn't work. Address, Address2, etc is still null. What should my createmap look like?
The reason is because AutoMapper can't map all those flat fields to a Location object by convention.
You'll need a custom resolver.
Mapper.CreateMap<UserBM, User>()
.ForMember(dest => dest.Location, opt => opt.ResolveUsing<LocationResolver>());
public class LocationResolver : ValueResolver<UserBM,Location>
{
protected override Location ResolveCore(UserBMsource)
{
// construct your object here.
}
}
However, i dont like this. IMO, a better way would be to encapsulate those properties in your ViewModel into a nested viewmodel:
public class UserBM
{
public int UserId { get; set; }
public LocationViewModel Location { get; set; }
}
Then all you have to do is define an additional map:
Mapper.CreateMap<User, UserBM>();
Mapper.CreateMap<LocationViewModel,Location>();
Then it will all work.
You should try and make use of AutoMapper conventions, where possible. And it's certainly possible to make your ViewModel more hierachical, to match the destinations hierachy.
EDIT strange this question has been asked by you.. seems to be a same question - i guess i am missing something...
Check this SQ Question
*
Define two mappings, both mapping from the same source to different
destinations
*
I think you need to make the property names similar to LocationAddress and LocationAddress2 on UserBM for their automatic projection to work, but I may be wrong.
Check out their page on Flattening they have property names that have both property names of the source concatenated like I indicated.
Simply follow the naming convention in your target class and prefix the address properties with Location since that's the property name in the source class:
public class UserBM
{
public int UserId { get; set; }
public string LocationAddress { get; set; }
public string LocationAddress2 { get; set; }
public string LocationAddress3 { get; set; }
public string LocationState { get; set; }
public int CountryId { get; set; }
public string Country { get; set; }
}
I am using EF4 CTP5. Here are my POCOs:
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public decimal Total { get; set; }
public Address ShippingAddress { get; set; }
public Address BillingAddress { get; set; }
}
Is there a way to get Address to be a ComplexType for the Order class? After playing around with this, I'm guessing not, but maybe there's a way I haven't seen.
EDIT: In response to Shawn below, I gave it my best shot:
//modelBuilder.Entity<Order>().Ignore(o => o.BillingAddress);
//modelBuilder.Entity<Order>().Ignore(o => o.ShippingAddress);
modelBuilder.Entity<Order>()
.Property(o => o.BillingAddress.City).HasColumnName("BillingCity");
Fails at runtime with error "The configured property 'BillingAddress' is not a declared property on the entity 'Order'." Trying to use Ignore() doesn't work. Next, the Hanselman article is CTP4, but the CTP5 equivalent is:
modelBuilder.Entity<Order>().Map(mapconfig =>
{
mapconfig.Properties(o => new {
o.Id
, o.Total
, o.BillingAddress.City
});
mapconfig.ToTable("Orders");
});
Fails with error "Property 'BillingAddress.City' of type 'Order' cannot be included in its mapping."
I give up. Maybe the final release will have something like this. Or maybe I need to switch to NHibernate =)
All you need to do is to place ComplexTypeAttribute on Address class:
[ComplexType]
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
Alternatively, you can achieve this by fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Address>();
}
But you cannot have Address type as to be both an Entity and a Complex Type, it's one way or another.
Take a look at this blog post where I discuss this at length:
Associations in EF Code First CTP5: Part 1 – Complex Types
If you want Address to be in the same table as Order, you're going to have to tell EF that in the DbContext OnModelCreating override.
Take a look here: http://weblogs.asp.net/scottgu/archive/2010/07/23/entity-framework-4-code-first-custom-database-schema-mapping.aspx