【问题标题】:How to configure AutoMapper with generic types如何使用泛型类型配置 AutoMapper
【发布时间】:2017-02-28 19:09:37
【问题描述】:

我正在尝试在我的实体中实现属性版本跟踪。使用TrackedProperty 的架构作为我的可跟踪属性;

public class PropertyVersion<TValue, TVersion>
{
    public TVersion Version { get; set; }
    public TValue Value { get; set; }
}


public class TrackedProperty<TValue, TVersion> : List<PropertyVersion<TValue, TVersion>>
{

}

例如,在我的存储库中,我将保留TrackedFoo 对象,并且我将能够从Foo 中检索特定版本的数据(在这种情况下,该版本被描述为时间对象)。

public class TrackedFoo
{
    public string Id { get; set; }
    public TrackedProperty<string, DateTime> Name { get; set; }
}

public class Foo
{
    public string Id { get; set; }
    public string Name { get; set; }
}

我希望尽可能保持通用性。所以我尝试使用 AutoMapper,但我无法以通用方式配置它,我不需要配置每个 TrackedTypeXTypeX 的映射。

我需要获取 TrackedProperty 中的最后一个 PropertyVersion 项并将其 Value 属性映射到 TValue 类型

你能帮我找到解决这个问题的方法吗?

我可以使用

减少变量类型的数量(这就是你所说的 之间的东西吗?,无论如何)
DateTrackedProperty<TValue> : TrackedProperty<TValue,DateTime>
IntegerTrackedProperty<TValue> : TrackedProperty<TValue, int>
StringTrackedProperty<TValue> : TrackedProperty<TValue, string>

我可以为这三种类型编写 AutoMapper 配置。

【问题讨论】:

  • 好的,在谈到泛型映射之前,从TrackedFooFoo 的具体映射是什么?更具体地说,您将如何将TrackedProperty&lt;string, DateTime&gt; 转换为string
  • @IvanStoev, TrackedProperty 具有字符串值属性。所以我只需要得到那个值,不需要花哨的转​​换
  • 啊哈,你为什么不在问题中显示它呢? :)
  • @IvanStoev 抱歉。 TrackedProperty 没有 Value 属性。它实际上是包含 value 属性的 PropertyVersion 列表。我需要获取 TrackedProperty 中的最后一个 PropertyVersion 并使用那个。

标签: c# generics automapper


【解决方案1】:

最后我设法创建了一个通用映射配置文件(只有一种方式) 通过将以下内容放在我的 MappingProfile 中

CreateMap(typeof(PropertyVersion<,>), typeof(object)).ConvertUsing(typeof(PropertyVersionToValueConverter<,>));
CreateMap(typeof(TrackedProperty<,>), typeof(PropertyVersion<,>)).ConvertUsing(typeof(TrackedPropertyToPropertyVersionConverter<,>));
CreateMap(typeof(TrackedProperty<,>), typeof(object)).ConvertUsing(typeof(TrackedPropertyToValueConverter<,>));

在哪里

public class PropertyVersionToValueConverter<TValue, TVersion> : ITypeConverter<PropertyVersion<TValue, TVersion>, TValue>
{
    public TValue Convert(PropertyVersion<TValue, TVersion> source, TValue destination, ResolutionContext context)
    {
        if (source != null)
            return source.Value;
        return default(TValue);
    }
}

public class TrackedPropertyToPropertyVersionConverter<TValue, TVersion> : ITypeConverter<TrackedProperty<TValue, TVersion>, PropertyVersion<TValue, TVersion>>
{
    public PropertyVersion<TValue, TVersion> Convert(TrackedProperty<TValue, TVersion> source, PropertyVersion<TValue, TVersion> destination, ResolutionContext context)
    {
        if (source != null && source.Count > 0)
            return source.Last();

        else return default(PropertyVersion<TValue, TVersion>);
    }
}

public class TrackedPropertyToValueConverter<TValue, TVersion> : ITypeConverter<TrackedProperty<TValue, TVersion>, TValue>
{
    public TValue Convert(TrackedProperty<TValue, TVersion> source, TValue destination, ResolutionContext context)
    {
        var vers = context.Mapper.Map(source, typeof(TrackedProperty<TValue, TVersion>), typeof(PropertyVersion<TValue,TVersion>));
        return (TValue)context.Mapper.Map(vers, typeof(PropertyVersion<TValue, TVersion>), typeof(TValue));
    }
}

第一行映射提取 PropertyVersion.Value。

第二个映射行假定我只需要 TrackedProperty 中的最后一个版本并提取该版本。

第三行将所有内容组合在一起。

我可能可以将所有内容合二为一,并拥有一个 CreateMap 线和一个 Converter,但这很简单。

