How to map .NET function call to property automati

2019-07-01 15:56发布

问题:

Edit: This was originally for 1:1 mapping, but I figured out that I needed a more complicated recursive mapping so a new question was posted here: How to recursively map entity to view model with Automapper function call?

I am trying to generically map an entity class function to a view model, using ServiceStack ConvertTo<> method. This maps all similar types and property names and works fine, but I want to find a way to map the result of a function to a property. Here is some code

Entity example:

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

ViewModel example:

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

The ideal result would be to have a map function that looks at the entity class for a method that matches the return type and the function name is "Get" + Property name, then to execute it and map the result to the view model.

回答1:

While you might be able to find that something like Automapper or Value Injecter could already contain this functionality, there's nothing from stopping you from writing a quick little utility method:

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;
}

The conditions in the .FirstOrDefault calls above just use Name, but you could expand them to whatever you like.

To use this, just call it like so:

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

Just be careful if you have any parameters for your methods in your regular model. If you find that your models are too complex (or don't follow one or two naming conventions), then you might be better off with something like AutoMapper. For simple mappings, though, something like this should do the job.



回答2:

Doing this mapping with ServiceStack would be quite difficult to make a general solution, as you'd basically have to write the code to match Products to GetProducts yourself.

You could use another mapping library, like AutoMapper, which automatically uses GetX() to populate 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()

It's also much easier to manually change how certain things are mapped, e.g. you could manually set this one using this CreateMap:

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

It looks like AutoMapper is a much more robust mapper. Even if you use ServiceStack for other portions, it may be worth it to use AutoMapper for your mapping.