AutoMapper 和 is*Specified 属性

发布于 2024-08-24 14:39:27 字数 319 浏览 6 评论 0原文

我有一堆 XSD.exe 生成的数据契约类,对于所有可选元素都有一对 C# 属性,例如

int Amount {get; set;}
bool isAmountSpecified {get; set;}

在映射舞台的另一侧,我有一个可以为 null 的 int,例如

int? Amount {get; set;}

理想情况下,我希望 AutoMapper 能够识别这样的模式,并且知道如何在两个方向映射事物,而无需我为每个单独的属性指定映射。这可能吗?

I have a bunch of XSD.exe-generated data contract classes which for all optional elements have a pair of C# properties like

int Amount {get; set;}
bool isAmountSpecified {get; set;}

On the other side of mapping arena I have a nullable int like

int? Amount {get; set;}

Ideally I'd like for AutoMapper to be able to recognize such patterns and know how to map things in both directions without me having to specify a mapping for each individual property. Is this possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

小猫一只 2024-08-31 14:39:27

好的,昨天我和 AutoMapper 的作者 Jimmy Bogard 进行了简短的讨论,基本上我正在寻找的东西目前是不可能的。对此类约定的支持将在未来某个时候实现(如果我理解正确的话:))。

OK, yesterday I've had a brief discussion with Jimmy Bogard, author of AutoMapper, and basically what I'm looking for is currently not possible. Support for such conventions will be implemented some time in the future (if I understood him correctly :) ).

小嗲 2024-08-31 14:39:27

老实说,我不知道 AutoMapper 是否会这样做(因为我不太使用 AutoMapper),但我知道 protobuf-net 支持这两种模式,因此您可以使用 Serializer.ChangeType<,>(obj) 在它们之间翻转。

然而,当前版本非常依赖于成员上的属性(例如 [XmlElement(Order = n)]) - 我不知道这是否会导致一个问题? 进行中版本支持普通类型(没有属性),但尚未完成(但很快)。

例子:

[XmlType]
public class Foo
{
    [XmlElement(Order=1)]
    public int? Value { get; set; }
}
[XmlType]
public class Bar
{
    [XmlElement(Order = 1)]
    public int Value { get; set; }
    [XmlIgnore]
    public bool ValueSpecified { get; set; }
}
static class Program
{
    static void Main()
    {
        Foo foo = new Foo { Value = 123 };
        Bar bar = Serializer.ChangeType<Foo, Bar>(foo);
        Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);

        foo = new Foo { Value = null };
        bar = Serializer.ChangeType<Foo, Bar>(foo);
        Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);

        bar = new Bar { Value = 123, ValueSpecified = true };
        foo = Serializer.ChangeType<Bar, Foo>(bar);
        Console.WriteLine(foo.Value);

        bar = new Bar { Value = 123, ValueSpecified = false };
        foo = Serializer.ChangeType<Bar, Foo>(bar);
        Console.WriteLine(foo.Value);
    }
}

I honestly have no idea whether AutoMapper will do that (since I don't use AutoMapper much), but I know that protobuf-net supports both those patterns, so you could use Serializer.ChangeType<,>(obj) to flip between them.

The current version is, however, pretty dependent on having attributes (such as [XmlElement(Order = n)]) on the members - I don't know if that causes an issue? The in progress version supports vanilla types (without attributes), but that isn't complete yet (but soon).

Example:

[XmlType]
public class Foo
{
    [XmlElement(Order=1)]
    public int? Value { get; set; }
}
[XmlType]
public class Bar
{
    [XmlElement(Order = 1)]
    public int Value { get; set; }
    [XmlIgnore]
    public bool ValueSpecified { get; set; }
}
static class Program
{
    static void Main()
    {
        Foo foo = new Foo { Value = 123 };
        Bar bar = Serializer.ChangeType<Foo, Bar>(foo);
        Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);

        foo = new Foo { Value = null };
        bar = Serializer.ChangeType<Foo, Bar>(foo);
        Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);

        bar = new Bar { Value = 123, ValueSpecified = true };
        foo = Serializer.ChangeType<Bar, Foo>(bar);
        Console.WriteLine(foo.Value);

        bar = new Bar { Value = 123, ValueSpecified = false };
        foo = Serializer.ChangeType<Bar, Foo>(bar);
        Console.WriteLine(foo.Value);
    }
}
爺獨霸怡葒院 2024-08-31 14:39:27

以下示例展示了如何将具有*指定属性(源)的模型映射到具有可为空属性(目标)的模型。
我已经为所有成员配置了 Condition 方法,它将检查源属性是否具有相应的 *Specified 属性,如果有,则它将检查其值。如果*指定属性返回false,则不满足条件并且将跳过映射。

