映射相同类型的两个对象(不包括某些字段)的最佳方法是什么?

发布于 2024-10-27 06:27:16 字数 1937 浏览 1 评论 0原文

我之前在此处发布了我的问题,但没有得到任何回复因为 - 我猜 - 它太笼统了。我会尝试更简洁。

我有两个相同类型的对象,我想映射一些属性并排除其他属性。我想做的是将对象保存在缓存中并稍后获取它,仅使用具有特定属性的属性(字段)。

我看过Automapper,但我没有找到任何适合我的东西,所以我考虑实现自己的系统。
我创建了一个属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FilterFieldAttribute: Attribute
{
} 

并用它在我需要包含的字段上装饰了一个类:

public class OrdersViewModel : BaseViewModel
{
...

    [FilterField]
    [DisplayName("Order Number:")]
    public string OrderNumber { get; set; }

    [FilterField]
    [DisplayName("From date:")]
    public DateTime FromDate { get; set; }

    [FilterField]
    [DisplayName("To date:")]
    public DateTime ToDate { get; set; }

    [DisplayName("Status:")]
    public int Status { get; set; }

    ...
} 

现在,我已经实现了一个负责映射的函数:

    private T Map<T>(T Source, T Destination) where T : BaseViewModel
    {
        if (Source == null)
        {
            return (Source);
        }

        FilterFieldAttribute filterAttribute;

        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
        {
            foreach (FilterFieldAttribute attr in propInfo.GetCustomAttributes(typeof(FilterFieldAttribute), false))
            {
                filterAttribute = attr as FilterFieldAttribute;
                if (filterAttribute != null)
                {
                    var value = propInfo.GetValue(Source, null);
                    propInfo.SetValue(Destination, value, null);
                }
            }
        }
        return (Destination);
    }

现在,当我需要从缓存中获取视图模型并填充时只有标有属性的属性我的代码看起来像这样:

viewModel = Map<T>(myCache.Get(Key) as T, viewModel);

我不知道这是否是最好的方法,但这似乎是我找到的唯一方法。
任何建议将不胜感激。

I've previously post my question here but I didn't get any reply cause - I guess - it's too generic. I'll try to be more concise.

I've got two object of the same type and I want to map some of the properties and exclude others. What I am trying to do is save an object in a cache and fetch it later, using only the properties (fields) with a specific attribute.

I've had a look at Automapper but I haven't found anything which seems to be suitable for me so I've thought to implement my own system.
I've created an attribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FilterFieldAttribute: Attribute
{
} 

and decorated a class with it on the fields I need to include:

public class OrdersViewModel : BaseViewModel
{
...

    [FilterField]
    [DisplayName("Order Number:")]
    public string OrderNumber { get; set; }

    [FilterField]
    [DisplayName("From date:")]
    public DateTime FromDate { get; set; }

    [FilterField]
    [DisplayName("To date:")]
    public DateTime ToDate { get; set; }

    [DisplayName("Status:")]
    public int Status { get; set; }

    ...
} 

Now, I've implemented a function which is responsible for the mapping:

    private T Map<T>(T Source, T Destination) where T : BaseViewModel
    {
        if (Source == null)
        {
            return (Source);
        }

        FilterFieldAttribute filterAttribute;

        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
        {
            foreach (FilterFieldAttribute attr in propInfo.GetCustomAttributes(typeof(FilterFieldAttribute), false))
            {
                filterAttribute = attr as FilterFieldAttribute;
                if (filterAttribute != null)
                {
                    var value = propInfo.GetValue(Source, null);
                    propInfo.SetValue(Destination, value, null);
                }
            }
        }
        return (Destination);
    }

Now, when I need to fetch my viewmodel from the cache and fill only the properties marked with the attribute my code looks like this:

viewModel = Map<T>(myCache.Get(Key) as T, viewModel);

I don't know if this is the best to do it but it seems the only way I have found.
Any suggestion would be appreciated.

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

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

发布评论

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

