繁体   English   中英

使用 AutoMapper v11.0.1 时写入未映射的 model 属性

[英]Write model properties not mapped when using AutoMapper v11.0.1

我正在开发一个 ASP.NET 核心项目,在该项目中我定义了一些Profile类,这些类用于通过我的 Startup class 中的此语句配置 AutoMapper: services.AddAutoMapper(typeof(CommissionRulesReadModelProfile).Assembly); .

我目前正在尝试更新以下依赖项:

  • AutoMapper v10.0.0 -> v11.0.1
  • AutoMapper.Extensions.Microsoft.DependencyInjection v8.0.1 -> v11.0.0

我发现更新到这些版本后,我的写入模型的某些属性没有映射到相应的域实体。

应用层/领域

在我的项目中,我有以下域实体和相应的写入模型:

编写模型和域实体

写入模型使用可公开访问的{ get; init; } { get; init; } { get; init; }属性,并且域实体都通过它们的构造函数设置了它们的属性。

我使用以下代码从应用层模型到域的 map:

public class CommissionRulesWriteModelProfile : Profile
{
    public CommissionRulesWriteModelProfile()
    {
        ShouldMapProperty = p => (p.GetMethod!.IsPublic || p.GetMethod.IsAssembly)
                                && p.Name != nameof(Entity.Id)
                                && p.Name != nameof(Entity.DomainEvents);

        // ...
        MapUnitCommissionRuleset();
        // ...
    }

    private void MapUnitCommissionRuleset()
    {
        var unitCommissionRulesetMap =
            CreateMap<UnitCommissionRulesetWriteModel, UnitCommissionRuleset>();
            
        unitCommissionRulesetMap.MapPerformanceMetricScoreRule(
            src => src.AverageCsiScoreRange,
            dest => dest.AverageCsiScoreRule,
            thresholds => new AverageCsiScoreRule(thresholds));

        // ...
        
        CreateMap<PointsThresholdWriteModel, UnitCommissionPointsThreshold>();
    }
}

public static class MappingExtensions
{
    /// <summary>
    /// Maps a performance metric rule within a commission ruleset write model to the domain-layer rule.
    /// </summary>
    public static void MapPerformanceMetricScoreRule<TRange, TRule>(
        this IMappingExpression<UnitCommissionRulesetWriteModel, UnitCommissionRuleset> map,
        Func<UnitCommissionRulesetWriteModel, TRange> rangeSelector,
        Expression<Func<UnitCommissionRuleset, TRule>> ruleSelector,
        Func<ICollection<UnitCommissionPointsThreshold>, TRule> constructRuleFromThresholds) where TRule : class
    {
        TRule MapRule(
            UnitCommissionRulesetWriteModel src,
            UnitCommissionRuleset dest,
            TRule rule,
            ResolutionContext ctx)
        {
            var range = rangeSelector(src);

            if (range is null)
                return null;

            var thresholds = ctx.Mapper.Map<List<UnitCommissionPointsThreshold>>(range);
            return constructRuleFromThresholds(thresholds);
        }

        map.ForMember(ruleSelector, opt => opt.MapFrom(MapRule));
    }
}

在 AutoMapper v10 中,这可以正常工作。 In v11 I find that the UnitCommissionRuleset.AverageCsiScoreRule is null even when the corresponding write model is populated, and that the MapRule local function within MapPerformanceMetricScoreRule is never invoked.

最小复制

以下 .NET 6 控制台应用程序在使用 AutoMapper v10.0.0 和 AutoMapper.Extensions.Microsoft.DependencyInjection v8.0.1 时执行没有问题,但在分别升级到 v11.0.1 和 v11.0.0 时无法执行 map。 该项目还依赖于Microsoft.Extensions.DependencyInjection v6.0.0。

如果此代码可以与 AutoMapper v11 一起使用,我希望该解决方案也适用于我的 ASP.NET 核心项目。

using System.Linq.Expressions;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static void Main(string[] args) 
    {
        var serviceProvider = new ServiceCollection()
            .AddAutoMapper(typeof(ExampleProfile).Assembly)
            .BuildServiceProvider();

        var mapper = serviceProvider.GetRequiredService<IMapper>();
        var writeModel = CreateExampleWriteModel();
        var unitCommissionRuleset = mapper.Map<UnitCommissionRuleset>(writeModel);

        if (unitCommissionRuleset.AverageCsiScoreRule is null)
            throw new Exception("Average CSI Score Rule property was not mapped");
    }

    private static UnitCommissionRulesetWriteModel CreateExampleWriteModel()
    {
        var averageCsiScoreRange = new List<PointsThresholdWriteModel>
        {
            new PointsThresholdWriteModel
            {
                Target = 50,
                Points = 1,
            },
            new PointsThresholdWriteModel
            {
                Target = 55,
                Points = 2,
            },
            new PointsThresholdWriteModel
            {
                Target = 60,
                Points = 3,
            },
        };

        return new UnitCommissionRulesetWriteModel
        {
            AverageCsiScoreRange = averageCsiScoreRange,
        };
    }
}

#region Mapping Logic

