Как найти исходное свойство на основе имени сплющенного свойства с помощью AutoMapper - PullRequest
9 голосов
/ 04 августа 2010

Я использую AutoMapper, и мне бы хотелось, чтобы он отслеживал исходное свойство на основе имени сопоставленного (сплющенного) целевого свойства.

Это связано с тем, что у моего контроллера MVC есть имя сопоставленного свойства, которое необходимо предоставить вызову службы для сортировки. Службе необходимо знать имя свойства, из которого было получено сопоставление (и контроллер не должен его знать), чтобы выполнить правильный вызов хранилища, которое фактически сортирует данные.

Например:

[Source.Address.ZipCode] отображается на [Destination.AddressZipCode]

Тогда

Трассировка "AddressZipCode" обратно в [Source.Address.ZipCode]

Это то, что AutoMapper может сделать для меня, или мне нужно прибегнуть к копанию в данных сопоставления AutoMapper?

UPDATE

Джимми Богард сказал мне, что это должно быть возможно, но не очевидным образом. Это требует загрузки карты типов и прохождения через нее. Я кратко рассмотрел его, но, похоже, мне нужен доступ к внутренним типам, чтобы получить информацию о сопоставлении свойств, которая требуется для обратного сопоставления.

ОБНОВЛЕНИЕ 2

Я решил предоставить более подробную информацию.

Когда я загружаю карту типов, я обнаруживаю, что в ней есть два преобразователя значений источника для неявного отображения ZipCode:

  • a AutoMapper.Internal.PropertyGetter, который получает адрес.
  • a AutoMapper.Internal.PropertyGetter, который получает ZipCode.

Когда у меня есть явное сопоставление (для которого задано лямбда-выражение), я не нахожу никакого распознавателя исходного значения, а пользовательского распознавателя:

  • a AutoMapper.DelegateBasedResolver<Company,string>, который, как мне кажется, содержит мое явное отображение лямбда-выражения.

К сожалению, эти средства распознавания являются внутренними, поэтому я могу получить к ним доступ только через отражение (что я действительно не хочу делать) или путем изменения исходного кода AutoMapper.

Если бы я мог получить к ним доступ, я мог бы решить эту проблему, либо просматривая распознаватели значений, либо проверяя пользовательский распознаватель, хотя я сомневаюсь, что это вернуло бы меня к отображению лямбда-выражения, которое мне нужно, чтобы построить неразглашенное имя свойства (фактически набор имен свойств, разделенных точками).

Ответы [ 4 ]

4 голосов
/ 02 ноября 2010

В настоящее время я написал вспомогательный класс, который может определять исходную цепочку свойств из объединенной цепочки свойств.Конечно, это станет устаревшим, когда AutoMapper получит функцию для такого рода действий.

using System.Globalization;
using System.Reflection;

/// <summary>
///     Resolves concatenated property names back to their originating properties.
/// </summary>
/// <remarks>
///     An example of a concatenated property name is "ProductNameLength" where the originating
///     property would be "Product.Name.Length".
/// </remarks>
public static class ConcatenatedPropertyNameResolver
{
    private static readonly object mappingCacheLock = new object();
    private static readonly Dictionary<MappingCacheKey, string> mappingCache = new Dictionary<MappingCacheKey, string>();

