Cómo solucionar el problema de referencia circular con JSON y Entity

13

He estado experimentando con la creación de un sitio web que aprovecha MVC con JSON para mi capa de presentación y el marco de Entidad para el modelo de datos / base de datos. My Issue entra en juego con la serialización de mis objetos Model en JSON.

Estoy usando el primer método de código para crear mi base de datos. Al hacer el primer método de código, una relación uno a muchos (padre / hijo) requiere que el hijo tenga una referencia de regreso al padre. (Código de ejemplo puede ser un error tipográfico pero obtienes la imagen)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

Cuando se devuelve un objeto "primario" a través de un JsonResult, se genera un error de referencia circular porque "secundario" tiene una propiedad de la clase principal.

He probado el atributo ScriptIgnore pero pierdo la capacidad de mirar los objetos secundarios. Tendré que mostrar información en una vista padre-hijo en algún momento.

He tratado de hacer clases base para padres e hijos que no tienen una referencia circular. Lamentablemente, cuando intento enviar el baseParent y el baseChild, JSON Parser los lee como sus clases derivadas (estoy bastante seguro de que este concepto se me escapa).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

La única solución que se me ocurrió es crear modelos "Ver". Creo versiones simples de los modelos de bases de datos que no incluyen la referencia a la clase principal. Cada uno de estos modelos de vista tiene un método para devolver la Versión de la base de datos y un constructor que toma el modelo de la base de datos como parámetro (viewmodel.name = databasemodel.name). Este método parece forzado aunque funciona.

NOTA: Estoy publicando aquí porque creo que esto es más digno de discusión. Podría aprovechar un patrón de diseño diferente para superar este problema o podría ser tan simple como usar un atributo diferente en mi modelo. En mi búsqueda no he visto un buen método para superar este problema.

Mi objetivo final sería tener una buena aplicación MVC que aproveche en gran medida JSON para comunicarse con el servidor y mostrar datos. Mientras mantengo un modelo consistente en todas las capas (o lo mejor que se me ocurre).

DanScan
fuente

Respuestas:

6

Veo dos temas distintos en su pregunta:

  • ¿Cómo gestionar referencias circulares al serializar a JSON?
  • ¿Qué tan seguro es usar entidades EF como entidades modelo en sus vistas?

Con respecto a las referencias circulares, lamento decir que no hay una solución simple. Primero porque JSON no se puede usar para representar referencias circulares, el siguiente código:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

Resultados en: TypeError: Converting circular structure to JSON

La única opción que tiene es mantener solo el componente compuesto -> parte de la composición y descartar el componente "navegación hacia atrás" -> compuesto, por lo tanto, en su ejemplo:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

Nada le impide recomponer esta propiedad de navegación en su lado del cliente, aquí usando jQuery:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

Pero luego deberá descartarlo nuevamente antes de enviarlo nuevamente al servidor, ya que JSON.stringify no podrá serializar la referencia circular:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

Ahora está el problema de usar entidades EF como entidades de modelo de vista.

Primero, es probable que EF use Proxys dinámicos de su clase para implementar comportamientos como la detección de cambios o la carga diferida, debe deshabilitarlos si desea serializar las entidades EF.

Además, el uso de entidades EF en la interfaz de usuario puede estar en riesgo, ya que todo el archivador predeterminado asignará todos los campos de la solicitud a los campos de entidades, incluidos aquellos que no desea que el usuario establezca.

Por lo tanto, si desea que su aplicación MVC esté diseñada correctamente, le recomendaría usar un modelo de vista dedicado para evitar que las "agallas" de su modelo de negocio interno se expongan al cliente, por lo que le recomendaría un modelo de vista específico.

Julien Ch.
fuente
¿Hay alguna manera elegante con las técnicas orientadas a objetos que pueda evitar tanto la referencia circular como el problema de EF?
DanScan
¿Hay alguna manera elegante con las técnicas orientadas a objetos que pueda evitar tanto la referencia circular como el problema de EF? Como BaseObject es heredado por entityObject y por viewObject. Entonces entityObject tendría la referencia circular pero viewObject no tendría la referencia circular. He superado esto creando viewObject desde entityObject (viewObject.name = entityObject.name) pero esto parece ser una pérdida de tiempo. ¿Cómo puedo solucionar este problema?
DanScan
Ellos muy mucho. Su explicación fue muy clara y fácil de entender.
Nick
2

Una alternativa más simple a intentar serializar los objetos sería deshabilitar la serialización de los objetos primarios / secundarios. En su lugar, puede realizar una llamada por separado para recuperar los objetos principales / secundarios asociados cuando los necesite. Esto puede no ser ideal para su aplicación, pero es una opción.

Para hacer esto, puede configurar un DataContractSerializer y establecer la propiedad DataContractSerializer.PreserveObjectReferences en 'falso' en el constructor de su clase de modelo de datos. Esto especifica que las referencias a objetos no deben conservarse al serializar las respuestas HTTP.

Ejemplos:

Formato Json:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

Formato XML:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

Esto significa que si busca un elemento que tiene objetos secundarios referenciados, los objetos secundarios no se serializarán.

Vea también la clase DataContractsSerializer .

Ciaran Gallagher
fuente
1

Serializador JSON que se ocupa de referencias circulares

Aquí hay un ejemplo de Jackson personalizado JSONSerializerque trata con referencias circulares serializando la primera aparición y almacenando un * referencea la primera aparición en todas las ocurrencias posteriores.

Manejo de referencias circulares al serializar objetos con Jackson

Fragmento parcial relevante del artículo anterior:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

fuente
0

La única solución que se me ocurrió es crear modelos "Ver". Creo versiones simples de los modelos de bases de datos que no incluyen la referencia a la clase principal. Cada uno de estos modelos de vista tiene un método para devolver la Versión de la base de datos y un constructor que toma el modelo de la base de datos como parámetro (viewmodel.name = databasemodel.name). Este método parece forzado aunque funciona.

Enviar la mínima cantidad de datos es la única respuesta correcta. Cuando envía datos desde la base de datos, generalmente no tiene sentido enviar cada columna con todas las asociaciones. Los consumidores no deberían tener que lidiar con estructuras y asociaciones de bases de datos, es decir, con bases de datos. Esto no solo ahorrará ancho de banda, sino que también es mucho más fácil de mantener, leer y consumir. Consulte los datos y luego ejecútelos para lo que realmente necesita para enviar eq. El mínimo indispensable.

Dante
fuente
Se requiere más tiempo de procesamiento cuando habla de big data, ya que ahora tiene que transformar todo dos veces.
David van Dugteren
-2

.Include(x => x.TableName ) no devolver relaciones (de la tabla principal a la tabla dependiente), o solo devolver una fila de datos, ARREGLAR AQUÍ:

/programming/43127957/include-not-working-in-net-core-returns-one-parent

Además, en Startup.cs asegúrese de tener esto en la parte superior:

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;
Joe Hoeller
fuente
hijo wat? erm .. wat?
Amels