使用自动映射器但不使用ForMember或用于扁平化的命名约定来扁平化对象

时间:2018-10-07 00:07:45

标签: c# automapper

问题

使用自动映射器平展大型多级对象,以允许我使用Dapper将值传递给sql。

选项

在阅读了文档以及关于SO的各种答案之后,似乎有两个选择,使用ForMember或使用NamingConventions。这些对象都不适合我,因为对象太大,ForMember几乎需要手动映射。 NamingConventions表示使用目标对象的位置的一些不可读且不相关的名称。

我使用的

refs:ForMember flattening

我确实找到了一个使用ITypeConverterCustom type converters)的非常基本的示例,并尝试使其对我有用,但是经过三天的尝试,我真的很努力我应该寻求帮助。

在搜索了各种论坛之后,我决定尝试创建一个通用的解决方案,因为我将在整个项目中多次重复使用此解决方案。

请注意,如果有更好的方法(我想念现有的扩展名或配置,我很想听听。如果不能,请提供一些有关如何完成任务的指导-我感觉就像我的头撞在砖墙上)

在下面,我添加了我创建的控制台应用程序以尝试解决此问题(我觉得我应该说我知道这段代码很糟糕,我只是在hacking以期希望能起作用)

using AutoMapper;
using System;
using System.Linq;
using System.Reflection;

namespace AutomapperTests
{
    public class Program
    {
        static void Main(string[] args)
        {
            var property = Program.CreateProperty();
            var config = new MapperConfiguration(cfg => cfg.CreateMap<PropertyDto, FlatProperty>()
                                                           .ConvertUsing<MyTypeConverter<PropertyDto, FlatProperty>>());

            var mapper = config.CreateMapper();

            var flatProperty = mapper.Map<FlatProperty>(property);

        }

        private static PropertyDto CreateProperty()
        {
            var property = new PropertyDto();
            property.Guid = new Guid();//not mapping to guid
            property.SomeTestVal = 123456;
            property.TransactionStatus = TransactionStatus.ForSale;
            property.Address = new AddressDto();
            property.Detail = new DetailDto();
            property.Address.PostCode1 = "ng10";
            property.Detail.LandArea = 12;
            return property;
        }
    }

    public class MyTypeConverter<T, TK> : ITypeConverter<T, TK>
    {
        public TK Convert(T source, TK destination, ResolutionContext context)
        {
            var sourceType = source.GetType();
            PropertyInfo[] sourceClassProperties = sourceType.GetProperties();//note: i've tried flattening the structure at this point but failed

            var properties = GetCustomObjectsFromProperties(sourceType.GetProperties());

            destination = (TK)Activator.CreateInstance(typeof(TK));

            var destinationType = destination.GetType();
            PropertyInfo[] destinationProperties = destinationType.GetProperties();

            foreach (PropertyInfo sourceClassProperty in sourceClassProperties)
            {
                SetValue(sourceClassProperty, source, destinationProperties, destination);
            }

            return destination;
        }

        private void SetValue(PropertyInfo sourceClassProperty, T source, PropertyInfo[] destinationProperties, TK destination)
        {
            bool isNativeClass = IsNativeClass(sourceClassProperty);

            object sourceValue = sourceClassProperty.GetValue(source);
            string sourceName = sourceClassProperty.Name;

            PropertyInfo destProperty = destinationProperties.FirstOrDefault(x => x.Name == sourceName);

            if (destProperty == null && !isNativeClass)
            {
                //note: my idea was to use recursion to enter the object if necessary and search for a matching value ---maybe i'm really overthinking this???
            }

            SetDestinationValue(destProperty, destination, sourceValue);
        }

        private bool IsNativeClass(PropertyInfo sourceClassProperty)
        {
            return sourceClassProperty.GetType().Namespace == "System";
        }

        private void SetDestinationValue(PropertyInfo destProperty, TK destination, object sourceValue)
        {
            if (destProperty != null)
            {
                destProperty.SetValue(destination, sourceValue);
            }
        }

        private PropertyInfo[] GetCustomObjectsFromProperties(PropertyInfo[] propertyInfo)
        {
            return propertyInfo.Where(info => info.Module.Name == GetAssemblyName()).ToArray();
        }

        private string GetAssemblyName()
        {
            return $"{typeof(T).Assembly.GetName().Name}.dll";
        }
    }

    public class FlatProperty
    {
        public Guid Guid { get; set; }
        public int SomeTestVal { get; set; }
        public int? TransactionStatusId { get; set; }
        public string Postcode1 { get; set; }
        public decimal? LandArea { get; set; }
    }

    public class PropertyDto
    {
        public Guid Guid { get; set; }
        public int SomeTestVal { get; set; }
        public TransactionStatus? TransactionStatus { get; set; }
        public AddressDto Address { get; set; }
        public DetailDto Detail { get; set; }

    }

    public class AddressDto
    {
        public string PostCode1 { get; set; }
    }

    public class DetailDto
    {
        public decimal? LandArea { get; set; }
    }

    public enum TransactionStatus
    {
        Sold = 1,
        ForSale = 2
    }
}

编辑:

我刚刚发现我似乎可以做这样的事情:

在配置中:

        CreateMap<AddressDto, CreatePropertyDataModel>();
        CreateMap<DetailDto, CreatePropertyDataModel>();
        CreateMap<PropertyDto, CreatePropertyDataModel>()
           .ForMember(dest => dest.Guid,
                      opts => opts.MapFrom(
                              src => src.Guid.ToString()));

设置对象:

        var property = new PropertyDto();
        property.Guid = new Guid();//not mapping to guid
        property.SomeTestVal = 123456;
        property.TransactionStatus = TransactionStatus.ForSale;
        property.Address = new AddressDto();
        property.Detail = new DetailDto();
        property.Address.PostCode1 = "ng10";
        property.Detail.LandArea = 12;
        return property;

在某些班上:

        var dataModel = new UpsertPropertyDataModel();
        _mapper.Map(_property, dataModel);
        _mapper.Map(_property.Address, dataModel);
        _mapper.Map(_property.Detail, dataModel);

我的问题是,我得到了错误Unmapped members were found,这些未映射的成员引用了地址或明细无法映射到的成员,因此我必须将那些成员设置为Ignore(),是这是我使用不正确的可行解决方案? (请注意,我现实世界中的物体要大得多)

0 个答案:

没有答案