您可以在其他方向执行相同的操作,但您必须设置它,而不是读取 *Specified 属性值。

public void Configure(IMapperConfigurationExpression cfg)
{
    cfg.CreateMap<Source, Destination>()
        .ForAllOtherMembers(opt => opt.PreCondition((srcObj, context) => IsSpecified(srcObj, context, opt)));
}

public static bool IsSpecified<TSource, TDestination, TMember>(TSource source, ResolutionContext context, IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var dstMemberPropertyInfo = opt.DestinationMember as PropertyInfo;

    // if destination member not nullable, then assume that source member won't have *Specified property
    if (!IsNullableType(dstMemberPropertyInfo.PropertyType))
        return true;

    var config = context.Mapper.ConfigurationProvider;
    var map = config.FindTypeMapFor<TSource, TDestination>();
    var propertyMap = map.PropertyMaps.FirstOrDefault(x => x.DestinationMember.Name == opt.DestinationMember.Name);
    var sourceMembers = new Queue<MemberInfo>(propertyMap.SourceMembers);
    var srcParentType = typeof(TSource);
    var srcParentObj = source as object;

    // get the source parent instance
    while (sourceMembers.Count > 1) // the last item in queue is the SourceMember itself
    {
        var srcParentPropertyInfo = sourceMembers.Dequeue() as PropertyInfo;
        srcParentType = srcParentPropertyInfo.PropertyType;
        srcParentObj = srcParentPropertyInfo.GetValue(srcParentObj);

        // the source parent is not defined, so we can skip this mapping
        if (srcParentObj == null)
            return false;
    }

    var srcMemberSpecifiedPropName = propertyMap.SourceMember.Name + "Specified";
    var srcMemberSpecifiedProp = srcParentType.GetProperty(srcMemberSpecifiedPropName);

    // if there is no *Specified property, then assume value is specified
    return srcMemberSpecifiedProp == null || (bool)srcMemberSpecifiedProp.GetValue(srcParentObj);
}

private bool IsNullableType(Type type) => IsGenericType(type, typeof(Nullable<>));

private bool IsGenericType(Type type, Type genericType) => IsGenericType(type) && type.GetGenericTypeDefinition() == genericType;

private bool IsGenericType(Type type) => type.GetTypeInfo().IsGenericType;

Here is example of how to map model with *Specified properties (source) to model with nullable properties (destination).
I've configured Condition method for all members, which will check if source property have corresponding *Specified property and if it does, then it will check it's value. If *Specified property returns false, then condition is not met and mapping will get skipped.

You can do the same in other direction, but instead of reading *Specified property value you will have to set it.

public void Configure(IMapperConfigurationExpression cfg)
{
    cfg.CreateMap<Source, Destination>()
        .ForAllOtherMembers(opt => opt.PreCondition((srcObj, context) => IsSpecified(srcObj, context, opt)));
}

public static bool IsSpecified<TSource, TDestination, TMember>(TSource source, ResolutionContext context, IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var dstMemberPropertyInfo = opt.DestinationMember as PropertyInfo;

    // if destination member not nullable, then assume that source member won't have *Specified property
    if (!IsNullableType(dstMemberPropertyInfo.PropertyType))
        return true;

    var config = context.Mapper.ConfigurationProvider;
    var map = config.FindTypeMapFor<TSource, TDestination>();
    var propertyMap = map.PropertyMaps.FirstOrDefault(x => x.DestinationMember.Name == opt.DestinationMember.Name);
    var sourceMembers = new Queue<MemberInfo>(propertyMap.SourceMembers);
    var srcParentType = typeof(TSource);
    var srcParentObj = source as object;

    // get the source parent instance
    while (sourceMembers.Count > 1) // the last item in queue is the SourceMember itself
    {
        var srcParentPropertyInfo = sourceMembers.Dequeue() as PropertyInfo;
        srcParentType = srcParentPropertyInfo.PropertyType;
        srcParentObj = srcParentPropertyInfo.GetValue(srcParentObj);

        // the source parent is not defined, so we can skip this mapping
        if (srcParentObj == null)
            return false;
    }

    var srcMemberSpecifiedPropName = propertyMap.SourceMember.Name + "Specified";
    var srcMemberSpecifiedProp = srcParentType.GetProperty(srcMemberSpecifiedPropName);

    // if there is no *Specified property, then assume value is specified
    return srcMemberSpecifiedProp == null || (bool)srcMemberSpecifiedProp.GetValue(srcParentObj);
}

private bool IsNullableType(Type type) => IsGenericType(type, typeof(Nullable<>));

private bool IsGenericType(Type type, Type genericType) => IsGenericType(type) && type.GetGenericTypeDefinition() == genericType;

private bool IsGenericType(Type type) => type.GetTypeInfo().IsGenericType;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文