2010-04-01 11 views
20

Tengo un equipo de clase que contiene una lista genérica:WCF: serializar y deserializar colecciones genéricas

[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()); 
    } 

Cuando uso esta clase en un servicio WCF, consigo tras la salida del registro

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[] 

Después de la deserialización members es una matriz y ya no es una lista genérica, aunque el tipo de campo es IList <> (?!) Cuando intento enviar este objeto de vuelta al servicio WCF, obtengo el resultado de registro

OnSerializing of Team called 
XXX.Person[] 

Después de esto, mi prueba de unidad se bloquea con System.ExecutionEngineException, lo que significa que el servicio WCF no puede serializar la matriz. (Quizás porque esperaba un IList <>)

Por lo tanto, mi pregunta es: ¿Alguien sabe por qué el tipo de mi IList <> es una matriz después de deserializar y por qué no puedo serializar mi objeto de equipo por más tiempo después de eso ?

Gracias

Respuesta

22

Te has encontrado con uno de los DataContractSerializer gotchas.

Fix: Cambiar el declaración de miembro privado a:

[DataMember] 
private List<Person> members = new List<Person>(); 

o cambiar la propiedad a:

[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>(); 
     } 
    } 
} 

Y funcionará. El error de Microsoft Connect es here

Este problema se produce cuando deserializa un objeto con un DataMember IList<T> y luego trata de serializar la misma instancia nuevamente.

Si quieres ver algo fresco:

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>); 
    } 
} 

Se imprimirá int[] is IList<int>: True.

Sospecho que esta es posiblemente la razón por la que lo ve como una matriz después de la deserialización, pero no es muy intuitivo.

Si llama al método Add() en el IList<int> de la matriz, arroja NotSupportedException.

Uno de esos .NET peculiaridades.

+3

Gracias. Eso es realmente difícil de descubrir a lo que un "yo" puede afectar. Mientras tanto, encontré otra solución, especialmente cuando necesitas un IList <> en lugar de List <> (por ejemplo, mi OR-Mapper solo puede manejar IList <> pero no List <>): en lugar de marcar el campo "members" con [ DataMember] marque la propiedad y use este código para el setter: if (value! = Null) members = new List (value); else members = new List (); Con esto la matriz deserializada se pasa al colocador que crea una nueva lista a partir de ella. – Fabiano

+0

La última técnica es lo que siempre hago también. Ese campo de miembro no es algo que el serializador debería estar tocando. – IDisposable

+0

¡En segundo lugar el enfoque de la propiedad! Estoy molesto porque no pensé en eso yo mismo. – codechurn

1

Suena como su referencia servicio WCF está creando una clase de proxy en lugar de utilizar el tipo existente. Las clases proxy solo pueden usar matrices simples y ningún tipo específico de .NET como la Lista genérica.

Para evitar esta conversión de clase de proxy, en la pantalla Agregar referencia de servicio, haga clic en el botón Avanzado y luego asegúrese de que esté marcado "Reutilizar tipos en ensamblados a los que se hace referencia". Esto asegurará que la clase existente (con la Lista genérica) se utilice al serializar y deserializar el objeto.

+0

No utilizo aquí una referencia de servicio. Tengo una referencia a mi proyecto WCF y creo el host y el canal mediante programación. Entonces, reutilizo los tipos yo solo – Fabiano

+0

Ya veo. Bueno, en ese caso, no tengo idea. ;-) – jeremcc

2

Recibí este error al transportar una lectura de IList desde una base de datos a través de LINQ. El WCF se alojó en IIS 7 en un Windows Server 2008 x64.

El grupo de aplicaciones se colgó sin advertencias.

[ServiceBehavior] 
public class Webservice : IWebservice 
{ 

    public IList<object> GetObjects() 
    { 
     return Database.Instance.GetObjects(); 
    } 
} 

No es exactamente el mismo problema pero puede tener la misma causa.

La resolución para era instalar MS revisión KB973110 http://support.microsoft.com/kb/971030/en-us

1

Tomado directamente de mi blog. Espero que sea útil:

Recientemente me encontré con un problema en el que estábamos consumiendo un servicio WCF y utilizando un encuadernador de modelo personalizado en nuestra aplicación ASP.NET MVC. Todo funcionó bien cuando estábamos serializando nuestros ILists. IList se serializa en matrices de forma predeterminada. Terminé convirtiendo nuestras matrices en ILists utilizando Reflection y llamando al siguiente método en el cuaderno de modelo personalizado. Así es como se ve el método:

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; 
} 
Cuestiones relacionadas