【讨论】:

  • 我在想这样的事情(你在最后提到的),但没有意识到你可以使用 typeof(object) 作为目标类型。 +1 附注您可以考虑接受自己的答案,以便人们知道问题已解决。
  • @IvanStoev 我还不能接受(直到明天)。解决了这个问题后,我需要处理 TrackedEntity(可以具有普通属性 + 跟踪属性)和 Entity(只有普通属性)类型之间的映射。由于属性的数量和名称将始终相同(按设计),如果我完全放弃 AutoMapper 并使用反射,您认为在性能方面有什么好处吗?
  • 嗯,我不知道。使用 AM 的好处之一是它可以缓存(和编译)几乎所有内容,因此理论上它应该提供比反射更好的性能。
  • 这是一种有趣的解决方法,我尝试使用typeof(string),但没有成功,但我从未想过使用typeof(object),我认为它也会失败。关于性能,我认为您不会因此而遭受重大的性能损失,除非您确实要转换大量对象。很高兴知道您找到了更好的解决方案,我已添加到收藏夹中,有一天它可能会对我有所帮助。我也喜欢你对属性值进行版本化的方式,非常聪明。也投了赞成票。
【解决方案2】:

当您尝试将Foo 映射到TrackedFoo 时,由于它们具有相同的属性名称,起初一切都会好起来的。当 AutoMapper 尝试将您的 Foo 的属性 Name (string) 转换为您的 TrackedFoo 的属性 Name (TrackedProperty&lt;string,DateTime&gt;) 时,就会出现问题。

由于 AutoMapper 不知道如何在 stringTrackedProperty&lt;,&gt; 之间进行自然转换,所以它会失败。

不过,您可以教 AutoMapper 如何在这些类型之间进行转换。为此,您需要一个Custom Type Converter

public class TrackedPropertyConverter<TValue, TVersion> : ITypeConverter<TrackedProperty<TValue, TVersion>, TValue>
{
    public TValue Convert(TrackedProperty<TValue, TVersion> source, TValue destination, ResolutionContext context)
    {
        return source.First().Value;
    }
}

然后你像这样配置它:

  Mapper.Initialize(
            cfg =>
            {
                cfg.CreateMap<TrackedFoo, Foo>();

                cfg.CreateMap<TrackedProperty<string, DateTime>, string>().ConvertUsing<TrackedPropertyConverter<string,DateTime>>();
                cfg.CreateMap<TrackedProperty<int, DateTime>, int>().ConvertUsing<TrackedPropertyConverter<int, DateTime>>();
                cfg.CreateMap<TrackedProperty<double, DateTime>, double>().ConvertUsing<TrackedPropertyConverter<double, DateTime>>();
            }
        );

Mapper.AssertConfigurationIsValid();

这是一个示例用法:

var tracked = new TrackedFoo
{
    Id = "SomeGuid",
    Name = new TrackedProperty<string, DateTime> {
        new PropertyVersion<string, DateTime> { Value = "FooBar", Version = new DateTime(2017, 2, 28) }
    },
    Value = new TrackedProperty<int, DateTime> {
        new PropertyVersion<int, DateTime> { Value = 456, Version = new DateTime(2017, 2, 28) }
    }
};

var foo = Mapper.Map<Foo>(tracked);

Console.WriteLine("Id: {0} | Name: {1} | Value: {2}", foo.Id, foo.Name, foo.Value);
Console.ReadLine();

这是一种教 AutoMapper 如何在类型之间进行转换的通用方法,无论这些类型的对象被转换(例如 Foo 到 TrackedFoo / Bar 到 TrackedBar / Person 到 TrackedPerson)。

您必须明确配置每种类型(字符串、整数、双精度),就像他们页面中的示例一样,但您只需执行一次,您不必为所有可能的类都执行此操作使用跟踪属性。

此外,您的存储库负责将版本值作为参数(如 DateTime),并使用您要求该版本的单个值获取 TrackedProperty,因为您不能为同一个版本。

【讨论】:

  • 感谢您的努力,但您的回答没有一般性。您刚刚为每个具体类型创建了配置。
  • @Zagoda 我编辑了我的答案。我想不出比这更通用的东西,或者我误解了你真正想要的东西。即使TrackedProperty 是一个列表,当从数据库中获取一个“TrackedFoo”时,你应该只用一个项目填充这个列表,包含你想要的版本的值。
  • @Zagoda 另一件事是有效的评论。可以使用CreateMap(typeof(SomethingWithOneGenericType&lt;&gt;), typeof(TrackedProperty&lt;,&gt;)),其中您的TrackedProperty 有两种通用类型(注意&lt;,&gt; 中的逗号)。但是这两种类型都必须使用泛型,因此不能使用typeof(string)typeof(TrackedProperty&lt;,&gt;)。如果这是您想要的,AutoMapper 不支持(idk 为什么)。我提出的解决方案是我能找到的最具活力的解决方案。
【解决方案3】:

自动映射器的整个想法是在彼此之间映射特定对象。 当我们使用泛型时,我们创建了一个辅助函数:

public static class AutoMaps
{
    public static void Initialize()
    {
        Mapper.Initialize(cfg =>
        {
            CreateGenericMapping<CatModel>(cfg);
        });
    }
    public static void CreateCatMapping<TCatType>(IMapperConfigurationExpression cfg)
    {
        cfg.CreateMap<TCatType, Cat>();
        cfg.CreateMap<Cat, TCatType>();
    }
}

【讨论】:

    猜你喜欢
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多