首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何利用Automapper将ProjectTo与地图相结合?

如何利用Automapper将ProjectTo与地图相结合?
EN

Stack Overflow用户
提问于 2022-03-25 11:20:14
回答 1查看 1.1K关注 0票数 0

为了简短起见,这里有数据库实体:

代码语言:javascript
运行
复制
public class Client
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public ICollection<ClientAddress> Addresses { get; set; }
}

public abstract class ClientAddress : ClientSubEntityBase
{
    public int ClientId { get; set; }

    [Required]
    public virtual AddressType AddressType { get; protected set; }

    [Required]
    public string Address { get; set; }
}

public enum AddressType
{
    Fact = 1,
    Registered = 2,
}

public class ClientAddressFact : ClientAddress
{
    public override AddressType AddressType { get; protected set; } = AddressType.Fact;
    public string SpecificValue_Fact { get; set; }
}
public class ClientAddressRegistered : ClientAddress
{
    public override AddressType AddressType { get; protected set; } = AddressType.Registered;
    public string SpecificValue_Registered { get; set; }
}

这些被EF Core 6正确地映射到TPH。当读取返回值时,我们得到ClientAddressFactClientAddressRegistered对应于Client.Addresses内部的AddressType

现在,我需要将这些转换为DTO:

代码语言:javascript
运行
复制
public record Client
{
    public string Name { get; init; }
    public IEnumerable<ClientAddress> Addresses { get; init; }
}

public abstract record ClientAddress
{
    public ClientAddressType AddressType { get; init; }
    public string Address { get; init; }
}

public enum ClientAddressType
{
    Fact,
    Registered,
}

public record ClientAddressFact : ClientAddress
{
    public string SpecificValue_Fact { get; init; }
}
public record ClientAddressRegistered : ClientAddress
{
    public string SpecificValue_Registered { get; init; }
}

显然,使用ProjectTo无法工作,因为无法在LINQ之外构造正确的SELECT语句并创建相应的实体类型。因此,我们的想法是第一个ProjectTo地址列表,如下所示:

代码语言:javascript
运行
复制
public record ClientAddressCommon : ClientAddress
{
    public string SpecificValue_Fact { get; init; }
    public string SpecificValue_Registered { get; init; }
}

然后用Map来纠正实体类型,这样最终我就可以得到正确的Client DTO,并在Addresses中填充正确的ClientAddressFactClientAddressRegistered

但问题是,如何使用单个ProjectTo调用而只使用概要文件来实现呢?问题是投影代码与使用它的多个概要文件项目是分开的。

以下是其中一个简介:

代码语言:javascript
运行
复制
private static Models.ClientAddressType ConvertAddressType(Database.Entities.Enums.AddressType addressType) =>
    addressType switch
    {
        Database.Entities.Enums.AddressType.Fact => Models.ClientAddressType.Fact,
        Database.Entities.Enums.AddressType.Registered => Models.ClientAddressType.Registered,

        _ => throw new ArgumentException("Unknown address type", nameof(addressType))
    };

CreateProjection<Database.Entities.Data.Client, Models.Client>()
;

CreateProjection<Database.Entities.Data.ClientAddress, Models.ClientAddress>()
    .ForMember(dst => dst.AddressType, opts => opts.MapFrom(src => ConvertAddressType(src.AddressType)))
    .ConstructUsing(src => new Models.ClientAddressCommon())
;

使用var projected = _mapper.ProjectTo<Models.Client>(filtered).Single()可以让我正确地填充Client,但只能使用ClientAddressCommon地址。那么如何在第二步使用Map的全部功能来转换它们呢?

UPDATE_01:

根据卢西恩·巴戈阿努的comment,我做了一些调整:

代码语言:javascript
运行
复制
var projected = _mapper.ProjectTo<Models.Client>(filtered).Single();
var mapped = _mapper.Map<Models.Client>(projected);

但不知道该怎么做。以下是更新的个人资料:

代码语言:javascript
运行
复制
CreateMap<Models.Client, Models.Client>()
    .AfterMap((src, dst) => Console.WriteLine("CLIENT: {0} -> {1}", src, dst)) // <-- this mapping seems to work
;

CreateMap<Models.ClientAddressCommon, Models.ClientAddress>()
    .ConstructUsing(src => new Models.ClientAddressFact()) // simplified for testing
    .AfterMap((src, dst) => Console.WriteLine("ADR: {0} -> {1}", src, dst)) // <-- this is not outputting
;

基本上,我现在将Client映射到自身,以转换投影所留下的内容。在这种情况下,我需要将ClientAddressCommon映射到ClientAddressFact或基于AddressTypeClientAddressRegistered。但看起来地图没被用过。我现在少了什么?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-03-27 12:34:22

这就是我想出来的。ClientAddress现在看起来是这样的:

代码语言:javascript
运行
复制
public record ClientAddress
{
    public ClientAddressType AddressType { get; init; } // <-- used to differentiate between address types
    public string Address { get; init; }
    public virtual string SpecificValue_Fact { get; init; } // <-- specific for ClientAddressFact
    public virtual string SpecificValue_Registered { get; init; } // <-- specific for ClientAddressRegistered
}

public record ClientAddressFact : ClientAddress
{
}
public record ClientAddressRegistered : ClientAddress
{
}

public enum ClientAddressType
{
    Fact,
    Registered,
}

配置文件如下所示:

代码语言:javascript
运行
复制
CreateProjection<Database.Entities.Data.Client, Models.Client>() // <-- project from DB to DTO for the main entity
;

CreateProjection<Database.Entities.Data.ClientAddress, Models.ClientAddress>() // <-- project from TPH entity type to a type which holds all the common properties for all address types
    .ForMember(dst => dst.AddressType, opts => opts.MapFrom(src => ConvertAddressType(src.AddressType)))
;

CreateMap<Models.Client, Models.Client>() // <-- this is needed so AM knows that we need to map a type to itself
;

CreateMap<Models.ClientAddress, Models.ClientAddress>() // <-- changed destination type to itself, since it is the only one available at that moment after projection
    .ConvertUsing<ClientAddressTypeConverter>()
;

CreateMap<Models.ClientAddress, Models.ClientAddressFact>()
;
CreateMap<Models.ClientAddress, Models.ClientAddressRegistered>()
;

enum转换助手:

代码语言:javascript
运行
复制
public static Models.ClientAddressType ConvertAddressType(Database.Entities.Enums.AddressType addressType) =>
    addressType switch
    {
        Database.Entities.Enums.AddressType.Fact => Models.ClientAddressType.Fact,
        Database.Entities.Enums.AddressType.Registered => Models.ClientAddressType.Registered,

        _ => throw new ArgumentException("Неизвестный address type", nameof(addressType))
    };

这就是最后的转换方式:

代码语言:javascript
运行
复制
private class ClientAddressTypeConverter : ITypeConverter<Models.ClientAddress, Models.ClientAddress>
{
    public Models.ClientAddress Convert(Models.ClientAddress source, Models.ClientAddress destination, ResolutionContext context) =>
        source.AddressType switch
        {
            Models.ClientAddressType.Fact => context.Mapper.Map<Models.ClientAddressFact>(source),
            Models.ClientAddressType.Registered => context.Mapper.Map<Models.ClientAddressRegistered>(source),

            _ => throw new ArgumentException("Unknown address type")
        };
}

是的,投影后我还需要重新映射:

代码语言:javascript
运行
复制
var projected = _mapper.ProjectTo<Models.Client>(filtered).Single();
var mapped = _mapper.Map<Models.Client>(projected); // map from itself to itself to convert ClientAddress to corresponding sub-types

这一切似乎都奏效了,但我不完全确定这是否是正确的做事方式。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71616100

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档