gpt4 book ai didi

c# - 如何使用 AutoMapper 根据展平属性的名称查找源属性

转载 作者:太空狗 更新时间:2023-10-29 17:54:16 25 4
gpt4 key购买 nike

我正在使用 AutoMapper,我希望它根据映射(扁平化)目标属性的名称来追溯源属性。

这是因为我的 MVC Controller 具有映射属性的名称,它需要提供给用于排序目的的服务调用。服务需要知道映射源自的属性的名称(并且 Controller 不应该知道它),以便对实际对数据进行排序的存储库执行正确的调用。

例如:

[Source.Address.ZipCode] maps to [Destination.AddressZipCode]

然后

Trace "AddressZipCode" back to [Source.Address.ZipCode]

这是 AutoMapper 可以为我做的事情,还是我需要求助于挖掘 AutoMapper 的 map 数据?

更新

Jimmy Bogard 告诉我这应该是可能的,但不是以一种显而易见的方式。它需要加载类型映射并通过它。我已经对其进行了简要调查,但似乎我需要访问内部类型才能获得进行反向映射所需的属性映射信息。

更新 2

我决定提供更多详细信息。

当我加载类型映射时,我发现其中有两个用于隐式 ZipCode 映射的源值解析器:

  • 一个AutoMapper.Internal.PropertyGetter获取地址。
  • 一个AutoMapper.Internal.PropertyGetter获取邮政编码。

当我有一个显式映射(指定了一个 lambda 表达式)时,我发现没有源值解析器,而是一个自定义解析器:

  • 一个AutoMapper.DelegateBasedResolver<Company,string>我认为它包含我的显式映射 lambda 表达式。

不幸的是,这些解析器是内部的,所以我只能通过反射(我真的不想这样做)或通过更改 AutoMapper 源代码来访问它们。

如果我可以访问它们,我可以通过遍历值解析器或检查自定义解析器来解决问题,尽管我怀疑这会引导我回到映射 lambda 表达式,我需要构建未展平的属性名称(实际上是由点分隔的一系列属性名称)。

最佳答案

目前,我编写了一个辅助类,可以从级联属性链中确定原始属性链。当然,当 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);

关于c# - 如何使用 AutoMapper 根据展平属性的名称查找源属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3405149/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com