Automapper (C#): Nested mappings not working - c#

I have a simple mapping and it is working but it's not filling in Output.Details.
I am a bit confused, I think it maybe because I am using the source as "Task" for each one.
Mapper.CreateMap<Task, Output>();
Mapper.CreateMap<Task, Output.Details>().ForMember(
dest => dest.Item, opt => opt.MapFrom(src => src.Name));
As far as i know i have to create 2 maps, 1 for the object and 1 for object contained within.
Problem is the source for the OUTPUT and OUTPUT.DETAILS can be found in TASK
I tried delving into Details within the first map and specifying Mapfrom but it gives the following error which is why i must create 2 maps
must resolve to top-level member. Parameter name: lambdaExpression error
IList<Task> tempItems= GetItems();
IList<Output> items =
Mapper.Map<IList<Task>, IList<Output>>(tempItems);
The map works but my property "Item" availble in Output.Details is NULL
What am I doing wrong? Here is my Destination object.
It fills in Name no problem, but nothing inside DETAILS... they are left NULL.
Task is not my class, but I checked it and all values are there to be copied hence Tag has a value and is a STRING.
public class Output
{
public string Name { get; set; }
public Details Summary { get; private set; }
public class Details
{
public string Item{ get; set; }
}
public Output()
{
Summary = new Details();
}
}
EDIT
Here is an example of the Task class.
EDIT
They is a sample vs 2010 project here and it shows exactly the problem.
http://dl.dropbox.com/u/20103903/AutomapperNotWorking.zip
and here is an image showing the issue, as you can see Summary Item is "NULL" but it should contain the NAME from Task.

First off, always use Mapper.AssertConfigurationIsValid(); to make sure your mapping configuration is valid. I added it to your code and it immediately highlighted the problem: You didn't tell Automapper what to do with the Summary property. Since Task doesn't contain a property called Summary, Automapper needs to know what to do with it.
So the problem isn't really how to map a nested class, you just need to tell Automapper what to do with Summary. Here's the Automapper configuration that works for your example:
Mapper.CreateMap<Task, Output>()
.ForMember(d => d.Summary, o => o.MapFrom(t => new Output.Details {Item = t.Name}));
Mapper.AssertConfigurationIsValid();
That's all you need.

for the new version, it can be performed as follow:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<UdtDevQuestion, QuestionViewModel>();
});
config.AssertConfigurationIsValid();

Related

Single Element Using Lookup operator

I am stuck in an issue with MongoDB. I am using C# driver for MongoDB.
So basically I am trying to join Categories document with Users document. Each category document has one property:
public Guid UserId { get; set; }
This property is an ObjectId behind the scenes. I have another property:
public UserDoc User { get; set; }
Now I am trying to fill this User property with all the user details based on the UserId property. This is the code I am trying to achieve this:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDoc>(
usersCollection,
x => x.UserId,
x => x.Id,
x => x.User)
As expected, 'Lookup' is expecting an array of User documents but I have a property referencing a single user object and thus, it throws an error:
An error occurred while deserializing the User property of class TatSat.API.Documents.CategoryDoc: Expected a nested document representing the serialized form of a TatSat.API.Documents.UserDoc value, but found a value of type Array instead.
Can someone help me with this? I am new to Mongo so this is a bit of a pain for me. Kindly note that I am looking for a strongly typed solution and don't want to mess with BsonDocuments if that can be avoided.
Thanks in advance.
So I finally figured out in some ways. Basically I decided to have another class:
public class CategoryDocWithTemporaryData : CategoryDoc
{
public UserDoc[] Users { get; set; }
public static Expression<Func<CategoryDocWithTemporaryData, CategoryDoc>> ToCategoryDoc => c =>
new CategoryDoc
{
Id = c.Id,
//other properties
User = c.Users.First()
};
}
Then I use the lookup as:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDocWithTemporaryData>(
usersCollection,
c => c.UserId,
c => c.Id,
c => c.Users)
.Project(CategoryDocWithTemporaryData.ToCategoryDoc)
This, however needs me to use additional projection where I have to manually select all properties which is something I wanted to avoid. But I think I can live with it until I come across a better approach.
Lookup will always return an array, even if it only has 1 element. If you need just the first (or only) document in that array, use a project/set/addFields stage with arrayElemAt.

Nested Collections Not Working in AutoMapper 5.1

Trying to upgrade to AutoMapper 5.1 from v4.2 and am finding that a collection isn't mapping at runtime - the source object has items in the collection, but the mapped destination property is empty.
Under 4.2, everything worked exactly as expected with the same mapping configuration (save for the MemberList.None in the CreateMap() ctor)
I have DTOs like so
public class GeographicEntity
{
...
}
public class County : GeographicEntity
{
...
}
public class State : GeographicEntity
{
public List<County> Counties { get; } = new List<County>();
}
And viewmodels like so
public class GeographicEntityViewModel
{
...
}
public class CountyViewModel : GeographicEntityViewModel
{
...
}
public class StateViewModel : GeographicEntityViewModel
{
public List<CountyViewModel> Counties { get; } = new List<CountyViewModel>();
}
And Mapping confirmation like so
Mapper.Initialize(configuration =>
{
configuration.CreateMap<GeographicEntity, GeographicEntityViewModel>(MemberList.None);
configuration.CreateMap<County, CountyViewModel>(MemberList.None)
.IncludeBase<GeographicEntity, GeographicEntityViewModel>();
configuration.CreateMap<State, StateViewModel>(MemberList.None)
.IncludeBase<GeographicEntity, GeographicEntityViewModel>();
});
After the Mapper.Map<> call, the Counties collection of the StateViewModel is empty (a list with 0 items) even though the source object has items in its .Counties collection:
var st = new State()
... (initialize the state, including the .Counties list)
var stateViewModel = Mapper.Map<StateViewModel>(st);
Any clues would be appreciated!
After some digging, it turns out that the AutoMapper 5 upgrade introduced some breaking changes. Specifically, the behavior has changed in cases like mine where the destination collection has a getter but no setter. In AutoMapper 4, the default behavior was to use the destination property by default, rather than trying to create a new instance. AutoMapper 5 does NOT do that by default.
The solution is to tell AutoMapper to use the destination value explicitly:
.ForMember(dest => dest.Counties, o => o.UseDestinationValue())
I'm sure there's a good reason for introducing a breaking change like this, but it causes no end of heartache when you've implemented a broad pattern and now have to hunt down and fix every mapped object that might be affected by this change.
I'm almost tempted to just bail on the upgrade and stick with Automapper 4.2, as it did exactly what I needed it to without a lot of extra and unnecessary configuration required.
For more detail, refer to https://github.com/AutoMapper/AutoMapper/issues/1599

NUnit: Automapper<IDataReader, Dto>.ConvertUsing() returns Dto with null properties

I'm using AutoMapper to map from IDataReader to a simple DTO.
I'm able to map the properties when I use ForMember, but not when I use ConstructUsing/ConvertUsing. In this case, all my NUnit tests fail, because the AutoMapper returns a DTO with null properties.
What's interesting is that this behavior does not occur in MSTest: When running the tests under MSTest, the mapping works.
Here's the code:
public class Dto
{
public string Name { get; set; }
public string Value { get; set; }
}
This passes in NUnit and in MSTest:
Mapper.CreateMap<IDataReader, Dto>()
.ForMember(x => x.Name, map => map.MapFrom(reader => reader["Name"]))
.ForMember(x => x.Value, map => map.MapFrom(reader => reader["Value"]));
This passes only in MSTest and returns Dto with null properties in NUnit:
Mapper.CreateMap<IDataReader, Dto>()
.ConvertUsing(Map); // ConstructUsing doesn't work either
private Dto Map(IDataReader reader)
{
return new Dto
{
Name = (string)reader["Name"],
Value = (string)reader["Value"]
};
}
MyTestMethod is not even called in NUnit.
Is this a bug in AutoMapper? In NUnit? Both?
Should I not use AutoMapper for IDataReader mapping?
Thanks in advance.
After I accidentally stumbled upon this question,
https://groups.google.com/forum/#!topic/automapper-users/3DcPbP-GgNg
I figured out that this has nothing to do with NUnit/MsTest and was simply caused due to AutoMapper.Net4.DLL being in my project.
This DLL contains DataReaderMapper, which overrode my custom IDataReader mapping.
Since my real project columns did not match the object's properties names, DataReaderMapper simply returned null.
(As for NUnit/MsTest, I had different projects, and didn't suspect the extra DLL in one of them. After narrowing the problem I was able to reproduce it on both frameworks.)
Removing AutoMapper.Net4.DLL from my project solved the problem.
Sorry for the misleading question :)

Automapper to create object from XML

If I have the following class:
class SPUser
{
public int ID { get; set; }
public string Name { get; set; }
public string LoginName { get; set; }
public string Email { get; set; }
public bool IsSiteAdmin { get; set; }
public bool IsSiteAuditor { get; set; }
public bool IsDomainGroup { get; set; }
public List<SPGroup> Groups { get; set; }
}
And I am using the sharepoint web services, which return an XML with an attribute for each property on my class, such as:
<Users>
<User Name="name" Description="desc" ..... />
</Users>
Is there any way to use AutoMapper to map the XML fragment to an SPUser class instance?
Blog has been deleted - here's the Bing archive of the post by #DannyDouglass
Simplify Using Xml Data With AutoMapper and Linq-to-Xml
I recently ran into a scenario at work that required manually consuming several SOAP web services, which I’m sure you can imagine was rather monotonous. A co-worker (Seth Carney) and I tried a few different approaches, but we finally settled on a solution that simplified consumption of the xml and ultimately made the code more testable. That solution centered around leveraging AutoMapper, an open source object-object mapping tool, to create a link between the XElements(http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.aspx) returned in the SOAP messages and custom contracts we created – in a reusable manner.
I put together a quick demo that shows how you could use the same approach to consume and display the Twitter Public Timeline (http://api.twitter.com/1/statuses/public_timeline.xml) (using the API’s Xml response type).
Note: The source code for the following example can be found on my GitHub page: https://github.com/DannyDouglass/AutoMapperXmlMappingDemo
Getting the Project Setup
After creating a basic MVC3 (download beta) project and the associated test project, the first step was to get the AutoMapper package installed. I have been using NuGet, Microsoft’s recently announced package management system, to install any open source dependencies. The following command was all that was needed to setup AutoMapper in my MVC3 project (read more about NuGet here(http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx) and here(http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx)):
PM> add-package AutoMapper
Creating the Mapping
With AutoMapper installed I’m ready to get started creating the components necessary for the xml-to-object mapping. The first step is creating a quick contract used in my application to represent the Tweet object:
public interface ITweetContract
{
ulong Id { get; set; }
string Name { get; set; }
string UserName { get; set; }
string Body { get; set; }
string ProfileImageUrl { get; set; }
string Created { get; set; }
}
Nothing crazy here – just a simple entity. These are all fields that are provided in the response from the Twitter API using a different name for some fields. In simple cases where the source and destination objects have the same name you can setup a map very quickly using this syntax:
Mapper.CreateMap<SourceObj, DestinationObj>();
However, AutoMapper does not support Xml by default I have to specify the fields that I will be mapping. Using the Fluent API in AutoMapper I’m able to chain my field mappings. Take a look at one example field mapped in my example – the tweet’s Body:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
It may look complicated at first, but all that is really happening here is that we are providing details to AutoMapper on what value to use in my source object and how to map it to the destination object’s property. There is one particular line I would like to focus on in the above Body field mapping:
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
The XElementResolver is a custom value resolver (http://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers) that Seth came up with to handle parsing the XmlElement source object to retrieve a strongly-typed value for use in the mapping. I’ll detail that more in a moment, but before we move on take a look at my full mapping:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Id,
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
.ForMember(
dest => dest.Name,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("name").Single()))
.ForMember(
dest => dest.UserName,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("screen_name").Single()))
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
.ForMember(
dest => dest.ProfileImageUrl,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("profile_image_url").Single()))
.ForMember(
dest => dest.Created,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("created_at")));
The Generic XElementResolver
This custom value resolver is the real key that allowed these XElement-to-Contract maps to work in the original solution. I’ve reused this resolver in this example as we saw above. This was all that was necessary to create the custom resolver class:
public class XElementResolver<T> : ValueResolver<XElement, T>
{
protected override T ResolveCore(XElement source)
{
if (source == null || string.IsNullOrEmpty(source.Value))
return default(T);
return (T)Convert.ChangeType(source.Value, typeof(T));
}
}
This generic XElementResolver allows use to easily pass the type of the value retrieved in our mapping above. For example, the following syntax is used to strongly type the value retrieved from the XmlElement in the Id field’s .ForMember() declaration above:
ResolveUsing<XElementResolver<ulong>>()
With my mapping completely configured and instantiated, I’m ready to invoke the Twitter API and leverage AutoMapper to display that latest Public Timeline.
Putting the Pieces Together
I created a simple class responsible for retrieving the Twitter API response:
public class TwitterTimelineRetriever
{
private readonly XDocument _twitterTimelineXml;
public TwitterTimelineRetriever()
{
_twitterTimelineXml = XDocument
.Load("http://api.twitter.com/1/statuses/public_timeline.xml");
}
public IEnumerable<ITweetContract> GetPublicTimeline(int numberOfTweets)
{
var tweets = _twitterTimelineXml.Descendants("status")
.Take(numberOfTweets);
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
}
}
The GetPublicTimeline method is a simple method returning, you guessed it, the Twitter Public Timeline by leveraging the map we created earlier:
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
In my MVC3 site’s HomeController I can make a quick call to the retrieval method, requesting the last 10 results:
public class HomeController : Controller
{
private TwitterTimelineRetriever _twitterTimelineRetriever;
public ActionResult Index()
{
_twitterTimelineRetriever = new TwitterTimelineRetriever();
ViewModel.Message = "Twitter Public Timeline";
return View(_twitterTimelineRetriever.GetPublicTimeline(10));
}
}
And finally, after a little formatting in my View using the new Razor view engine from Microsoft, I have my public timeline displaying!
You need to check out XML serialization in .NET for this - that's the way to serialize an object to XML or deserialize it from XML.
Automapper can be used to set properties between two objects - it doesn't deal with XML at all.
More resources:
C# Tutorial - XML Serialization
Bit late in the day, but someone has used AutoMapper to map XML to a POCO rather than going down the XMLSerialization route. I found the following blog entry:-
Simplify Using Xml Data with AutoMapper and Linq-to-Xml
This is enough to get you started on implementing a generic custom resolver of your own if the example one is not enough.
EDIT: fixed link
EDIT: really fixed link
You can user XML deserialization for this purposes. In .NET we have XmlSerializer and DataContractSerializer

How to use AutoMapper?

First time using AutoMapper and I'm have a hard time figuring out how to use it.
I'm trying to map a ViewModel to my Database Tables.
My ViewModel looks like this...
public class AddressEditViewModel
{
public AddressEdit GetOneAddressByDistrictGuid { get; private set; }
public IEnumerable<ZipCodeFind> GetZipCodes { get; private set; }
public AddressEditViewModel(AddressEdit editAddress, IEnumerable<ZipCodeFind> Zips)
{
this.GetOneAddressByDistrictGuid = editAddress;
this.GetZipCodes = Zips;
}
}
The Mapping I'm trying to use is...
CreateMap<Address, AddressEditViewModel>();
When I run this test...
public void Should_map_dtos()
{
AutoMapperConfiguration.Configure();
Mapper.AssertConfigurationIsValid();
}
I get this error...
AutoMapper.AutoMapperConfigurationException: The following 2 properties on JCIMS_MVC2.DomainModel.ViewModels.AddressEditViewModel
are not mapped:
GetOneAddressByDistrictGuid
GetZipCodes
Add a custom mapping expression, ignore, or rename the property on JCIMS_MVC2.DomainModel.Address.
I'm not sure how I am supposed to map those 2 properties. I would appreciate any direction. Thanks
Mark
Ok so I can see a few things you are doing that probably won't help.
Firstly this AutoMapper is used to copy Properties in one object to Properties in a diff object. Along the way it might interrogate or manipulate them to get the end result viewmodel in the correct state.
The properties are named 'Get...' which sounds more like a method to me.
The setters on your properties are private so AutoSetter won't be able to find them. Change these to minimum internal.
Use of a parametrized constructor is no longer needed when you use AutoMapper - as you are converting directly from one object to another. The parametised constructor is there mainly to show what is explicitly required by this object.
CreateMap<Address, AddressEditViewModel>()
.ForMember( x => x.GetOneAddressByDistrictGuid ,
o => o.MapFrom( m => m."GetOneAddressByDistrictGuid") )
.ForMember( x => x.GetZipCodes,
o => o.MapFrom( m => m."GetZipCodes" ) );
What Automapper is really good for is copying from DataObjects into POCO objects, or View Model objects.
public class AddressViewModel
{
public string FullAddress{get;set;}
}
public class Address
{
public string Street{get;set;}
public string Suburb{get;set;}
public string City{get;set;}
}
CreateMap<Address, AddressViewModel>()
.ForMember( x => x.FullAddress,
o => o.MapFrom( m => String.Format("{0},{1},{2}"), m.Street, m.Suburb, m.City ) );
Address address = new Address(){
Street = "My Street";
Suburb= "My Suburb";
City= "My City";
};
AddressViewModel addressViewModel = Mapper.Map(address, Address, AddressViewModel);

Categories