There is an issue with automapper and generating select statement using ProjectTo, while it works most of the time, it doesn't create the select list the same way for the same destination type all the time. This causes the following exception in EF when you try to create a EF Union between two different configurations for the same destination.
System.NotSupportedException: 'The type 'AutoMapperTest.DTO.Entity' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.'
There are several ways to reproduce this, but this gisthub example is the simplest one.
The ideal solution would be that when ProjectTo generates the Select clause it either sorts the properties by name or by position in the destination type. It would then always be the same no matter the what the source is.
NOTE: I see an issue in the circumstances where one of the mappings to the same destnation types ignores a property and thus impossible to solve, but other than these I think that this could be solved by a simple sort of property rules before creating select expression.
// Destination
public class DTO
{
public string Col1 { get; set; }
public string Col2 { get; set; }
public int Id { get; set; }
}
// Sources
public class Entity1
{
public string Col1 { get; set; }
public string Col2 { get; set; }
public int Id { get; set; }
}
public class Entity2
{
public string Col1 { get; set; }
public int Id { get; set; }
}
Mapper.Initialize(config =>
{
config.CreateMap<Entity1, DTO>();
config.CreateMap<Entity2, DTO>()
.ForMember(dest => dest.Col2, opt => opt.UseValue("abc"));
});
The generated select for Entity2 -> DTO should have the same order of property assignment as Entity1 -> DTO select has no matter what the source type and configuration are.
The generated select for Entity2 -> DTO have Col2 as the last property assigned, while the generated select for Entity1 -> DTO has Col2 in the same position as it is in the DTO type.
These are the expression debug views of the generated selects:
Entity1:
.New AutoMapperTest.DTO(){
Col1 = $dto.Col1,
Col2 = $dto.Col2,
Id = $dto.Id
}
Entity2:
.New AutoMapperTest.DTO(){
Col1 = $dto.Col1,
Id = $dto.Id,
Col2 = .Constant<AutoMapper.Configuration.MemberConfigurationExpression`3+<>c__DisplayClass23_0`1[AutoMapperTest.Entity2,AutoMapperTest.DTO,System.String,System.String]>(AutoMapper.Configuration.MemberConfigurationExpression`3+<>c__DisplayClass23_0`1[AutoMapperTest.Entity2,AutoMapperTest.DTO,System.String,System.String]).value
}
See this gisthub example.
"no matter what the source type and configuration are"
I don't know about that :) It rather seems like _you_ should be doing that somehow.
Sorting does make sense I guess. I don't know if there's any explicit ordering we do now.
The ordering is most likely order of the properties in the class, overwritten by explicit mappings.
To fix this you would need a post select statement ExpressionBuilder to look for matching constructors and re-configure property sets so that they are in order. Maybe even set missing ones to default(T) so that can be fixed as well.
@lbargaoanu I agree, but it seems that it doesn't follow the rules I have set either.
@TylerCarlson1 I've done exactly what you suggest, using the same configuration rules (copy/paste but changing source values), but it seems that it moves some properties "out of order" anyway...
@jbogard I think that it does like Tyler describes mostly, but as stated there it seems to be some circumstances that makes it "out of order".
If it follows property order and then mapping rules, I'm satisfied, I don't really care about the exact order rules, but it should be the same with the same DTO and same mapping configuration order :)
But I will try to find a workaround in code for my current issue. The problem is that this is just one example, and having my developers analyze and use time to take out the expression and try to match the two is a quite time consuming operation that I think could be solved easily within AutoMapper by some kind of default ordering rules (without knowing exactly how AutoMapper solves this)
The mapping works as expected. No ordering would solve the problem unless the configuration is ignored. That just seems wrong to me.
@lbargaoanu I do not agree, it doesn't work as expected (I can't union two different ProjectTo's I get an exception, if I manually create the selects it works), and ordering WILL solve the problem since the properties will come in the same orders all the time. I can't really see why setting some kind of sorting rules won't solve the problem and what problems implementing it will eventually cause?
Wouldn't ordering fix it? Since we're creating a Select projection, even an alphabetical ordering should make things consistent?
Yes, I was wrong, I thought a property was missing or smth.
The workaround is to change the order of properties in the class definition.
No, the workaround is NOT to change order of properties, nor changing order of mapping definitions. There is no way to align the mappings at this level.
We always sort our DTO properties alphabetically and our automapper configurations too.
Continued trying workarounds, no matter how I organize properties in class definition or configuration in automapper, there is no way to align the mapping in the select expression correctly, the properties moves around, but it does it for both, so it is no way to make it work. I have tried to move them around in class definition to match exactly both select expressions, tried to configure the mapping configuration to match each select expression, but the properties does not align.
It seems easy enough:
public class DTO
{
public string Col1 { get; set; }
public int Id { get; set; }
public string Col2 { get; set; }
}
Anyway, a PR is welcome. The order should be added here.
After a bit digging in the AutoMapper code I realize that you have a SetMappingOrder function! This solves the issue for me for now. I found it by debugging down to TypeMap.GetPropertyMap() and followed the crumbtrail from there.
However, I think that even if it isn't set a default should be applied, but since the GetPropertyMap() retrieves the ordered by SetMappingOrder, that are set in the Seal method this is where a change should be placed where the MappingOrder are higher ranked than the default sort rule.
I can look at it, I tried to find information on how you want contributions and rules around this, but can't find any? Can you point me in that direction so I can read about the rules before making a PR on it.
Here. But I wouldn't worry about it. Add a failing integration test, then the order by. If you're lucky, that's all it takes.
Closed? I'm not done with this one yet.. The PR is waiting because I haven't had time fix that yet.
And it's still welcome whenever it's ready.
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.