簡體   English   中英

如何在 AutoMapper 中發現將枚舉映射到枚舉的缺失類型映射?

[英]How to discover missing type-maps for mapping enum to enum in AutoMapper?

好的,伙計們,這是一個相當長的問題,在我提出實際問題之前,我會盡力描述當前情況並提供一些有意義的背景信息。

TL;博士;

我需要一種方法來識別無效的枚舉到枚舉的映射,這可能會導致運行時問題,因為它們的定義隨着時間的推移而出現分歧。

一些背景

因此,我和我的團隊正在維護這組相當復雜的 REST-API……至少在涉及到的實際對象圖方面是復雜的。 我們總共要處理數百個模型。 為了提高結構復雜性,原始架構在內部 API 級別上采用了完整的 n 層樣式。

最重要的是,我們有多個這樣的架構服務,有時需要相互調用。 這是通過這里的普通 http 調用,那里的一些消息傳遞來實現的,你明白了。

為了讓一個 API 與另一個通信,並維護 SOA 和/或微服務原則,每個 API 至少提供一個相應的客戶端庫,該客戶端庫管理與代表 ZDB9742387104CA8DE634A7CE1 的實際通信的通信。

歸結起來,每個 API 至少包含以下層(自上而下)

  • 客戶層
  • API層
  • 領域層
  • 持久層

此外,所有這些層都維護自己對各種模型的表示。 通常,這些是 1:1 表示,只是在另一個命名空間中。 有時這些層之間存在更顯着的差異。 這取決於...

為了在這些層之間進行通信時減少樣板,我們大部分時間都在使用 AutoMapper(討厭或喜歡它)。

問題:

隨着我們整個系統的發展,我們越來越注意到在模型的各種表示中映射枚舉到枚舉屬性時出現的問題。 有時是因為一些開發人員只是忘記在其中一個層中添加一個新的枚舉值,有時我們重新生成了一個基於 Open-API 的生成客戶端等,然后導致這些定義不同步枚舉。 主要問題是,源枚舉可能比目標枚舉具有更多的值。 當命名略有不同時,可能會出現另一個問題,例如 Executer 與 Executor

假設我們有這個(非常非常過度簡化的)模型表示


    public enum Source { A, B, C, D, Executer, A1, B2, C3 } // more values than below

    public enum Destination { C, B, X, Y, A, Executor }  //fewer values, different ordering, no D, but X, Y and a Typo


    class SourceType
    {
        public Source[] Enums { get; set; }
    }

    class DestinationType
    {
        public Destination[] Enums { get; set; }
    }

現在假設我們的 AutoMapper 配置看起來像這樣:


var problematicMapper = new MapperConfiguration(config =>
{
    config.CreateMap<SourceType, DestinationType>();
}).CreateMapper();


所以映射下面的 model 在語義上是一種危險(或者至少在調試時提供了一些非常奇怪的樂趣)。

    var destination = problematicMapper.Map<DestinationType>(new SourceType()
    {
        Enums = new []
        {
            Source.A,
            Source.B,
            Source.C,
            Source.D,
            Source.Executer,
            Source.A1,
            Source.B2,
            Source.C3
        }
    });

    var mappedValues = destination.Enums.Select(x => x.ToString()).ToArray();
    
    testOutput.WriteLine(string.Join(Environment.NewLine, mappedValues));
    /*  
        Source.A            => A           <- ✔️ ok
        Source.B            => b           <- ✔️ok
        Source.C            => c           <- ✔️ok
        Source.D            => Y           <- 🤷‍♀️ whoops
        Source.Executer     => A           <- 🧏‍♂️ wait, what?
        Source.A1           => Executor    <- 🙊 nah
        Source.B2           => 6           <- 🙉 wtf?
        Source.C3           => 7           <- 🙈 wth?
        */

對我來說是裸露的,因為這里的某些情況是上演的,可能比現實中發現的更極端。 只是想指出一些奇怪的行為,即使 AutoMapper 試圖優雅地處理大多數情況,比如重新排序或不同的外殼。 目前,我們在源枚舉中面臨更多的值,或者在命名/拼寫錯誤方面略有不同

當這最終導致一些討厭的生產錯誤時,可以觀察到更少的樂趣,這也可能或多或少地對業務產生嚴重影響——尤其是當這種問題只發生在運行時,而不是測試和/或構建時時間。

此外,該問題不僅限於 n 層架構,還可能是正交/洋蔥/clean-ish 架構 styles 中的問題(而在這種情況下,這種值類型應該更有可能是放置在 API 中心的某個位置,而不是每個角落/外環/適配器層或任何當前術語)

(臨時)解決方案

盡管試圖減少各個層內的冗余剪切量,或者(手動)在定義本身內維護明確的枚舉值(這兩個都是有效的選項,但是見鬼,這是很多 PITA 工作),沒有在嘗試緩解此類問題時還有很多工作要做。

很高興,有一個不錯的選項可用,它利用 enum-to-enum-properties per-name而不是per-value映射,以及在每個成員的基礎上在非常細粒度的級別上進行更多自定義。

[AutoMapper.Extensions.EnumMapping] 來救援!

來自文檔:

如果兩個枚舉類型具有相同的值(或按名稱或按值),則 package AutoMapper.Extensions.EnumMapping 將 map 從源類型到目標類型的所有值

這個 package 添加了一個額外的 EnumMapperConfigurationExpressionExtensions.EnableEnumMappingValidation 擴展方法來擴展現有的 AssertConfigurationIsValid() 方法來驗證枚舉映射。

要啟用和自定義映射,只需在 AutoMapper-configuration 中創建相應的類型映射:

var mapperConfig = new MapperConfiguration(config =>
{
    config.CreateMap<SourceType, DestinationType>();
    config.CreateMap<Source, Destination>().ConvertUsingEnumMapping(opt => opt.MapByName());
    config.EnableEnumMappingValidation();
});

mapperConfig.AssertConfigurationIsValid();

然后它將驗證枚舉到枚舉的映射。

問題(終於^^)

由於我們的團隊以前沒有(不需要)為每個枚舉到枚舉的映射配置 AutoMapper(就像 AutoMapper 以前版本中的動態映射一樣),我們對如何有效地和確定性地發現需要以這種方式配置的每個 map。 特別是,因為我們每個 api(和每層)可能處理幾十個這樣的案例。

我們怎么可能做到這一點,我們已經驗證並調整了我們現有的代碼庫,並從一開始就進一步防止這種愚蠢行為?

利用自定義驗證在測試期間發現缺失的映射

好的,現在這種方法利用了多階段分析,最適合單元測試(盡管如此,它可能已經存在於您的解決方案中)。 它不是神奇地解決所有可能普遍存在的問題的金槍,而是讓你進入一個非常緊湊的開發循環,這應該有助於清理問題。 時期。

涉及的步驟是

  1. 啟用 AutoMapper 配置的驗證
  2. 使用 AutoMapper 自定義驗證來發現缺失的類型映射
  3. 添加和配置缺少的類型映射
  4. 確保地圖有效
  5. 適應枚舉或映射邏輯的變化(最適合的)

    這可能很麻煩,需要額外注意,具體取決於此方法發現的問題

  6. 沖洗並重復

下面的示例使用 xUnit。 使用您手頭可能擁有的任何東西。

0.起點

我們從您的初始 AutoMapper 配置開始:

var mapperConfig = new MapperConfiguration(config =>
{
    config.CreateMap<SourceType, DestinationType>();
});

1. 啟用 AutoMapper-Configuration 的驗證

在您的測試服中的某處,確保您正在驗證您的 AutoMapper 配置:


[Fact]
public void MapperConfigurationIsValid() => mapperConfig.AssertConfigurationIsValid();

2. 使用 AutoMapper 自定義驗證來發現缺失的類型映射

現在將您的 AutoMapper 配置修改為:

mapperConfig = new MapperConfiguration(config =>
            {
                config.CreateMap<SourceType, DestinationType>();

                config.Advanced.Validator(context => {

                    if (!context.Types.DestinationType.IsEnum) return;
                    if (!context.Types.SourceType.IsEnum) return;
                    if (context.TypeMap is not null) return;

                    var message = $"config.CreateMap<{context.Types.SourceType}, {context.Types.DestinationType}>().ConvertUsingEnumMapping(opt => opt.MapByName());";

                    throw new AutoMapperConfigurationException(message);
                });

                config.EnableEnumMappingValidation();
            });

這做了幾件事:

  1. 查找映射,即 map枚舉枚舉
  2. 沒有與之關聯的類型映射(也就是說,它們是由 AutoMapper 本身“生成”的,因此缺少顯式的CreateMap調用)
    if (!context.Types.DestinationType.IsEnum) return;
    if (!context.Types.SourceType.IsEnum) return;
    if (context.TypeMap is not null) return;
  1. 引發錯誤,該消息相當於缺少對CreateMap的實際調用
var message = $"config.CreateMap<{context.Types.SourceType}, {context.Types.DestinationType}>().ConvertUsingEnumMapping(opt => opt.MapByName());";

throw new AutoMapperConfigurationException(message);

3. 添加和配置缺少的類型映射

如果 output 像這樣,重新運行我們之前應該失敗的測試:

AutoMapper.AutoMapperConfigurationException : config.CreateMap<Sample.AutoMapper.EnumValidation.Source, Sample.AutoMapper.EnumValidation.Destination>().ConvertUsingEnumMapping(opt => opt.MapByName());

和繁榮,你有go。 銀盤上缺少的類型映射配置調用。

現在復制該行並將其放置在適合您的 AutoMapper 配置的地方。

對於這篇文章,我只是把它放在現有的下面:


config.CreateMap<SourceType, DestinationType>();
config.CreateMap<Sample.AutoMapper.EnumValidation.Source, Sample.AutoMapper.EnumValidation.Destination>().ConvertUsingEnumMapping(opt => opt.MapByName());

在現實世界的場景中,這將是每個枚舉到枚舉映射的一行,這些映射在 AutoMapper 配置中還沒有與之關聯的類型映射。 根據您實際配置 AutoMapper 的方式,可能需要稍微采用此行以滿足您的需要,例如用於 MappingProfiles。

  1. 適應枚舉的變化

從上面重新運行測試,現在也應該失敗,因為存在不兼容的枚舉值。 output 應如下所示:

    AutoMapper.AutoMapperConfigurationException : Missing enum mapping from Sample.AutoMapper.EnumValidation.Source to Sample.AutoMapper.EnumValidation.Destination based on Name
    The following source values are not mapped:
     - B
     - C
     - D
     - Executer
     - A1
     - B2
     - C3

go,AutoMapper 發現缺少或不可映射的枚舉值。

請注意,我們失去了對大小寫差異的自動處理。

現在要做什么在很大程度上取決於您的解決方案,並且不能在 SO-post 中涵蓋。 所以采取適當的措施來緩解。

6.沖洗並重復

Go 回到 3. 直到所有問題都解決。

從那時起,你應該有一個安全網,這應該可以防止你將來落入那種陷阱。

但是,請注意,映射每個名稱而不是每個值可能會對性能產生負面影響。 在將這種更改應用於您的代碼庫時,應該考慮到這一點。 但是由於存在所有這些層間映射,我猜想可能的瓶頸在另一座城堡,馬里奧;)

這篇文章中顯示的示例的完整總結可以在這個github-repo中找到

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM