Как заменить свойство во время сериализации с помощью DataContractSerializer? - PullRequest
2 голосов
/ 19 августа 2009

У меня есть работающая реализация ChangeTrackingList, которая прекрасно справляется со своей работой, но я бы хотел «отфильтровать» ее содержимое при отправке с клиента обратно на сервер, чтобы он включал только изменения. Получить изменения легко, поскольку мой список предоставляет метод GetChanges именно для этой цели. Как я могу прервать DataContractSerializer и заменить List.GetChanges (), где раньше был List?

Подробнее: Рассмотрим отношения родитель / потомок, в которых у меня есть один родитель с несколькими потомками, у каждого из которых есть ссылка на родителя, например, Customer / Orders. Сериализация всего дочернего списка в клиентском приложении - это хорошо, так как мне нужно показать все заказы. Однако при сохранении я не хочу возвращать все заказы на сервер, а только изменения.

Сложность: Я уже смотрел на реализацию ISerializable и на реализацию своего собственного GetObjectData, что было бы не очень сложно, если бы не тот факт, что мне нужно также сохранять ссылки на объекты. Если я укажу DataContractSerializer на свой график и включу PreserveObjectReferences (добавив поведение или явно через конструктор), я получу очень хороший график без дублирования, но он захочет включить весь мой ChangeTrackingList. Если я реализую ISerializable, я могу выписать свой ChangeTrackingList вручную и включать только изменения, но эти дочерние объекты больше не будут знать своих родительских ссылок.

Пояснение: Это очень упрощенный пример, предназначенный для иллюстрации проблемы. Я не ищу альтернативных решений этой конкретной проблемы. Моя реальная проблема никоим образом не связана с клиентами, заказами или позициями. Альтернативные решения проблемы Заказчик / Заказ - это не те ответы, которые я ищу.

Я просто ищу способ сериализации только "интересных" частей графа объектов. Механизм идентификации и фильтрации «интересного» уже сделан, мне просто нужен способ заместить эти части в граф объекта во время сериализации.

Другой пример: Допустим, у нас есть сущность «Персона», под которой находится коллекция сущностей «Телефон». Телефоны недействительны без родителя, и бизнес-правила гласят, что Лицо недействительно без хотя бы одного телефона. Я не могу просто сохранить человека, а затем телефоны в двух отдельных звонках, потому что каждый звонок будет ошибкой. Я должен сохранить их в виде графика в один вызов. Позже, если я обновлю Person, чтобы изменить его адрес, который хранится в Person, и добавлю новый номер телефона, мне нужно отправить обновленного лица, а также изменения в список телефонов. Я не хочу отправлять оригинальный телефон, потому что он не изменился.

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

Обновление: Похоже, что «подстановка», которую я хочу выполнить, может быть выполнена с помощью класса DataContractSurrogate. Я видел несколько примеров этого, и они относительно просты, но это потому, что их примеры тоже. Обычно они относятся к категории «Поменять местами суррогат сотрудника», где «Сотрудник» - это не несериализуемый класс. В моем случае все становится страннее, потому что класс, который я хочу поменять, является универсальным типом.

Итак, более простая версия проблемы может быть такой. Допустим, у меня есть полностью не сериализуемый класс MyList. (И прежде чем кто-то предложит это, замена класса MyList также не является допустимым решением. Помните, ребята, это всего лишь пример.) Я хочу настроить DataContractSurrogate, чтобы всякий раз, когда MyList появлялся в моем графе объектов, я хотел преобразовать это простой массив для сериализации.

Это звучит как правильное направление для кого-либо? Кто-нибудь когда-нибудь пытался заменить родовой тип? Я сошел с ума, даже подумав об этом?

Ответы [ 4 ]

0 голосов
/ 21 августа 2009

Хорошо, наконец, у меня есть кое-что, что работает, и хотел поделиться ответом. К сожалению, я не могу просто поделиться кодом, так как он был написан на время клиента.

Ключом к решению является класс DataContractSurrogate, о котором вы можете прочитать о здесь и здесь . Обычно, вы бы использовали это, чтобы обеспечить замену несериализуемого класса в графе объектов.

Предположим для иллюстрации, что у нас есть класс MyList, который не сериализуем. Нам нужно создать суррогатный класс, который будет действовать как его «заместитель». Соглашения об именах здесь довольно ужасны, так как тот, что называется MyListSurrogate, на самом деле больше фабричный, а тот, что называется MyListSurrogated, - это то, что я бы назвал настоящим суррогатом. В любом случае «суррогатный» класс предоставляет простой массив или список и помечается как DataContract. Это класс, который пойдет по проводам. Класс MyListSurrogate реализует IDataContractSurrogate и реализует четыре важных метода.

Метод GetDataContractType возвращает замещающий тип с учетом типа сущности. Когда задан тип MyList, он должен вернуть MyListSurrogated. Любой другой тип должен просто возвращать исходный тип. Этот метод требует некоторого беспорядочного отражения, и поэтому я включу код для этого, а не просто объясню его.

public Type GetDataContractType(Type type)
{
    if(type.IsGenericType 
        && (type.GetGenericTypeDefinition() == typeof(MyList<>)))
    {
        var itemType = type.GetGenericArguments()[0];
        var result = typeof(MyListSurrogated<>)
            .GetGenericTypeDefinition().MakeGenericType(itemType);
        return result;
    }
    return type;
}

Аналогичным образом GetObjectToSerialize превращает MyList экземпляр в экземпляр MyListSurrogated. А GetDeserializedObject превращает экземпляр MyListSurrogated обратно в экземпляр MyList. IDataContractSurrogate включает в себя несколько других методов, которые мы не будем использовать, и я только что возвратил null для большинства из них.

Экземпляр класса MyListSurrogate можно затем передать конструктору DataContractSerializer, и он будет вызываться во время сериализации и десериализации для замены типов по мере необходимости на лету. К сожалению, не существует простого способа указать суррогатный класс с помощью конфигурации, поэтому, если вы не создаете экземпляр своего собственного DataContractSerializer, вам придется реализовать DataContractSerializerOperationBehavior, о котором вы можете прочитать о здесь . Применение поведения операции аналогично описанному методу здесь .

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

И последнее. Моей первоначальной целью было создание ChangeTrackingList, который бы передавал все свое содержимое с сервера на клиент, но только изменения от клиента к серверу. Это на самом деле очень просто, когда суррогатная карта была нанесена на карту. Мой ChangeTrackingList имеет логическое свойство с именем "IncludeOriginalsWhenSerializing", которое по умолчанию имеет значение true. Метод GetObjectToSerialize суррогатного класса просматривает этот флаг, чтобы решить, следует ли копировать исходные элементы в резервный. На другом конце строки, GetDeserializedObject Метод воссоздает исходный ChangeTrackingList, а затем устанавливает флаг в false во время десериализации. Итак, список с флагом, установленным в true, идет с одного конца, а выходит на другой конец с флагом, установленным в false. Это просто, это автоматически, это закончено.

0 голосов
/ 19 августа 2009

Я боролся с подобной проблемой. Я думаю, что @casperOne находится на правильном пути. Сообщение от сервера клиенту должно содержать полный граф объектов в одном большом фрагменте. Но изменения в полном графе объектов должны быть сделаны с помощью небольших вспомогательных методов, которые обмениваются ограниченным подмножеством данных.

Например, вы получите полный график Customer / Orders с помощью операции ReadFullCustomer (), а затем перезвоните на сервер с помощью метода AddOrder (), передав только информацию о заказе, необходимую для этой одной задачи. Позвольте доменной логике вашего сервера справиться с поддержанием соответствия родительской записи и родительской записи.

У Рори Примроуза есть статья о дизайне контракта на обслуживание WCF , в которой говорится о коротких и болтливых интерфейсах. В вашем случае это звучит так, как будто вам нужна комбинация обоих: короткая операция чтения и болтливая модификация.


РЕДАКТИРОВАТЬ: Я не уверен, что я полностью понял ваш сценарий, @Mel. Похоже, вы хотите изменить граф объекта во время сериализации. Единственный способ, которым я знаю, это сделать, чтобы классы в вашем графе реализовали IXmlSerializable и использовали ваши собственные методы WriteXml и ReadXml. DataContractSerializer понимает классы, которые реализуют IXmlSerializable, поэтому стоит попробовать.

0 голосов
/ 19 августа 2009

Решение состоит в том, чтобы признать, что у вас есть два разных графика: график исходных объектов и график изменений. Они не того же типа. У оригинала будет объект типа Customer, а у другого будет объект типа ChangedCustomer. У клиента может быть коллекция заказов, но у ChangedCustomer будет коллекция ChangedOrders и т. Д.

Ваша операция GetChanges вернет список ChangedCustomer, а не список клиентов.

0 голосов
/ 19 августа 2009

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

У вас должен быть явный код на клиенте, который будет вызывать GetChanges, а затем отправлять этот урезанный список обратно на сервер.


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

Другими словами, вам нужно создать List<T> (или какой-либо другой соответствующий контейнер) и сериализовать ТО обратно на сервер. Дело в том, что у вас не будет полностью восстановленного объекта, только дети, которые были изменены.

Если вам действительно нужен полностью перегидратированный объект, вы должны отослать обратно весь граф объектов независимо от этого.

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