Подтвердить что ты не робот

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 больше после этого?

Спасибо

4b9b3361

Ответ 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;
}