为什么在使用记录时必须忽略 AutoMapper 中的所有其他属性?

Why do I have to Ignore all other properties in AutoMapper when using records?

public record Destination(double X, double Y);

public struct Source
{

    public double X { get; set; }

    public Potato Potato { get; set; }

    public double Z { get; set; }
}


public struct Potato
{
    public double Y { get; set; }
}

 public MappingProfile()
{
   CreateMap<Source, Destination>();
   .ForCtorParam(nameof(Destination.Y), e => e.MapFrom(x => x.Potato.Y))
   .ForAllOtherMembers(x => x.Ignore());
}

要将源映射到目标,我需要手动映射其中一个子项。 然而,automapper 会给出一个极其混乱的消息,说 Y 属性 未映射。

Unmapped members were found. Review the types and members below.
    Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
    For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
    ==========================================================================================================
    Source -> Destination (Destination member list)


Unmapped properties:
Y

我发现通过添加忽略所有其他成员的行,它会 'solve' 问题。有没有更好的方法来防止这个错误的发生?

报错信息提到映射所有构造函数参数,但即使我添加.ForCtorParam(nameof(Destination.X), e => e.MapFrom(x => x.X))错误仍然出现。

这已经在 11 中解决了。因为您无法升级,所以您将不得不忽略所有这些属性。问题是属性有设置器,在 AM 10 中,属性不被视为映射,即使它们已经通过构造函数映射。

另一个解决方案是使用不带 setter 的结构(或 class)而不是 record

我创建了以下 class 来帮助满足我的需求,因为我无法升级到更新的 AutoMapper 版本。

这很天真,并假定记录上的所有属性都应映射到构造函数。

好处:

  1. 将自动映射同名的所有属性
  2. 可以使用 lambda 参数代替 nameof
  3. 不需要调用忽略其他成员
  4. 如果 属性 未映射
  5. ,将抛出异常

要使用它,在 MappingProfile:

new RecordMapBuilder<TSource, TDestination>(this)
            .Map(x => x.Foo, x => x.Offset.Foo)
            .Build();
        public class RecordMapBuilder<TSource, TDestination>
        {
            private readonly Profile _profile;
            private readonly bool _autoMapMatchingProperties;
            private readonly Dictionary<string, Expression<Func<TSource, object>>> _ctorParamActions = new();
            
            public RecordMapBuilder(Profile profile, bool autoMapMatchingProperties = true)
            {
                _profile = profile;
                _autoMapMatchingProperties = autoMapMatchingProperties;
            }

            public void Build()
            {
                var map = _profile.CreateMap<TSource, TDestination>();

                var unmappedDestinationProperties = new HashSet<string>(
                    typeof(TDestination)
                    .GetProperties()
                    .Where(e => !_ctorParamActions.ContainsKey(e.Name))
                    .Select(e => e.Name));

                var sourceProperties = new HashSet<string>(typeof(TSource)
                    .GetProperties()
                    .Select(e => e.Name));

                var mappableProperties = unmappedDestinationProperties.Intersect(sourceProperties).ToHashSet();
                var unMappableProperties = unmappedDestinationProperties.Except(sourceProperties).ToHashSet();

                if (unMappableProperties.Any())
                {
                    var properties = string.Join(", ", unMappableProperties);
                    throw new InvalidOperationException($"Not all properties mapped for type {typeof(TSource)} -> {typeof(TDestination)}: {properties}");
                }

                if (_autoMapMatchingProperties)
                {
                    foreach (var name in mappableProperties)
                    {
                        map.ForCtorParam(name, x => x.MapFrom(name));
                    }
                }

                foreach (var kv in _ctorParamActions)
                {
                    map.ForCtorParam(kv.Key, x => x.MapFrom(kv.Value));
                }

                map.ForAllOtherMembers(x => x.Ignore());
            }

            public RecordMapBuilder<TSource, TDestination> Map(Expression<Func<TDestination, object>> destination, Expression<Func<TSource, object>> source)
            {
                var name = GetName(destination);
                _ctorParamActions[name] = source;
                return this;
            }

            private static string GetName(Expression<Func<TDestination, object>> destination)
            {
                {
                    if (destination.Body is UnaryExpression ue && ue.Operand is MemberExpression me)
                    {
                        return me.Member.Name;
                    }
                }

                {
                    if (destination.Body is MemberExpression me)
                    {
                        return me.Member.Name;
                    }
                }

                throw new InvalidOperationException($"Unhandled expression of type: {destination.Body.GetType()}");
            }