Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Value transformers in global, profiles, type maps, and members #2261

Merged
merged 10 commits into from Sep 5, 2017

Conversation

jbogard
Copy link
Member

@jbogard jbogard commented Aug 9, 2017

No description provided.

@jbogard jbogard added the Feature label Aug 9, 2017
@jbogard jbogard added this to the 6.2.0 milestone Aug 9, 2017
@jbogard jbogard mentioned this pull request Aug 9, 2017
6 tasks
@jbogard
Copy link
Member Author

jbogard commented Aug 9, 2017

cc/ @asherber

@@ -522,6 +522,10 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de
);
}

valueResolverFunc = _typeMap.Profile.ValueTransformers
.Where(vt => vt.IsMatch(propertyMap))
.Aggregate(valueResolverFunc, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this change to having ValueTransformerConfiguration have function likeMapExpression? Kinda like what IObjectMapper does now where it's an interface like IValueTransformerConfiguration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a further task on the list

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't going to make it like IObjectMapper though, more like IValueResolver and ITypeConverter. You can go the expression route, which can be used in LINQ etc. or the runtime route that can get access to ResolutionContext. That would also allow a DI-able version.

@jbogard jbogard changed the title [WIP] Value transformers in profiles Value transformers in global, profiles, type maps, and members Aug 10, 2017
@jbogard
Copy link
Member Author

jbogard commented Aug 10, 2017

This one's ready to review.

@TylerCarlson1
Copy link
Member

What do you think about making ValueTransformerConfiguration an interface with that being an implementation of it. That way it's more versatile to transform things in the future. Also people will be able to make their own custom ones based on whatever they want if they so desire.

The only issue with this if you want to keep the same public function calls ApplyTransform is should probably be an extension method and you would have to supply the List of transforms so the extension method can access and add to the list.

Unless this is meant for another PR in the future, then can discuss it there.

@jbogard
Copy link
Member Author

jbogard commented Aug 14, 2017

Hrm that's a big departure from the rest of the configuration API. I think you already have the ability to do similar though with ForAllTypeMaps?

@TylerCarlson1
Copy link
Member

But for all type maps doesn't apply to individual property maps and can't be profile specific. That's the whole reason for transforms.

Also your current implementation is only for property maps of same type. What about different types being mapped what about comparing by some kind of naming convention. If you keep the API as it is for each one of them you'll have to keep adding more and more functions to the interface, as well as their implementation in multiple places away from the base class. Where as if you use extension methods they can be confined to their own classes near their respected implementations of IValueTransformerConfiguration for profiles and member configurations.

ValueTransformerConfiguration can have a static extensions class in the same location as it, maybe even a sub class in the same file, with all the method calls that use it. Instead of every configuration interface having an implementation in 2-3 separate files that you have to search for if you want to change something.

Yes it's a big change in how the API is structured, but one thing I don't like about working with the current one is that the MapperConfiguration, Profile, and everything else have interfaces that have like 20-30 properties/functions that do almost the exact same things using a class and keep adding functionality with more and more function calls is going to make navigating them even worse to wade through.

I think this makes the code more easily expandable, without having to hack around in private properties or submit PR's and wait for the next version if you want to do something extra. But then it exposes things that you might not want the user to worry about in configuration. It's a trade off, the big one being change how API is structured, but from the end user experience I don't think it will change, unless there's some weird thing between Extension methods that I'm not aware of.

@jbogard
Copy link
Member Author

jbogard commented Aug 15, 2017 via email

@@ -522,6 +522,12 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de
);
}

valueResolverFunc = propertyMap.ValueTransformers
.Concat(_typeMap.ValueTransformers)
.Concat(_typeMap.Profile.ValueTransformers)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be a lot of transformers here, so a cache would help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know much about transformers, but that sounded weird :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few hundred mappings, maybe a few thousand properties, it seems reasonable. For all the existing transformers. This will hurt eventually :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would we cache? The list could be different since you can define at the property map level.

Alternatively we could push the transformers down from the parent levels, kinda like we do for Include.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but not the transformers per member are the problem, all the rest are. And yes, I thought about smth like your second suggestion, but I'm not sure how that would look like, caching by type seemed more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will only be an issue with large amounts of value transformers. And I'm not sure that would really happen

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can wait :)


public bool IsMatch(PropertyMap propertyMap)
{
return ValueType.IsAssignableFrom(propertyMap.DestinationPropertyType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the other way around?

/// </summary>
/// <typeparam name="TValue">Value type to match and transform</typeparam>
/// <param name="transformer">Transformation expression</param>
void ApplyTransform<TValue>(Expression<Func<TValue, TValue>> transformer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where TValue : TDestination? Although integer conversions don't fit.


namespace AutoMapper
{
public class ValueTransformerConfiguration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this could be a struct.

{
cfg.CreateMap<Source, Dest>();
cfg.ApplyTransform<string>(dest => dest + " is straight up dope");
cfg.ApplyTransform<string>(dest => dest + "! No joke!");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the order matters here. I don't know about that...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing some people expect stacking and some expect overriding :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the wording can be better? But yes, they'd stack is the idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's easy to make a mistake. You have a lot of config code and you have one ApplyTransform and a few pages below, oops, another one. Not exactly the pit of success :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think AddTransform would be better and exposing a list even clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but I don't mind

cfg.ValueTransformers.Add<string>(dest => dest + "!!!");

It is two dots. But it makes more obvious what's going on. I'm not sure if you wanted to do other things with that list.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been following this at a little bit of a distance. FWIW, from a functionality standpoint I like the idea of stacking transforms, but it seems like a departure from the rest of the API, which I think just does overrides. On the whole I think I would vote for ApplyTransform() and overrides, rather than AddTransform() and stacking, but I don't feel strongly about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lbargaoanu I gotcha, add the extension method to the list of value transformers rather than the root. I don't want to create a custom collection type.

@asherber I think we could add conditions to transforms that would allow for turning things off (or don't apply things globally).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbogard @lbargaoanu I kinda like extending off the list better. Less mass options from Intelisense and groups functionality by property.

Also can make it fluent where you add and then return list, so you can stack, and know you are stacking.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I made some changes here lemme know what you think. I couldn't go extension methods all the way because it would have required forcing generic arguments or a weird chaining.

/// </summary>
/// <typeparam name="TValue">Value type to match and transform</typeparam>
/// <param name="transformer">Transformation expression</param>
void AddTransform<TValue>(Expression<Func<TValue, TValue>> transformer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a non generic overload that just uses TMember.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we need an overload as much as just get rid of this version. I don't remember why I put a transform where you could specify a different type. Transformers get applied after resolving/mapping so it's always going to be of the destination member type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After resolving and before mapping. And I think you meant for TValue to be the type of the resolved value. Which doesn't have to be TMember. Right? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah then I wonder if I have value transformations in the right place. I think it's really before you assign it, you transform it but I have it as part of resolving.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the value transformations to after mapping and right before setting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it. I've always thought AfterPropertyMap is a good idea :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha well maybe this will take care of people's scenarios.

@jbogard
Copy link
Member Author

jbogard commented Sep 5, 2017

Anything else on this one?

@jbogard jbogard merged commit 01a4010 into AutoMapper:master Sep 5, 2017
@asherber
Copy link

asherber commented Sep 5, 2017

Thank you!

@AutoMapper AutoMapper deleted a comment from jbogard Sep 5, 2017
@lock
Copy link

lock bot commented May 5, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators May 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants