6

Intentaba serializar un modelo de dominio y encontré un problema en el que necesitaba convertir un proxy dinámico en un POCO. El problema que encontré fue que existen referencias circulares a través de propiedades virtuales en el modelo. Aunque intenté usar [ScriptIgnore] para que el serializador no analizara esas propiedades, todavía lo hace. Creo que esto se debe a que los objetos son proxies dinámicos y todavía hay algunos remanentes en las propiedades que hacen que el analizador ingrese (lo que a su vez causa un error de recursión "referencia circular" - Intenté limitar la recursión a 3 pasos, pero obtuve un error de "Pasos recursivos excedidos").¿Cómo puedo convertir un proxy dinámico en un POCO?

¿Cómo puedo convertir un objeto de un proxy dinámico en un POCO para que pueda ser serializado?

Editar: Ejemplo simple

public class One : BaseViewModel 
{ 
    public int OneId { get; set; } 
    public virtual ICollection<Two> Two { get; set; } 
} 

public class Two 
{ 
    public int TwoId { get; set; } 
    public int OneId { get; set; } 
    [ScriptIgnore] 
    public virtual One One { get; set; } 
} 

public abstract class BaseViewModel 
{ 
    public string AsJson() 
    { 
     var serializer = new JavaScriptSerializer(); 
     return serializer.Serialize(this); 
    } 
} 
+0

Los apoderados son una subclase del POCO que representan. En general, debería poder serializarlos perfectamente. ¿Puedes publicar un ejemplo pequeño pero completo de una clase que no puedes serializar? –

+0

@EricJ. - Cuando se construye normalmente, la clase se serializa. Puedo publicar un ejemplo, pero no estoy seguro de cuánto ayudará, porque cuando se ejecute funcionará perfectamente. El problema principal es cuando la clase se crea una instancia con datos de ObjectContext. Esto sucede cuando todavía hay referencias dentro de las propiedades virtuales, aunque deberían estar vacías porque no estaban todas incluidas en la consulta de la base de datos. –

+1

Ver la estructura de la clase que le está dando problemas puede arrojar algo de luz. –

Respuesta

3

Este es un problema conocido

Hemos corregido un problema por el ScriptIgnoreAttribute, que no estaba siendo propagado a las clases derivadas. Dado que los tipos de proxy POCO se crean derivando de la clase POCO proporcionada por el usuario, JavaScriptSerializer no pudo ver los atributos [ScriptIgnore] que tiene en su repro.

La corrección no se incluirá en la próxima versión preliminar de .NET 4.5.

(por lo que presumiblemente tiene que esperar para una siguiente versión preliminar o la versión final)

http://connect.microsoft.com/VisualStudio/feedback/details/723060/ef-4-2-code-first-property-attributes-not-honoured

corregidos en .NET 4.5

De los comentarios sobre esta cuestión, Parece que puede evitar el uso de NonSerializedAttribute en lugar de ScriptIgnoreAttribute si está utilizando la versión actual de JSON.Net

+0

"Si bien con esto los atributos ScriptIgnore o JsonIgnore finalmente se respetan, esto entorpece gravemente el marco de la entidad y te obliga a hacer más plomería de lo que normalmente harías, y al hacerlo va en contra de los principios de EF en mi humilde opinión". De la solución. Aprecio el enlace y he visto sugerencias para deshabilitar esta característica, pero eso significa incluir explícitamente los campos en cada consulta que ejecuto en la base de datos y tendré que encontrar una forma diferente debido a eso. –

+0

Sí, pero creo que la solución fue escrita antes de que Microsoft respondiera al ticket. De hecho, MSFT ha ofrecido mejores soluciones que las indicadas en la solución alternativa. Si cambia 'ScriptIgnore' a' NonSerialized' y tiene una versión reciente de JSON.Net, creo que esto funcionará hoy. –

+0

NonSerialized no funciona para cara triste y virtual. Sin embargo, una gran cara feliz, desencadenando la solución sobre una base situacional parece funcionar. Estoy haciendo esto cuando se envía una bandera: 'context.Configuration.ProxyCreationEnabled = false; context.Configuration.LazyLoadingEnabled = false; 'Estoy marcando esto como la respuesta correcta porque apunta a esta solución. –

3

Travis , Sé que tienes tu respuesta aceptada aquí, pero quería transmitir un poco de pensamiento lateral sobre esto. Hace poco me enfrenté a un problema muy similar y no conseguí nada que me funcionara, probé todos los atributos de [scriptignore], etc.

Lo que finalmente funcionó para mí fue utilizar Automapper y crear un mapa a partir del objeto proxy a un objeto pequeño adelgazado. Esto resolvió todos mis problemas en 2 minutos. Todo esto después de una mentalidad de asedio de 36 horas que prevaleció al tratar de obtener el proxy para jugar a la pelota -shish :-)

Otro enfoque para pensar en el ínterin.

[Editar] - usando AutoMapper (esto es un poco AutoMapper aplicación de prueba referencia)

ref: http://automapper.codeplex.com/

NuGet: Instalar Paquete AutoMapper

Clases:

public sealed class One : BaseViewModel 
{ 
    // init collection in ctor as not using EF in test 
    // no requirement in real app 
    public One() 
    { 
     Two = new Collection<Two>(); 
    } 
    public int OneId { get; set; } 
    public ICollection<Two> Two { get; set; } 
} 

public class Two 
{ 
    public int TwoId { get; set; } 
    public int OneId { get; set; } 
    [ScriptIgnore] 
    public virtual One One { get; set; } 
} 

public abstract class BaseViewModel 
{ 
    public string AsJson() 
    { 
     var serializer = new JavaScriptSerializer(); 
     return serializer.Serialize(this); 
    } 
} 

public class OnePoco : BaseViewModel 
{ 
    public int OneId { get; set; } 
    public virtual ICollection<TwoPoco> Two { get; set; } 
} 

public class TwoPoco 
{ 
    public int TwoId { get; set; } 
    public int OneId { get; set; } 
} 
código de controlador de prueba

:

public ActionResult Index() 
{ 
    // pretend this is your base proxy object 
    One oneProxy = new One { OneId = 1 }; 
    // add a few collection items 
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 1, One = oneProxy}); 
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 2, One = oneProxy}); 

    // create a mapping (this should go in either global.asax 
    // or in an app_start class) 
    AutoMapper.Mapper.CreateMap<One, OnePoco>(); 
    AutoMapper.Mapper.CreateMap<Two, TwoPoco>(); 

    // do the mapping- bingo, check out the asjson now 
    // i.e. oneMapped.AsJson 
    var oneMapped = AutoMapper.Mapper.Map<One, OnePoco>(oneProxy); 

    return View(oneMapped); 
} 

seguirlo y ver cómo le va, desde luego trabajó para mí, la 'tierra' se trasladó :)

+0

¿Puedes expandir Automapper un poco? Tal vez con un simple ejemplo?Además, al cambiar la configuración del contexto como se indica en la respuesta aceptada, las etiquetas '[ScriptIgnore]' se manejarán correctamente. –

+0

seguro, el automapper es "la" bala de plata para muchos problemas, no puedo exagerar este hecho. Automapper funciona permitiéndote crear un mapa entre el objeto intangible complejo y un objeto amigable más db/serialización. Actualizaré mi respuesta con un ejemplo basado en las cosas en las que he trabajado (generalmente viewModels-> poco complejo). Dame 30 minutos o menos. –

+0

Esto me da excepción de tiempo de ejecución "Falta la configuración del mapa de tipos o la asignación no compatible. Tipos de asignación: ->" con EF 5 y AutoMapper 2.2. –

4

Como alternativa a la solución AutoMapper de Jim, he tenido cierto éxito con la 'Mapeo' (copia poco profunda) del proxy POCO a una instancia del mismo POCO. La forma más sencilla de hacer esto es modificar el archivo de plantilla que genera la POCO para incluir un método ToSerializable(), por lo que las clases POCO aspecto:

public partial class cfgCountry 
    { 
     public cfgCountry() 
     { 
      this.People = new HashSet<Person>(); 
     } 

     [Key] 
     public int CountryID { get; set; } 
     public string Name { get; set; } 
     public int Ordinal { get; set; } 

     public virtual ICollection<Person> People { get; set; } 

     public cfgCountry ToSerializable() 
     { 
      return new cfgCountry() 
      { 
      CountryID = this.CountryID, 
      Name = this.Name, 
      Ordinal = this.Ordinal, 
      }; 
     } 
    } 

Aquí es la función que he añadido a la plantilla POCO (tt) presentar a crear la función ToSerializable (Tal sintaxis feo.):

<#+ 
void WriteToSerializableMethod (CodeGenerationTools code, IEnumerable<EdmProperty> primitiveProperties, EntityType entity) 
{ 

#> 
public <#=code.Escape(entity)#> ToSerializable() 
{ 
    return new <#=code.Escape(entity)#>() 
    { 
<#+ 
    foreach(var edmProperty in primitiveProperties) 
    { 
#> 
    <#=edmProperty.Name#> = this.<#=edmProperty.Name#>, 
<#+ 
    } 
#> 
    }; 
} 
<#+ 
} 
#> 

no es perfecto, ya que es necesario recordar para volver foo.ToSerializable() en lugar de foo en sí cada vez que se espera un resultado de ser serializado, pero espero que sea útil para alguien.

+0

Gracias por su respuesta @daveharnett. Sin embargo, lo que terminé haciendo fue escribir mi propia versión personalizada de JavaScriptSerializer. –

Cuestiones relacionadas