    /// <summary>
    ///     Returns the nested name of the property the specified concatenated property
    ///     originates from.
    /// </summary>
    /// <param name="concatenatedPropertyName">The concatenated property name.</param>
    /// <typeparam name="TSource">The mapping source type.</typeparam>
    /// <typeparam name="TDestination">The mapping destination type.</typeparam>
    /// <returns>
    ///     The nested name of the originating property where each level is separated by a dot.
    /// </returns>
    public static string GetOriginatingPropertyName<TSource, TDestination>(string concatenatedPropertyName)
    {
        if (concatenatedPropertyName == null)
        {
            throw new ArgumentNullException("concatenatedPropertyName");
        }
        else if (concatenatedPropertyName.Length == 0)
        {
            throw new ArgumentException("Cannot be empty.", "concatenatedPropertyName");
        }

        lock (mappingCacheLock)
        {
            MappingCacheKey key = new MappingCacheKey(typeof(TSource), typeof(TDestination), concatenatedPropertyName);

            if (!mappingCache.ContainsKey(key))
            {
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;

                List<string> result = new List<string>();
                Type type = typeof(TSource);

                while (concatenatedPropertyName.Length > 0)
                {
                    IEnumerable<PropertyInfo> properties = type.GetProperties(bindingFlags).Where(
                        n => concatenatedPropertyName.StartsWith(n.Name)).ToList();

                    if (properties.Count() == 1)
                    {
                        string match = properties.First().Name;
                        result.Add(match);
                        concatenatedPropertyName = concatenatedPropertyName.Substring(match.Length);
                        type = type.GetProperty(match, bindingFlags).PropertyType;
                    }
                    else if (properties.Any())
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Ambiguous properties found for {0} on type {1}: {2}.",
                                concatenatedPropertyName,
                                typeof(TSource).FullName,
                                string.Join(", ", properties.Select(n => n.Name))));
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "No matching property found for {0} on type {1}.",
                                concatenatedPropertyName,
                                typeof(TSource).FullName));
                    }
                }

                mappingCache.Add(key, string.Join(".", result));
            }

            return mappingCache[key];
        }
    }

    /// <summary>
    ///     A mapping cache key.
    /// </summary>
    private struct MappingCacheKey
    {
        /// <summary>
        ///     The source type.
        /// </summary>
        public Type SourceType;

        /// <summary>
        ///     The destination type the source type maps to. 
        /// </summary>
        public Type DestinationType;

        /// <summary>
        ///     The name of the mapped property.
        /// </summary>
        public string MappedPropertyName;

        /// <summary>
        ///     Initializes a new instance of the <see cref="MappingCacheKey"/> class.
        /// </summary>
        /// <param name="sourceType">The source type.</param>
        /// <param name="destinationType">The destination type the source type maps to.</param>
        /// <param name="mappedPropertyName">The name of the mapped property.</param>
        public MappingCacheKey(Type sourceType, Type destinationType, string mappedPropertyName)
        {
            SourceType = sourceType;
            DestinationType = destinationType;
            MappedPropertyName = mappedPropertyName;
        }
    }
}

Вот пример использования:

class TestEntity
{
    public Node Root {get; set;}
}

class Node
{
    public string Leaf {get; set;}
}

class TestFlattenedEntity
{
    public string RootLeaf {get; set;}
}

string result = ConcatenatedPropertyNameResolver.GetOriginatingPropertyName<TestEntity, TestFlattenedEntity>("RootLeaf");

Assert.AreEqual("Root.Leaf", result);
1 голос
/ 16 ноября 2010

Я изучал ту же проблему и придумал следующий фрагмент кода. Это придумано с непроверенной цепочкой собственности от AutoMapper. Я получил некоторое вдохновение от решения sgriffinusa.

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

public static class TypeMapExtensions
{
    public static MemberInfo[] TryGetSourceProperties(this TypeMap @this, string propertyName)
    {
        if (@this != null)
        {
            var propertyMap = @this.GetPropertyMaps()
                .Where(p => p.DestinationProperty.Name == propertyName).FirstOrDefault();

            if (propertyMap != null)
            {
                var sourceProperties = propertyMap.GetSourceValueResolvers().OfType<IMemberGetter>();
                if (sourceProperties.Any())
                    return sourceProperties.Select(x => x.MemberInfo).ToArray();
            }
        }
        return null;
    }

    /// <summary>
    /// Trys to retrieve a source property name, given a destination property name. Only handles simple property mappings, and flattened properties.
    /// </summary>
    public static string TryGetSourcePropertyName(this TypeMap @this, string propertyName)
    {
        var members = TryGetSourceProperties(@this, propertyName);
        return (members == null) ? null : string.Join(".", members.Select(x => x.Name).ToArray());
    }
}

И вы получите желаемую карту типов, используя:

Mapper.FindTypeMapFor<TSource, TDestination>();
1 голос
/ 06 августа 2010

Я столкнулся с аналогичной необходимостью с AutoMapper.Вот решение, которое я могу придумать.Я проверил это только с очень простыми отображениями.В основном один класс другому с использованием только свойств (в основном это поведение по умолчанию Mapper.CreateMap. Я предполагаю, что существует только одно отображение, поэтому я использую First вместо перебора коллекций.

    private MemberInfo getSource(Type destinationType, string destinationPropertyname)
    {
        TypeMap map = Mapper.GetAllTypeMaps().Where(m => m.DestinationType.Equals(destinationType)).First();

        IEnumerable<PropertyMap> properties = map.GetPropertyMaps().Where(p => p.DestinationProperty.Name.Equals(destinationPropertyname, StringComparison.CurrentCultureIgnoreCase));

        PropertyMap sourceProperty = properties.First();

        IMemberGetter mg = sourceProperty.GetSourceValueResolvers().Cast<IMemberGetter>().First();

        return mg.MemberInfo;
    }
0 голосов
/ 09 июня 2012

с ValueInjecter вы можете сделать:

var trails = TrailFinder.GetTrails(flatPropertyName, target.GetType().GetInfos(), t => true);

target - это незакрашенный объект (нам нужно передать коллекцию PropertyInfo)

trails будет списком строк, поэтому

var result = string.join(".",trails.ToArray());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...