admin管理员组

文章数量:1242791

Given the following EF Core entity class:

[PrimaryKey(nameof(CustomerID))]
[Table(nameof(Customer))]
public partial class Customer
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Nullable<Int32> CustomerID { get; set; }

    public string CustomerName { get; set; }
    public string CustomerCode { get; set; }

    public Nullable<int> HOOpAddressID { get; set; }

    [ForeignKey(nameof(HOOpAddressID))]
    public OperationalAddress HeadOfficeAddress { get; set; }

    public string Nickname { get; set; }

    public bool isActive { get; set; }
}

I have defined a DTO to hold a subset of the data as follows...

public class CustomerLookupListItem
{

    public Int32 CustomerID { get; set; }

    public String CustomerName { get; set; }
    public String Nickname { get; set; }
    public String Postcode { get; set; }

    public Boolean IsActive { get; set; }
}

And, in order to use AutoMapper, I have also defined a Profile as the Postcode property of the CustomerLookupListItem is not mapped from the top level:

public class CustomerLookupListItemMappingProfile : Profile
{
    public CustomerLookupListItemMappingProfile()
    {
        CreateMap<Customer, CustomerLookupListItem>()
            .ForMember(dst => dst.Postcode, opt => opt.MapFrom(src => src.HeadOfficeAddress.PostCode));
    }
}

When I then use the ProjectTo<T> method as a part of my EF query, the value of the Postcode property in the CustomerLookupListItem is always null.

Looking at the SQL that is sent to the database, I can see that it is not being requested as a part of the query...

info: Microsoft.EntityFrameworkCore.Database.Command
      Executed DbCommand (22ms) [Parameters=[@__startsWith_0_startswith='le%' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SELECT [c].[CustomerID], [c].[CustomerName], [c].[Nickname], [c].[isActive] AS [IsActive]
      FROM [Customer] AS [c]
      LEFT JOIN [OperationalAddress] AS [o] ON [c].[HOOpAddressID] = [o].[OperationalAddressID]
      WHERE [c].[isActive] = CAST(1 AS bit) AND [o].[PostCode] LIKE @__startsWith_0_startswith ESCAPE N'\'
      ORDER BY [o].[PostCode]

I have done this (MapFrom with ProjectTo) elsewhere in my application in multiple places and it has worked every time so why does it not work in this instance?

Note: I have verified that the constructor of the CustomerLookupListItemMappingProfile is being called so the mapping exists.

EDIT #1: Including the LINQ query as requested in the comments...

public async Task<List<CustomerLookupListItem>> GetCustomerPostcodeAutocompleteAsync(String startsWith, Boolean activeOnly)
    => await dbContext.Customers.AsNoTracking()
        .Where(c => !activeOnly || c.isActive && c.HeadOfficeAddress.PostCode.StartsWith(startsWith))
        .OrderBy(c => c.HeadOfficeAddress.PostCode)
        .ProjectTo<CustomerLookupListItem>(mapper.ConfigurationProvider)
        .ToListAsync();

EDIT #2: Including DebugView content from Automapper...

dbContext.Customers.ProjectTo(mapper.ConfigurationProvider).Expression.DebugView...

.Call System.Linq.Queryable.Select(
    .Extension<Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression>,
    '(.Lambda #Lambda1<System.Func`2[MyNameSpace.CustomerLookupListItem]>))

.Lambda #Lambda1<System.Func`2[MyNameSpace.CustomerLookupListItem]>(MyNameSpace.Customer $dtoCustomer) {
    .New MyNameSpace.CustomerLookupListItem() {
        CustomerID = $dtoCustomer.CustomerID ?? .New System.Int32(),
        CustomerName = $dtoCustomer.CustomerName,
        Nickname = $dtoCustomer.Nickname,
        IsActive = $dtoCustomer.isActive
    }
}

mapperConfiguration.BuildExecutionPlantypeof(Customer), typeof(CustomerLookupListItem)).Body.DebugView...

.If ($source == .Default(System.Object)) {
    .If ($destination == .Default(System.Object)) {
        .Default(MyNameSpace.CustomerLookupListItem)}
    .Else { 
        $destination
    }
}
.Else {
    .Block(MyNameSpace.CustomerLookupListItem $typeMapDestination) {
        .Block() {
            $typeMapDestination = ($destination ?? .New MyNameSpace.CustomerLookupListItem());
            .Try {
                .Block(
                    System.Nullable`1[System.Int32] $resolvedValue, System.Int32 $mappedValue) {
                        $resolvedValue = $source.CustomerID;
                        $mappedValue = .If ($resolvedValue.HasValue) {
                            $resolvedValue.Value
                        }
                        .Else {
                            .Default(System.Int32)
                        };
                        $typeMapDestination.CustomerID = $mappedValue
                    }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(CustomerID))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = $source.CustomerName;
                    $typeMapDestination.CustomerName = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(CustomerName))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = $source.Nickname;
                    $typeMapDestination.Nickname = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(Nickname))
            };
            .Try {
                .Block(System.Boolean $resolvedValue) {
                    $resolvedValue = $source.isActive;
                    $typeMapDestination.IsActive = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(IsActive))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = .Block(MyNameSpace.OperationalAddress $sourceHeadOfficeAddress) {
                        .Block() {
                            $sourceHeadOfficeAddress = $source.HeadOfficeAddress;
                            .If ($sourceHeadOfficeAddress == .Default(System.Object)) {
                                .Default(System.String)
                            }
                            .Else {
                                $sourceHeadOfficeAddress.PostCode
                            }
                        }
                    };
                    $typeMapDestination.Postcode = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(Postcode))
            };
            $typeMapDestination
        }
    }
}

Given the following EF Core entity class:

[PrimaryKey(nameof(CustomerID))]
[Table(nameof(Customer))]
public partial class Customer
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Nullable<Int32> CustomerID { get; set; }

    public string CustomerName { get; set; }
    public string CustomerCode { get; set; }

    public Nullable<int> HOOpAddressID { get; set; }

    [ForeignKey(nameof(HOOpAddressID))]
    public OperationalAddress HeadOfficeAddress { get; set; }

    public string Nickname { get; set; }

    public bool isActive { get; set; }
}

I have defined a DTO to hold a subset of the data as follows...

public class CustomerLookupListItem
{

    public Int32 CustomerID { get; set; }

    public String CustomerName { get; set; }
    public String Nickname { get; set; }
    public String Postcode { get; set; }

    public Boolean IsActive { get; set; }
}

And, in order to use AutoMapper, I have also defined a Profile as the Postcode property of the CustomerLookupListItem is not mapped from the top level:

public class CustomerLookupListItemMappingProfile : Profile
{
    public CustomerLookupListItemMappingProfile()
    {
        CreateMap<Customer, CustomerLookupListItem>()
            .ForMember(dst => dst.Postcode, opt => opt.MapFrom(src => src.HeadOfficeAddress.PostCode));
    }
}

When I then use the ProjectTo<T> method as a part of my EF query, the value of the Postcode property in the CustomerLookupListItem is always null.

Looking at the SQL that is sent to the database, I can see that it is not being requested as a part of the query...

info: Microsoft.EntityFrameworkCore.Database.Command
      Executed DbCommand (22ms) [Parameters=[@__startsWith_0_startswith='le%' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SELECT [c].[CustomerID], [c].[CustomerName], [c].[Nickname], [c].[isActive] AS [IsActive]
      FROM [Customer] AS [c]
      LEFT JOIN [OperationalAddress] AS [o] ON [c].[HOOpAddressID] = [o].[OperationalAddressID]
      WHERE [c].[isActive] = CAST(1 AS bit) AND [o].[PostCode] LIKE @__startsWith_0_startswith ESCAPE N'\'
      ORDER BY [o].[PostCode]

I have done this (MapFrom with ProjectTo) elsewhere in my application in multiple places and it has worked every time so why does it not work in this instance?

Note: I have verified that the constructor of the CustomerLookupListItemMappingProfile is being called so the mapping exists.

EDIT #1: Including the LINQ query as requested in the comments...

public async Task<List<CustomerLookupListItem>> GetCustomerPostcodeAutocompleteAsync(String startsWith, Boolean activeOnly)
    => await dbContext.Customers.AsNoTracking()
        .Where(c => !activeOnly || c.isActive && c.HeadOfficeAddress.PostCode.StartsWith(startsWith))
        .OrderBy(c => c.HeadOfficeAddress.PostCode)
        .ProjectTo<CustomerLookupListItem>(mapper.ConfigurationProvider)
        .ToListAsync();

EDIT #2: Including DebugView content from Automapper...

dbContext.Customers.ProjectTo(mapper.ConfigurationProvider).Expression.DebugView...

.Call System.Linq.Queryable.Select(
    .Extension<Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression>,
    '(.Lambda #Lambda1<System.Func`2[MyNameSpace.CustomerLookupListItem]>))

.Lambda #Lambda1<System.Func`2[MyNameSpace.CustomerLookupListItem]>(MyNameSpace.Customer $dtoCustomer) {
    .New MyNameSpace.CustomerLookupListItem() {
        CustomerID = $dtoCustomer.CustomerID ?? .New System.Int32(),
        CustomerName = $dtoCustomer.CustomerName,
        Nickname = $dtoCustomer.Nickname,
        IsActive = $dtoCustomer.isActive
    }
}

mapperConfiguration.BuildExecutionPlantypeof(Customer), typeof(CustomerLookupListItem)).Body.DebugView...

.If ($source == .Default(System.Object)) {
    .If ($destination == .Default(System.Object)) {
        .Default(MyNameSpace.CustomerLookupListItem)}
    .Else { 
        $destination
    }
}
.Else {
    .Block(MyNameSpace.CustomerLookupListItem $typeMapDestination) {
        .Block() {
            $typeMapDestination = ($destination ?? .New MyNameSpace.CustomerLookupListItem());
            .Try {
                .Block(
                    System.Nullable`1[System.Int32] $resolvedValue, System.Int32 $mappedValue) {
                        $resolvedValue = $source.CustomerID;
                        $mappedValue = .If ($resolvedValue.HasValue) {
                            $resolvedValue.Value
                        }
                        .Else {
                            .Default(System.Int32)
                        };
                        $typeMapDestination.CustomerID = $mappedValue
                    }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(CustomerID))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = $source.CustomerName;
                    $typeMapDestination.CustomerName = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(CustomerName))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = $source.Nickname;
                    $typeMapDestination.Nickname = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(Nickname))
            };
            .Try {
                .Block(System.Boolean $resolvedValue) {
                    $resolvedValue = $source.isActive;
                    $typeMapDestination.IsActive = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(IsActive))
            };
            .Try {
                .Block(System.String $resolvedValue) {
                    $resolvedValue = .Block(MyNameSpace.OperationalAddress $sourceHeadOfficeAddress) {
                        .Block() {
                            $sourceHeadOfficeAddress = $source.HeadOfficeAddress;
                            .If ($sourceHeadOfficeAddress == .Default(System.Object)) {
                                .Default(System.String)
                            }
                            .Else {
                                $sourceHeadOfficeAddress.PostCode
                            }
                        }
                    };
                    $typeMapDestination.Postcode = $resolvedValue
                }
            }
            .Catch (System.Exception $ex) {
                .Throw
                .Call AutoMapper.Execution.TypeMapPlanBuilder.MemberMappingError($ex, .Constant<AutoMapper.PropertyMap>(Postcode))
            };
            $typeMapDestination
        }
    }
}
Share Improve this question edited 10 hours ago Martin Robins asked 2 days ago Martin RobinsMartin Robins 6,16311 gold badges60 silver badges96 bronze badges 18
  • A repro would help. Make a gist that we can execute and see fail. – Lucian Bargaoanu Commented 2 days ago
  • @LucianBargaoanu but that would not have access to the database in order to reproduce it? – Martin Robins Commented 2 days ago
  • It depends. It might reproduce with an array and AsQueryable. But a test with a code first approach is not difficult. You can find examples in the AM repo. – Lucian Bargaoanu Commented 2 days ago
  • 1 Before anything, try if a query without AutoMapper successfully pulls HeadOfficeAddress.PostCode. If EF doesn't deliver, AutoMapper can't do anything. The SQL query may very well include OperationalAddress only because you filter on its PostCode. – Gert Arnold Commented 2 days ago
  • 1 @LucianBargaoanu just for context, I gor the BuildExecutionPlan output from using the example you pointed to, while I got the ProjectTo.Expression from actually running the application so the missing data in that ProjectTo.Expression will have been from the additional map overriding the map I thought I was working with. – Martin Robins Commented 10 hours ago
 |  Show 13 more comments

1 Answer 1

Reset to default 0

Summarizing from the comment discussion/debugging some possible causes for issues like this can include:

  • As confirmed in your case, an additional Automapper configuration for the desired mapping overriding the desired profile mapping, which neglected the expected mapping.
  • Prematurely materializing a query before projection /w a .ToList()/.AsEnumerable() meaning automapper will not work custom mapping into the Query.
  • Namespace conflicts where the class a projection is registered to project to ends up being a different instance than the projection actually wants to write to. (Commonly caused by mis-selecting an Intellisense hint that creates a new class in a different namespace)

本文标签: entity framework coreMapFrom and ProjectTo fail to populate target propertyStack Overflow