Как создать копию модели данных

#c# #wpf #mvvm #datamodel

#c# #wpf #mvvm #datamodel

Вопрос:

В моей программе я пытаюсь создать копию модели данных, чтобы я мог установить ее в качестве модели данных другого пользовательского элемента управления.

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

Пример того, что я сделал:

 newUserControl.NewDataModel = oldUserControl.OldDataModel;
 

Как мне создать копию модели данных, чтобы я мог установить ее в качестве контекста модели данных другого пользовательского элемента управления, не заставляя UCS ориентироваться на ту же модель данных?

Ответ №1:

Один из способов сделать это — создать универсальный метод расширения. Это позволяет вам клонировать любой объект независимо от типа, если он сериализуем (имеет атрибут ‘Serializable’).

 public static class ObjectExtensions
{    
    public static T Clone<T>(this T source)
    {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("This type must be serializable.", "source");
            }

            if (Object.ReferenceEquals(source, null))
                return default(T);

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
     }
}
 

Как упоминал @TroelsLarsen, существует риск копирования подписок на события. Чтобы избежать этого, вы можете добавить NonSerializedAttribute в поля, которые вы не хотите сериализовать. Вот документация MSDN с примером.

Затем вы просто используете это так:

 newUserControl.DataModel = oldDataUserControl.DataModel.Clone();
 

Комментарии:

1. Для этого требуется, чтобы объект был сериализуемым. Кроме того, существует риск копирования подписок на события при работе с объектами, напрямую связанными с пользовательским интерфейсом.

2. @TroelsLarsen Да, для этого требуется, чтобы он был сериализуемым, как я упоминаю в ответе. Вы всегда можете добавить NonSerialized атрибут к полям, которые вы не хотите сериализовать.

3. Как мне добавить NonSerialized атрибут в мои модели данных?

4. @Ericafterdark В своем ответе я добавил ссылку на документ MSDN, в котором есть пример. Но в основном вы просто добавляете [NonSerialized()] в поле, точно так же, как вы добавляете [Serializable()] в класс.

Ответ №2:

Что бы я сделал, так это внедрил инфраструктуру обмена сообщениями. Это тот, который я использовал раньше, и он намного упрощает задачу. По сути, это событие, которое вы можете настроить для одного элемента управления, а затем как бы подписаться на это событие в другом элементе управления и передавать данные туда и обратно, используя это событие:

http://mvvmlight.codeplex.com/

Ответ №3:

Вы создаете метод Clone() в классе, который создает новую модель данных, и копирует нужное вам свойство.

 public DataModel Clone() {
    return new DataModel() {
        PropertyA = this.PropertyA,
        PropertyB = this.PropertyB,
        //etc.
    }
}

//OR - Without a clone method:
newUserControl.NewDataModel = new DataModel()
{
    PropertyA = oldUserControl.OldDataModel.PropertyA,
    PropertyB = oldUserControl.OldDataModel.PropertyB,
    //etc
}
 

Для разработки:
Причина, по которой это не работает, заключается в том, что простое присвоение NewDataModel значению OldDataModal означает копирование только ссылки на этот объект. Другими словами, вы не создаете новую модель данных, вы просто указываете оба свойства на один и тот же экземпляр.

Создавая метод Clone(), вы создаете новую модель данных, идентичную старой, но в другом месте в памяти.

Комментарии:

1. Несколько вопросов: с помощью этого метода мне придется вручную копировать каждое свойство? Кроме того, не мог бы я просто сделать что-то вроде newUserControl.newDataModel = new DataModel()...

2. Да, и да. Метод Clone() — это просто стандартизированный способ сделать это на случай, если он понадобится вам более одного раза. Но да, используйте ключевое слово new и скопируйте все свойства (или, по крайней мере, те, которые вам нужны), и все готово. Если вы часто это делаете, вы можете рассмотреть возможность использования отражения, чтобы избежать необходимости делать это вручную, но это быстро усложняется. Ответ обновлен с помощью решения, не являющегося клоном.

3. Хорошо, при использовании метода с new ключевым словом я обнаружил, что некоторые свойства копируются правильно, но некоторые просто копируют ссылку. Например: string свойства копируются правильно, но ObservableCollection свойства копируются только по ссылке.

4. Да, ссылочные типы (любые, кроме простых типов) должны быть созданы заново, если вам нужна копия. Проблема, с которой вы столкнулись, заключается в глубоком копировании и мелком копировании ( en.wikipedia.org/wiki/Object_copy ). При таком подходе вам нужно будет клонировать весь граф объектов. Вы можете попытаться автоматизировать это, используя подход evanb, имея в виду, что у вас могут возникнуть проблемы с циклическими ссылками. Все зависит от того, сколько раз в вашей системе вы столкнетесь с этой проблемой.