public class ExampleProfile : Profile
{
    public ExampleProfile()
    {
        var unitCommissionRulesetMap =
            CreateMap<UnitCommissionRulesetWriteModel, UnitCommissionRuleset>();

        unitCommissionRulesetMap.MapPerformanceMetricScoreRule(
            src => src.AverageCsiScoreRange,
            dest => dest.AverageCsiScoreRule,
            thresholds => new AverageCsiScoreRule(thresholds));

        CreateMap<PointsThresholdWriteModel, UnitCommissionPointsThreshold>();
    }
}

internal static class MappingExtensions
{
    public static void MapPerformanceMetricScoreRule<TRange, TRule>(
        this IMappingExpression<UnitCommissionRulesetWriteModel, UnitCommissionRuleset> map,
        Func<UnitCommissionRulesetWriteModel, TRange> rangeSelector,
        Expression<Func<UnitCommissionRuleset, TRule>> ruleSelector,
        Func<ICollection<UnitCommissionPointsThreshold>, TRule> constructRuleFromThresholds) where TRule : class
    {
        TRule MapRule(
            UnitCommissionRulesetWriteModel src,
            UnitCommissionRuleset dest,
            TRule rule,
            ResolutionContext ctx)
        {
            var range = rangeSelector(src);

            if (range is null)
                return null;

            var thresholds = ctx.Mapper.Map<List<UnitCommissionPointsThreshold>>(range);
            return constructRuleFromThresholds(thresholds);
        }

        map.ForMember(ruleSelector, opt => opt.MapFrom(MapRule));
    }
}

#endregion

#region Write Models

public class UnitCommissionRulesetWriteModel
{
    public ICollection<PointsThresholdWriteModel> AverageCsiScoreRange { get; init; }
}

public class PointsThresholdWriteModel
{
    public int Target { get; init; }
    public int Points { get; init; }
}
#endregion

#region Domain

public class UnitCommissionRuleset
{
    public UnitCommissionRuleset(AverageCsiScoreRule? averageCsiScoreRule = null)
    {
        AverageCsiScoreRule = averageCsiScoreRule;
    }

    public AverageCsiScoreRule? AverageCsiScoreRule { get; private init; }
}

public class AverageCsiScoreRule
{
    public AverageCsiScoreRule(ICollection<UnitCommissionPointsThreshold> thresholds)
    {
        Thresholds = thresholds.ToList();
    }

    public IReadOnlyCollection<UnitCommissionPointsThreshold> Thresholds { get; private init; } = null!;
}

public class UnitCommissionPointsThreshold
{
    public UnitCommissionPointsThreshold(int target, int points)
    {
        Target = target;
        Points = points;
    }

    public int Target { get; private init; }
    public int Points { get; private init; }
}

#endregion

我试过的

我已经看到并阅读了MapFrom 在 AutoMapper 11.0.1 中无法正常工作,但是根据该帖子中的讨论和链接文档,我无法取得任何进展。

作为一个实验,我尝试通过将 SSCCE 中的服务设置语句更改为services.AddAutoMapper(cfg => cfg.DisableConstructorMapping(), typeof(ExampleProfile).Assembly); . 这会导致引发以下异常:

ArgumentException: UnitCommissionPointsThreshold needs to have a constructor with 0 args or only optional args. Validate your configuration for details.

编辑

下面是一个简化的示例,它也演示了不使用 DI 的这个问题:

public class Program
{
    public static void Main()
    {
        var mapperConfiguration = new MapperConfiguration(c => c.AddProfile<ExampleProfile>());
        var mapper = mapperConfiguration.CreateMapper();
        var writeModel = CreateExampleWriteModel();
        var parent = mapper.Map<Parent>(writeModel);

        if (parent.ChildGroup is null)
            throw new Exception("ChildGroup property was not mapped");
    }

    private static ParentWriteModel CreateExampleWriteModel()
    {
        var children = new List<ChildWriteModel>
        {
            new()
            {
                Foo = 1
            },
            new()
            {
                Foo = 2
            },
            new()
            {
                Foo = 3
            }
        };

        return new ParentWriteModel
        {
            Children = children
        };
    }
}

#region Mapping Logic

public class ExampleProfile : Profile
{
    public ExampleProfile()
    {
        CreateMap<ParentWriteModel, Parent>().ForMember(
            dest => dest.ChildGroup,
            opt => opt.MapFrom((src, _, _, ctx) => new ChildGroup(ctx.Mapper.Map<List<Child>>(src.Children))));

        CreateMap<ChildWriteModel, Child>();
    }
}

#endregion

#region Write Models

public class ParentWriteModel
{
    public ICollection<ChildWriteModel> Children { get; init; }
}

public class ChildWriteModel
{
    public int Foo { get; init; }
}

#endregion

#region Domain

public class Parent
{
    public Parent(ChildGroup? childGroup = null)
    {
        ChildGroup = childGroup;
    }

    public ChildGroup? ChildGroup { get; private init; }
}

public class ChildGroup
{
    public ChildGroup(ICollection<Child> thresholds)
    {
        Children = thresholds.ToList();
    }

    public IReadOnlyCollection<Child> Children { get; } = null!;
}

public class Child
{
    public Child(int foo)
    {
        Foo = foo;
    }

    public int Foo { get; private init; }
}

#endregion

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM