【问题标题】:How to map .NET function call to property automatically?如何将 .NET 函数调用自动映射到属性?
【发布时间】:2013-12-14 02:40:05
【问题描述】:

编辑:这最初是用于 1:1 映射,但我发现我需要更复杂的递归映射,因此在此处发布了一个新问题:How to recursively map entity to view model with Automapper function call?

我正在尝试使用 ServiceStack ConvertTo 方法将实体类函数一般映射到视图模型。这映射了所有相似的类型和属性名称并且工作正常,但我想找到一种方法将函数的结果映射到属性。这是一些代码

实体示例:

public class Item {
    public long Id {get; set;}
    public List<Product> GetProducts() {
          return Repository.GetAll<Product>();
    }
}

ViewModel 示例:

 public class ItemViewModel {
   public long Id {get;set;}
   public List<Product> Products {get; set;}
 }

理想的结果是有一个映射函数,它在实体类中查找与返回类型匹配的方法,函数名称是“Get”+属性名称,然后执行它并将结果映射到视图型号。

【问题讨论】:

    标签: c# asp.net servicestack


    【解决方案1】:

    虽然您可能会发现 AutomapperValue Injecter 之类的东西可能已经包含此功能,但没有什么可以阻止您编写一个快速的小实用方法:

    public TTarget MapToViewModel<TSource, TTarget>(TSource source)
        where TTarget : new()
    {
        var sourceType = source.GetType();
    
        var targetProperties = typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    
        var sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var sourceMethods = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
    
        var target = Activator.CreateInstance<TTarget>();
    
        foreach (var prop in targetProperties)
        {
            var sourceProp = sourceProperties.FirstOrDefault(x => x.Name == prop.Name);
            if (sourceProp != null)
            {
                prop.SetValue(target, sourceProp.GetValue(source, null), null);
            }
            else
            {
                var sourceMethod = sourceMethods.FirstOrDefault(x => x.Name == "Get" + prop.Name);
                if (sourceMethod != null)
                {
                    prop.SetValue(target, sourceMethod.Invoke(source, null), null);
                }
            }
        }
    
        return target;
    }
    

    上述.FirstOrDefault 调用中的条件仅使用Name,但您可以将它们扩展为您喜欢的任何内容。

    要使用它,只需像这样调用它:

    var itemViewModel = MapToViewModel<Item, ItemViewModel>(item);
    

    如果您的常规模型中的方法有任何参数,请务必小心。如果你发现你的模型太复杂(或者不遵循一两个命名约定),那么你最好使用 AutoMapper 之类的东西。但是,对于简单的映射,这样的事情应该可以完成。

    【讨论】:

    • 谢谢,我搞定了。现在我有另一个困境。使这个递归有多困难?例如,如果 Product 类有一个属性 List Images 并且我需要从 Product 类调用 GetImages()?
    • 我并没有真正看到这需要递归调用。您的意思是如果对象上同时存在方法和属性,则需要调用该方法吗?如果是这种情况,您可以切换属性和方法的if 语句以首先检查方法。
    • 不完全是,我的意思是如果映射的对象有另一个属性需要通过调用不同的函数来映射。您的回答帮助我确定我需要一些更复杂的东西,看起来 Automapper 可能能够处理。我在这里问了另一个Q stackoverflow.com/questions/20573600/…
    • 是的,使用 AutoMapper,您可以为多个对象设置地图,然后将其发送出去。例如,您可以致电Mapper.CreateMap&lt;Product, ProductViewModel&gt;()Mapper.CreateMap&lt;Image, ImageViewModel&gt;()。然后,当您调用 Mapper.Map&lt;ProductViewModel&gt;(product) 时,它应该返回您需要的内容。
    【解决方案2】:

    使用 ServiceStack 进行这种映射很难形成一个通用的解决方案,因为您基本上必须自己编写代码来匹配 ProductsGetProducts

    您可以使用另一个映射库,例如 AutoMapper,它会自动使用 GetX() 填充 X

    // one time config
    Mapper.CreateMap<Item, ItemViewModel>();
    Mapper.AssertConfigurationIsValid();
    
    // whenever you map
    Item item = // whatever
    ItemViewModel viewModel = Mapper.Map<ItemViewModel>(item);
    // viewModel.Products is populated from item.GetProducts()
    

    手动更改某些事物的映射方式也容易得多,例如你可以使用这个CreateMap手动设置这个:

    Mapper.CreateMap<Item, ItemViewModel>()
        .ForMember(dest => dest.Products,
                          opt => opt.ResolveUsing(src => src.GetProducts()));
    

    看起来 AutoMapper 是一个更强大的映射器。即使您将 ServiceStack 用于其他部分,使用 AutoMapper 进行映射也是值得的。

    【讨论】:

    • 很好,我认为 AutoMapper 可以做这样的事情(即使它只是使用 .AfterMap 功能)
    • 很好,我没有考虑 Automapper。 Automapper 会递归地支持这个吗?
    • 是的(我认为;不确定您所说的“递归”是什么意思),例如如果ProductsGetProducts 返回不同的类型,但是在这两种产品类型之间创建了一个映射(例如Mapper.CreateMap&lt;Product, ProductViewModel&gt;();),它也会自动映射这些项目。
    • 添加了一个衍生问题,因为我认为 Automapper 将能够更轻松地做我想做的事。 stackoverflow.com/questions/20573600/…
    猜你喜欢
    • 1970-01-01
    • 2019-07-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-18
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 2021-09-15
    相关资源
    最近更新 更多