评论(3

无法言说的痛 2024-11-03 06:27:16

使用直接反射(如示例中所示)将会缓慢;给出更完整的答案很棘手,因为这取决于您想要子对象的浅克隆还是深层克隆。无论哪种方式,您都应该期望这涉及一些元编程和缓存 - 使用 ILGenerator 或 Expression。

但是,对于惰性选项,序列化可能有用。许多序列化程序允许您使用属性来包含/排除特定项目;通过序列化到内存流、回滚 (.Position=0) 和反序列化,您应该只获得所选成员的深层副本。

以下是使用表达式的示例:

using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FilterFieldAttribute: Attribute
{
    public static T Clone<T>(T obj) where T : class, new()
    {
        return Cache<T>.clone(obj);
    }

    private static class Cache<T> where T : class, new()
    {
        public static readonly Func<T,T> clone;
        static Cache()
        {
            var param = Expression.Parameter(typeof(T), "source");
            var members = from prop in typeof(T).GetProperties()
                          where Attribute.IsDefined(prop, typeof(FilterFieldAttribute))
                          select Expression.Bind(prop, Expression.Property(param, prop));

            var newObj = Expression.MemberInit(Expression.New(typeof(T)), members);
            clone = Expression.Lambda<Func<T,T>>(newObj, param).Compile();
        }
    }
} 

public class OrdersViewModel 
{
    [FilterField]
    [DisplayName("Order Number:")]
    public string OrderNumber { get; set; }

    [FilterField]
    [DisplayName("From date:")]
    public DateTime FromDate { get; set; }

    [FilterField]
    [DisplayName("To date:")]
    public DateTime ToDate { get; set; }

    [DisplayName("Status:")]
    public int Status { get; set; }

    static void Main()
    {
        var foo = new OrdersViewModel { OrderNumber = "abc", FromDate = DateTime.Now,
            ToDate = DateTime.Now, Status = 1};
        var bar = FilterFieldAttribute.Clone(foo);
    }
}

Using direct reflection (as in the example) will be sloooow; giving a more complete answer is tricky, as it depends on whether you want a shallow clone or deep clone of sub-objects. Either way, you should expect this to involve some meta-programming and caching - using either ILGenerator or Expression.

However, for a lazy option, serialization might be useful. A number of serializers allow you to use attributes to include/exclude particular items; by serializing to a memory-stream, rewinding (.Position=0) and deserializing you should get a deep copy of just your chosen members.

Here's an example using Expression:

using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FilterFieldAttribute: Attribute
{
    public static T Clone<T>(T obj) where T : class, new()
    {
        return Cache<T>.clone(obj);
    }

    private static class Cache<T> where T : class, new()
    {
        public static readonly Func<T,T> clone;
        static Cache()
        {
            var param = Expression.Parameter(typeof(T), "source");
            var members = from prop in typeof(T).GetProperties()
                          where Attribute.IsDefined(prop, typeof(FilterFieldAttribute))
                          select Expression.Bind(prop, Expression.Property(param, prop));

            var newObj = Expression.MemberInit(Expression.New(typeof(T)), members);
            clone = Expression.Lambda<Func<T,T>>(newObj, param).Compile();
        }
    }
} 

public class OrdersViewModel 
{
    [FilterField]
    [DisplayName("Order Number:")]
    public string OrderNumber { get; set; }

    [FilterField]
    [DisplayName("From date:")]
    public DateTime FromDate { get; set; }

    [FilterField]
    [DisplayName("To date:")]
    public DateTime ToDate { get; set; }

    [DisplayName("Status:")]
    public int Status { get; set; }

    static void Main()
    {
        var foo = new OrdersViewModel { OrderNumber = "abc", FromDate = DateTime.Now,
            ToDate = DateTime.Now, Status = 1};
        var bar = FilterFieldAttribute.Clone(foo);
    }
}
风情万种。 2024-11-03 06:27:16

听起来像是一个好的开始,但您正在失去 AutoMapper 的很多好处。 AutoMapper 还可以处理此处没有的嵌套属性(当您的类包含嵌套映射类时)。

由于 AutoMapper 是一个 oprn 源项目,我建议您获取 AutoMapper 源并在那里实现过滤。事实上,这对其他人也很有用。

Sounds like a good start but you are loosing so much of the benefits of AutoMapper. AutoMapper can also take care of the nested properties (when your class contains nested mapped classes) which you do not have here.

Since AutoMapper is an oprn source project, I suggest you take AutoMapper source and implement filtering there. This could be in fact useful for others too.

冷…雨湿花 2024-11-03 06:27:16

我觉得你的做法没问题。反射有一些性能影响——这是值得考虑的。

另一种高效且更简单的方法可能是让 BaseViewModel 定义一个抽象方法:

public abstract BaseViewModel ToCacheVersion();

该方法可用于将子类转换为正确的类型。每个子类都会处理自己的映射:

public class ViewModelX
{
    public ViewModelX(string name, string description)
    {
        Name = name;
        Description = description;
    }

    ...

    public override BaseViewModel ToCacheVersion()
    {
        return new ViewModelX(
            Name, // Include the name.
            null  // Ignore the description.
        );
    }

    ...
}

I think your approach is ok. Reflection has some performance implications - which is worth considering.

An alternative, performant, and simpler approach might be to have the BaseViewModel define an abstract method:

public abstract BaseViewModel ToCacheVersion();

Which can be used to convert the subclass to the correct type. Each subclass would take care of its own mapping:

public class ViewModelX
{
    public ViewModelX(string name, string description)
    {
        Name = name;
        Description = description;
    }

    ...

    public override BaseViewModel ToCacheVersion()
    {
        return new ViewModelX(
            Name, // Include the name.
            null  // Ignore the description.
        );
    }

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