Let's assume I have these mappings:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.ForMember(o => o.Temp, m => m.Ignore());
Inheritance of Dtos:
DetailDto : BaseDto
UpdateDto : BaseDto
Is there any way to merge/combine the first mapping into mapping2 and mapping3?
Something like this:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.Import(mapping1);
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.Import(mapping1);
.ForMember(o => o.Temp, m => m.Ignore());
The .Include function does not seem to do what I want as the following doesn't work:
var mapping1 = Mapper.CreateMap<Order, BaseDto>()
.Include<Order, DetailDto>()
.Include<Order, UpdateDto>()
.ForMember(o => o.Referrer, m => m.Ignore());
var mapping2 = Mapper.CreateMap<Order, DetailDto>()
.ForMember(o => o.Price, m => m.Ignore());
var mapping3 = Mapper.CreateMap<Order, UpdateDto>()
.ForMember(o => o.Temp, m => m.Ignore());
Update: Edited question to be more specific
I wonder if its a bug. The documentation states:
In AutoMapper 2.0, this becomes:
Mapper.CreateMap<Order, OrderDto>()
.Include<OnlineOrder, OnlineOrderDto>()
.Include<MailOrder, MailOrderDto>()
.ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
Mapper.CreateMap<OnlineOrder, OnlineOrderDto>();
Mapper.CreateMap<MailOrder, MailOrderDto>();
Because we have defined the mapping for the base class, we no longer have to define it in the child mappings.
I have just upgraded to AutoMapper 2.2.1-ci9000 and I'm getting the same behaviour as you (ie, Referrer is not being ignored).
I suggest you create an issue for it and see what they say.
I realise this comes a bit late, but for the benefit of someone else with similar issues...
I struggled with the same problem until I found this page on the AutoMapper Github wiki.
In summary:
Inherited configuration is evaluated against priorities (to resolve issues where multiple maps have conflicting configuration for the same members). An Ignore() that is inherited has a lower priority than AutoMapper's standard convention-based mapping. So where the source and destination types both have a Referrer property, the convention-based mapping for the most derived types overrides the inherited Ignore().
That suggests then, that in this instance, you need to repeat the Ignore() for the maps for each type derived from BastDto
Related
I know, there are tons of questions related to this topic, but I couldn't find an answer to what I was looking for. All the questions seem to be related to the top level properties, and what I'm looking for is a conditional mapping for child properties.
In the official documentation itself, there's no such example either, unfortunately.
Here's an example that they have:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
In my case, I want something like this:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz.foo, opt => opt.Condition(src => (src.baz.foo >= 0)));
});
But this is ofc throwing an exception, because you can't use "ForMember" for child properties. Instead you need to use "ForPath". What I'd expect, is to have similar code, that will work just fine for the child properties the following way:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz.foo, opt => opt.Condition(src => (src.baz.foo >= 0)));
});
But this code won't even compile, since the signature differes for the "Condition" method in "ForPath". So I've adjusted it to based on what the signature wanted and added "Source", before accessing the property, the following way:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz.foo, opt => opt.Condition(src => (src.Source.baz.foo >= 0)));
});
And even though the code complies and looks okay to me, the conditional mapping does not work. It's being ignored and the property is being set all the time, no matter what the value of "foo" is.
I have ended up with a working code, though. I took an alternative path to get what I need, using "AfterMap" method:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz, opt => opt.MapFrom(src => (src.Source.baz)))
.AfterMap((dest, src) =>
{
// Resetting the value of the property I don't need based on the reversed condition
if (src.baz.foo < 0)
{
src.baz.foo = default;
}
}
});
Even though I got it working, I want to know how to achieve the same result with conditional mapping? And what am I doing wrong?
I have the two distinct objects and would like to map them to one destination object. The source objects are complex objects which contain multiple child objects which would also need to be mapped. I've tried something similar to the below example but as expected, the last mapping will overwrite any previous mapping.
CreateMap<sourceObject, destinationObject>()
.ForMember(d => d.Addresses, o => o.MapFrom(s => s.Addresses.MainAddresses))
.ForMember(d => d.Addresses, o => o.MapFrom(s => s.Addresses.SomeOtherAddresses))
I suppose I'm looking for something like a MapFrom().AndThenMapFrom() method (a join or union type transformation).
I have used a custom value resolver to get around the issue but this seems to defeat the purpose of automapper in my eyes. Any other suggestions would be welcomed.
If you want to concatenate in Addresses results of mappings (MainAddresses to Addresses and SomeOtherAddresses to Addresses), one solution would be
CreateMap<sourceObject, destinationObject>()
.ForMember(
d => d.Addresses,
o => o.MapFrom(
s => s.Addresses.MainAddresses
.Cast<object>()
.Concat(s.Addresses.SomeOtherAddresses)))
or
CreateMap<sourceObject, destinationObject>()
.ForMember(
d => d.Addresses,
o => o.MapFrom(
s => s.Addresses.MainAddresses
.Cast<IAddress>()
.Concat(s.Addresses.SomeOtherAddresses)))
if objects in MainAddresses and SomeOtherAddresses realize IAddress interface.
Another solution is to do it in Before/AfterMap method
CreateMap<sourceObject, destinationObject>()
.BeforeMap(
(s, d, c) =>
{
var mainAddresses = c.Mapper.Map<IEnumerable<Address>>(s.Addresses.MainAddresses);
var someOtherAddresses = c.Mapper.Map<IEnumerable<Address>>(s.Addresses.SomeOtherAddresses);
d.Addresses = mainAddresses
.Concat(someOtherAddresses)
.ToArray();
})
.ForMember(d => d.Addresses, o => o.Ignore());
In this case mapping for Addresses should be ignored, because we do it "manually" in BeforeMap. Unfortunately both solutions are not elegant as most of simple automapper rules.
I make a mapping using Automapper 3.2.1:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ForMember(dst => dst.IDAvviso, src => src.MapFrom(v => EstraiIdAvviso(v)))
.ForMember(dst => dst.CodiceErrorePiattaforma, src => src.MapFrom(v => EstraiCodiceErrorePiattaforma(v.History)))
.ForMember(dst => dst.DescrizioneErrorePiattaforma, src => src.MapFrom(v => EstraiDescrizioneErrorePiattaforma(v.History)))
.ForMember(dst => dst.CodiceEsitoPostalizzazione, src => src.MapFrom(v => EstraiEsitoPostalizzazione(v.History)))
And this works good. now I want to remove parameterless construction in order to use a better encapsulation::
public EsitiPostalizzazione(int IDAvviso, int CodiceEsitoPostalizzazione, String CodiceErrorePiattaforma, String DescrizioneErrorePiattaforma)
{
this.IDAvviso = IDAvviso;
this.CodiceEsitoPostalizzazione = CodiceEsitoPostalizzazione;
this.CodiceErrorePiattaforma = CodiceErrorePiattaforma;
this.DescrizioneErrorePiattaforma = DescrizioneErrorePiattaforma;
}
so as far as I documented I should use the ConstructUsingafter the createMap() but I don't know how to use it, can someone help me?
All the example on the web will make something like:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ConstructUsing(x => new EsitiPostalizzazione(x.IdAvviso, ...))
but I have nothing like x.IDAvviso
The x in the ConstructUsing lambda should be your source type, which is AvvisoPec. So when you're using the EsitiPostalizzazione constructor you need to provide the values the way you were doing when using the MapFrom syntax.
Based on your code I would expect it to be:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ConstructUsing(v => new EsitiPostalizzazione(EstraiIdAvviso(v),
EstraiEsitoPostalizzazione(v.History),
EstraiCodiceErrorePiattaforma(v.History),
EstraiDescrizioneErrorePiattaforma(v.History)));
I am trying to do a query like:
var a = session.QueryOver<Site>()
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId).Select(p => p.RegionLocation.City))
.List<object[]>();
but I get the error
could not resolve property: RegionLocation.City of: Entities.Site
The property exists and I can retrieve it using LINQ but QueryOver does not work. What am I doing wrong ?
As far as i remember, with QueryOver, you have to join all entities in the association in order to be able to access it's properties.
This means you ought to do something like:
(notice the .JoinQueryOver)
var a = session.QueryOver<Site>()
.JoinQueryOver(s => s.RegionLocation)
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId)
.Select(p => p.RegionLocation.City))
.List<object[]>();
Or maybe this will work:
RegionLocation regionLocationAlias = null;
var a = session.QueryOver<Site>()
.JoinAlias(s => s.RegionLocation, () => regionLocationAlias)
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId)
.Select(() => regionLocationAlias.City))
.List<object[]>();
Also you might want to have a look at https://github.com/davybrion/NHibernateWorkshop/tree/master/NHibernateWorkshop
There's lots of great examples!
Specifically for your problem, have a look at: https://github.com/davybrion/NHibernateWorkshop/blob/master/NHibernateWorkshop/Querying/QueryOver/Projecting.cs
Let´s say I have
class Product
{
string name;
List<Order> orders;
}
class Order
{
string name;
}
If I try to map analyzers to Product name it work, but not for Order.name
//This work and adds the analyzer on the mapping list.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
));
//This does not.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.orders.First().Name)
.IndexAnalyzer("normalize")
)
));
Am I doing something wrong, or is this a bug?
Some more info:
Those classes are just an example to show the problem.
If I add [ElasticProperty(Analyzer = "normalize")] on the variable it works.
Actually the setup look something like Product Inherists from BaseProdcuts and BaseProductis is the one who has the List
As answered here, .Name(p => p.Orders.First().Name) is telling ES to map the field 'Name' on the Product document. Instead, you want to map to the 'Name' field on Orders, which is an array in your Product document.
Try this instead:
client.Map<Product>(m => m
.Properties(pp => pp
// Map Product.Name
.String(s => s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
// Map Product.Orders.Name
.Object<List<Order>>(o => o
.Name(p => p.Orders)
.Properties(op => op
.String(s => s
.Name(os => os.First().Name)
.IndexAnalyzer("normalize"))))
));