I am trying to take some of the pain out of creating mapping expressions in AutoMapper, using AutoMapper.QueryableExtensions
I have the following, which gives a critical performance gain:
private MapperConfiguration CreateConfiguration() {
return new MapperConfiguration(cfg => cfg.CreateMap<Widget, WidgetNameDto>()
.ForMember(dto => dto.Name,
conf => conf.MapFrom(w => w.Name)));
}
To understand the performance gain, see here: https://github.com/AutoMapper/AutoMapper/blob/master/docs/Queryable-Extensions.md The key is that the query is limited by field at the database level.
It's terrific that this works. But I anticipate needing to do a lot of this kind of projecting. I am trying to take some of the pain out of the syntax in the ForMember clause above.
For example, I've tried this:
public static IMappingExpression<TFrom, TTo> AddProjection<TFrom, TTo, TField>(this IMappingExpression<TFrom, TTo> expression,
Func<TFrom, TField> from,
Func<TTo, TField> to
)
=> expression.ForMember(t => to(t), conf => conf.MapFrom(f => from(f)));
The problem is that everything I do runs into an error:
AutoMapper.AutoMapperConfigurationException : Custom configuration for members is only supported for top-level individual members on a type.
Even if the passed in Funcs are top-level individual members, that fact is lost in the passing, so I hit the error. I've also tried changing Func<Whatever> to Expression<Func<Whatever>>. It doesn't help.
Is there any way I can simplify the syntax of the ForMember clause? Ideally, I would just pass in the two relevant fields.
First, there is no need to add mapping for the fields/properties that match by name - AutoMapper maps them automatically by convention (that's why it is called convention-based object-object mapper). And for including just some of the properties in the projection you could use the Explicit expansion feature.
Second, what you call a pain in the ForMember syntax is in fact a flexibility. For instance, the explicit expansion and other behaviors can be controlled by conf argument, so it's not only for specifying the source.
With that being said, what you ask is possible. You have to change the from/ to type to Expression:
Expression<Func<TFrom, TField>> from,
Expression<Func<TTo, TField>> to
and the implementation simply as follows:
=> expression.ForMember(to, conf => conf.MapFrom(from));
Related
I was going to create an issue on github, but the issue template says I'd rather discuss it here first, so here we go:
I am trying to use Mapstruct to generate mappings from one WSDL-generated set of entities to another. However, without adding a "default" method (manual mapping) it does not work ! That seems to be strange, as I would expect this kind of mapping should not be difficult for Mapstruct. repro case is here: https://github.com/62mkv/wsdl-mapstruct-repro-case
the gist of the code is here:
#Mapper(uses = {
org.system.wsdl.legacy.ObjectFactory.class
})
public interface WsMapper {
org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
}
this code above will fail to compile, with such message:
Can't map property "javax.xml.bind.JAXBElement inputParameters" to "javax.xml.bind.JAXBElement inputParameters". Consider to declare/implement a mapping method: "javax.xml.bind.JAXBElement map(javax.xml.bind.JAXBElement value)". org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
basically, mapping would go as follows: EntityNew -> JaxbElement -> FieldNew -> FieldOld -> JaxbElement -> EntityOld
as I've read here (https://stackoverflow.com/a/46015381/2583044), mapping from JaxbElement to T is trivial for MapStruct, and to map from T to JaxbElement one has to use "uses" annotation and provide ObjectFactory.class, which I do; however... this seems to not be enough.
if I add these two methods, code compiles good:
org.system.wsdl.legacy.GarlicParameterCollection garlicParameterCollectionToGarlicParameterCollection(org.system.wsdl.cloud.GarlicParameterCollection collection);
default JAXBElement<org.system.wsdl.legacy.GarlicParameterCollection> garlicParameterCollectionToGarlicParameterCollection(JAXBElement<org.system.wsdl.cloud.GarlicParameterCollection> parameterCollectionJAXBElement) {
return new org.system.wsdl.legacy.ObjectFactory().createSellGarlicRequestInputParameters(
this.garlicParameterCollectionToGarlicParameterCollection(parameterCollectionJAXBElement.getValue())
);
}
is it a potential issue in mapstruct or I just don't know how to cook it well?
The problem is that MapStruct sees your object factory method (with argument) as a mapping method. So, it delivers a target, but it has a source as well. If you realise this, then the mapping suddenly is not symmetrical (as it initially appears).
The simple solution is to instruct MapStruct how to handle this.
So: try this:
#Mapper(uses = {
org.system.wsdl.legacy.ObjectFactory.class
})
public interface WsMapper {
org.system.wsdl.legacy.GarlicParameterCollection garlicParameterCollectionToGarlicParameterCollection(org.system.wsdl.cloud.GarlicParameterCollection collection);
#Mapping( target = "inputParameters", source = "inputParameters.value") // this instructs MapStruct to use value of the source JAXBElement (for which it has an object factory method) instead of trying to map JAXBElement to JAXBElement.
org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
}
Last but not least, you need to define the first method garlicParameterCollectionToGarlicParameterCollection which surprised me initially.
The reason: MapStruct either tries to:
1. find a mapping method (which is not there if you leave this one out)
or
2. tries to generate a direct mapping (by inspecting if it can find methods for all the attributes on source and target).
However, MapStruct cannot find a direct case for this mapping (it would in principle needs to apply all other possible mappings on its path (e.g. all the methods in the object factory) and then try to generate a mapping method as explained in 2, which could be a lot of combinations. That functionality is not there (and would be load intensive as well I guess).
I've seen a bunch of questions related to this subject, but none of them offers anything that would be an acceptable solution (please, no loading external Groovy scripts, no calling to sh step etc.)
The operation I need to perform is a oneliner, but pipeline limitations made it impossible to write anything useful in that unter-language...
So, here's minimal example:
#NonCPS
def encodeProperties(Map properties) {
properties.collect { k, v -> "$k=$v" }.join('|')
}
node('dockerized') {
stage('Whatever') {
properties = [foo: 123, bar: "foo"]
echo encodeProperties(properties)
}
}
Depending on whether I add or remove #NonCPS annotation, or type declaration of the argument, the error changes, but it never gives any reason for what happened. It's basically random noise, that contradicts the reality of the situation (at times it would claim that some irrelevant object doesn't have a method encodeProperties, other times it would say that it cannot find a method encodeProperties with a signature that nobody was trying to call it with (like two arguments instead of one) and so on.
From reading the documentation, which is of disastrous quality, I sort of understood that maybe functions in general aren't serializable, and that is why you need to explain this explicitly to the Groovy interpreter... I'm sorry, this makes no sense, but this is roughly what documentation says.
Obviously, trying to use collect inside stage creates a load of new errors... Which are, at least understandable in that the author confesses that their version of Groovy doesn't implement most of the Groovy standard...
It's just a typo. You defined encodeProperties but called encodeProprties.
Currently trying to understand the puppet manifests written by another person and met the following construction in the class:
postgres_helper::tablespace_grant { $tablespace_grants:
privilege => 'all',
require => [Postgresql::Server::Role[$rolename]]
}
what does $tablespace_grants: means in this case? First i suggested that is some kind of a title, however when i used notice to receive the value of it, it is hash:
Tablespace_grants value is [{name => TS_INDEX_01, role => developer},
{name => TS_DATA01_01, role => developer}]
what does $tablespace_grants: means in this case? First i suggested
that is some kind of a title,
It is a variable reference, used, yes, as the title of a postgres_helper::tablespace_grant resource declaration.
however when i used notice to receive
the value of it, it is hash:
Tablespace_grants value is [{name => TS_INDEX_01, role => developer},
{name => TS_DATA01_01, role => developer}]
Actually, it appears to be an array of hashes. An array may be used as the title of a resource declaration to compactly declare multiple resources, one for each array element. In Puppet 4, however, the elements are required to be strings. Earlier versions of Puppet would stringify hashes presented as resource titles; I am uncertain offhand whether Puppet 4 still falls back on this.
In any case, it is unlikely that the overall declaration means what its original author intended, in any version of Puppet. It looks like the intent is to declare multiple resources, each with properties specified by one of the hashes, but the given code doesn't accomplish that, and it's unclear exactly what the wanted code would be.
I understand what is happening here with the spread operator *. in Groovy (2.4.3):
[].class.methods*.name
=> [add, add, remove, remove, get, ...
But why does the leaving the * out produce the same results?
[].class.methods.name
=> [add, add, remove, remove, get, ...
I'd have expected that to be interpreted as accessing the name property of the java.lang.reflect.Method[] returned by methods and so be an error. But it seems to work. Having then experimented a bit more, so do the following:
[*[].class.methods].name
=> [add, add, remove, remove, get, ...
([].class.methods.toList()).name
=> [add, add, remove, remove, get, ...
So it appears attempting to access a property of an array or list (perhaps even Iterable) actually returns a list of that property for each element of the list (as the spread operator would).
So this leaves me wondering:
Is this behaviour documented anywhere? (I don't see it here for example: http://www.groovy-lang.org/operators.html and haven't seen it noted elsewhere in the docs.)
Does this behaviour only apply to 'properties' (i.e. non-arg methods following the getFoo() naming convention)? This seems to be the case from some quick GroovyConsole tests.
Is the spread operator therefore only necessary/useful when calling non-getFoo() style methods or methods with arguments? (Since you can just use . otherwise.)
UPDATE:
It appears to be the case that spread *. works for any Iterable whereas the . only applies to collections. For example:
class Foo implements Iterable {
public Iterator iterator() { [Long.class, String.class].iterator() }
}
(new Foo())*.name
=> [java.lang.Long, java.lang.String]
(new Foo()).name
=> groovy.lang.MissingPropertyException: No such property: name for class: Foo
(I guess this is a good thing: if the Iterable itself later gained a property with the same name, the code would start returning that (single) property from the Iterable - rather than the list property values from the elements.)
That's the GPath expression documented (ish) here, and yes, it only works for properties. (There's an old blog post by Ted Naleid here about digging in to how it worked in 2008)
For methods, you need to use *. or .collect()
See also: Groovy spread-dot operator
Better docs link (as pointed out by #NathanHughes below
I am trying to write a mapping to map between two classes using automapper. Most of it is pretty straight forward, direct mappings between 2 fields of the same type. However, I have an indexer on each class that may that need to map to each other. It probably isn't relevant that the source type has an indexer, so essentially what I am trying to do is something like:
mappingExpression.ForMember(d => d["Text"], opt => opt.MapFrom(s => s.Text));
Which gives me the error:
Custom configuration for members is only supported for top-level individual members on a type.
Is there any way of achieving this?