Как лучше всего отобразить два объекта одного типа, исключая некоторые поля? - PullRequest
3 голосов
/ 29 марта 2011

Ранее я уже публиковал свой вопрос здесь , но я не получил никакой причины ответа - я думаю - это слишком общий характер. Я постараюсь быть более кратким.

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

Я посмотрел на 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);

Я не знаю, лучше ли это делать, но, похоже, это единственный способ, который я нашел.
Любое предложение будет оценено.

Ответы [ 3 ]

2 голосов
/ 29 марта 2011

Использование прямого отражения (как в примере) будет sloooow ;дать более полный ответ сложно, так как это зависит от того, хотите ли вы мелкий клон или глубокий клон подобъектов.В любом случае вы должны ожидать, что это потребует некоторого метапрограммирования и кэширования - с использованием либо ILGenerator, либо Expression.

Однако для ленивого варианта сериализация может быть полезной.Ряд сериализаторов позволяет вам использовать атрибуты для включения / исключения определенных элементов;сериализуя в поток памяти, перематывая (.Position=0) и десериализовав, вы должны получить глубокую копию только выбранных вами элементов.

Вот пример использования 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);
    }
}
1 голос
/ 29 марта 2011

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

Поскольку AutoMapper является исходным проектом oprn, я предлагаю вам взять исходный код AutoMapper и применить там фильтрацию. На самом деле, это может быть полезно и для других.

0 голосов
/ 29 марта 2011

Я думаю, что ваш подход в порядке. Отражение имеет некоторые последствия для производительности - что стоит учитывать.

Альтернативный, производительный и более простой подход может состоять в том, чтобы 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.
        );
    }

    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...