WCF: Сериализация и десериализация общих коллекций
У меня есть команда класса, которая содержит общий список:
[DataContract(Name = "TeamDTO", IsReference = true)]
public class Team
{
[DataMember]
private IList<Person> members = new List<Person>();
public Team()
{
Init();
}
private void Init()
{
members = new List<Person>();
}
[System.Runtime.Serialization.OnDeserializing]
protected void OnDeserializing(StreamingContext ctx)
{
Log("OnDeserializing of Team called");
Init();
if (members != null) Log(members.ToString());
}
[System.Runtime.Serialization.OnSerializing]
private void OnSerializing(StreamingContext ctx)
{
Log("OnSerializing of Team called");
if (members != null) Log(members.ToString());
}
[System.Runtime.Serialization.OnDeserialized]
protected void OnDeserialized(StreamingContext ctx)
{
Log("OnDeserialized of Team called");
if (members != null) Log(members.ToString());
}
[System.Runtime.Serialization.OnSerialized]
private void OnSerialized(StreamingContext ctx)
{
Log("OnSerialized of Team called");
Log(members.ToString());
}
Когда я использую этот класс в службе WCF, я получаю следующий выход журнала
OnSerializing of Team called
System.Collections.Generic.List 1[XXX.Person]
OnSerialized of Team called
System.Collections.Generic.List 1[XXX.Person]
OnDeserializing of Team called
System.Collections.Generic.List 1[XXX.Person]
OnDeserialized of Team called
XXX.Person[]
После десериализации members
является массивом и уже не является общим списком, хотя тип поля - IList < > (?!)
Когда я пытаюсь отправить этот объект обратно через службу WCF, я получаю вывод журнала
OnSerializing of Team called
XXX.Person[]
После этого мой unit test выходит из строя с помощью System.ExecutionEngineException, что означает, что служба WCF не может сериализовать массив. (возможно, потому, что он ожидал IList < > )
Итак, мой вопрос: кто-нибудь знает, почему тип моего IList < > является массивом после десериализации и почему я не могу сериализовать свой объект Team больше после этого?
Спасибо
Ответы
Ответ 1
Вы столкнулись с одним из DataContractSerializer
gotchas.
Исправление: Измените свою декларацию частного участника на:
[DataMember]
private List<Person> members = new List<Person>();
ИЛИ измените свойство на:
[DataMember()]
public IList<Person> Feedback {
get { return m_Feedback; }
set {
if ((value != null)) {
m_Feedback = new List<Person>(value);
} else {
m_Feedback = new List<Person>();
}
}
}
И это сработает. Ошибка Microsoft Connect здесь
Эта проблема возникает при десериализации объекта с помощью IList<T>
DataMember, а затем попробуйте снова сериализовать тот же экземпляр.
Если вы хотите увидеть что-то классное:
using System;
using System.Collections.Generic;
class TestArrayAncestry
{
static void Main(string[] args)
{
int[] values = new[] { 1, 2, 3 };
Console.WriteLine("int[] is IList<int>: {0}", values is IList<int>);
}
}
Он напечатает int[] is IList<int>: True
.
Я подозреваю, что это возможно причина, по которой вы видите, что он возвращается в массив после десериализации, но он совершенно неинтуитивен.
Если вы вызываете метод Add() в IList<int>
массива, он бросает NotSupportedException
.
Один из этих .NET quirks.
Ответ 2
Я получил эту ошибку при транспортировке IList, прочитанного из базы данных через LINQ. WCF был размещен в IIS 7 на Windows Server 2008 x64.
Сбой приложения без предупреждений.
[ServiceBehavior]
public class Webservice : IWebservice
{
public IList<object> GetObjects()
{
return Database.Instance.GetObjects();
}
}
Это не совсем та же проблема, но может иметь ту же причину.
Решением для было установить MS-исправление KB973110 http://support.microsoft.com/kb/971030/en-us
Ответ 3
Похоже, ссылка на службу WCF создает прокси-класс, а не используемый существующий тип. Прокси-классы могут использовать только простые массивы, а не любые специфичные для .NET типы, такие как общий список.
Чтобы избежать этого преобразования прокси-класса, на экране "Добавить ссылку на службу" нажмите кнопку "Дополнительно", а затем убедитесь, что "Типы повторного использования в ссылочных сборках" отмечены. Это гарантирует, что существующий класс (с общим списком) используется при сериализации и десериализации объекта.
Ответ 4
Взято прямо из моего блога. я надеюсь, что это будет полезно:
Недавно я столкнулся с проблемой, когда мы потребляли службу WCF и использовали настраиваемое связующее устройство в нашем приложении ASP.net MVC. Все мы отлично работали, когда сериализовали наших Илистов. По умолчанию IList сериализуется в массивы. Я закончил преобразовывать наши массивы обратно в ILists, используя Reflection, и вызывая следующий метод в привязке настраиваемой модели. Вот как выглядит метод:
public object ConvertArraysToIList(object returnObj)
{
if (returnObj != null)
{
var allProps = returnObj.GetType().GetProperties().Where(p => p.PropertyType.IsPublic
&& p.PropertyType.IsGenericType
&& p.PropertyType.Name==typeof(IList<>).Name).ToList();
foreach (var prop in allProps)
{
var value = prop.GetValue(returnObj,null);
//set the current property to a new instance of the IList<>
var arr=(System.Array)value;
Type listType=null;
if(arr!=null)
{
listType= arr.GetType().GetElementType();
}
//create an empty list of the specific type
var listInstance = typeof(List<>)
.MakeGenericType(listType)
.GetConstructor(Type.EmptyTypes)
.Invoke(null);
foreach (var currentValue in arr)
{
listInstance.GetType().GetMethod("Add").Invoke(listInstance, new[] { currentValue });
}
prop.SetValue(returnObj, listInstance, null);
}
}
return returnObj;
}