EF codefirst: ¿Debo inicializar las propiedades de navegación?


Había visto algunos libros (por ejemplo programming entity framework code first Julia Lerman) definir sus clases de dominio (POCO) sin inicialización de las propiedades de navegación como:

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Address { get; set; }
    public virtual License License { get; set; }
}

Algunos otros libros o herramientas (por ejemplo Entity Framework Power Tools) cuando genera POCOs inicializa las propiedades de navegación de la clase, como:

public class User
{
    public User()
    {
        this.Addresses = new IList<Address>();
        this.License = new License();
    }
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual License License { get; set; }
}

P1: ¿Cuál es mejor? ¿Por qué? Pros y Contras?

Editar:

public class License
{
    public License()
    {
        this.User = new User();
    }
    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}

P2: En segundo lugar acercamiento habría desbordamiento de pila si la clase` License `tiene una referencia a la clase` User ' también. Significa que deberíamos tener una referencia unidireccional.(?) ¿Cómo debemos decidir cuál de las propiedades de navegación debe eliminarse?

Author: Iman Mahmoudinasab, 2013-12-24

6 answers

Colecciones: no importa.

Existe una clara diferencia entre colecciones y referencias como propiedades de navegación. Una referencia es una entidad. A collections contiene entidades. Esto significa que inicializar una colección es sin sentido en términos de lógica de negocio: no define una asociación entre entidades. Establecer una referencia lo hace.

Así que es puramente una cuestión de preferencia si o no, o cómo, inicializar listas incrustadas.

En cuanto al "cómo", algunas personas prefieren la inicialización perezosa:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}

Evita las excepciones de referencia nula, por lo que facilita las pruebas unitarias y la manipulación de la colección, pero también evita la inicialización innecesaria. Esto último puede hacer una diferencia cuando una clase tiene relativamente muchas colecciones. La desventaja es que se necesita relativamente mucha plomería, esp. cuando se compara con las propiedades automáticas sin inicialización. También, el advenimiento de la propagación nula operator en C# ha hecho que sea menos urgente inicializar las propiedades de la colección.

...a menos que se aplique una carga explícita

Lo único es que la inicialización de colecciones hace que sea difícil comprobar si una colección fue cargada o no por Entity Framework. Si se inicializa una colección, una instrucción like...

var users = context.Users.ToList();

...creará User objetos con colecciones vacías, no null Addresses (carga lenta aparte). Comprobar si la colección está cargada requiere código como...

var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;

Si la colección no se inicializa una simple verificación null hará. Así que cuando la carga explícita selectiva es una parte importante de su práctica de codificación, es decir ...

if (/*check collection isn't loaded*/)
    context.Entry(user).Collection(c => c.Addresses).Load();

May puede ser más conveniente no inicializar las propiedades de la colección.

Propiedades de referencia: No

Las propiedades de referencia son entidades, por lo que asignarles un objeto vacío es significativo.

Peor, si los inicias en el constructor, EF no los sobrescribirá al materializar su objeto o al cargar perezosamente. Siempre tendrán sus valores iniciales hasta que los reemplace activamente. Peor aún, ¡incluso puede terminar guardando entidades vacías en la base de datos!

Y hay otro efecto: la reparación de la relación no ocurrirá. Relationship fixup es el proceso por el cual EF conecta todas las entidades en el contexto por sus propiedades de navegación. Cuando un User y un Licence se cargan por separado, still User.License estar poblada y viceversa. A menos que por supuesto, si License fue inicializado en el constructor. Esto también es cierto para las asociaciones 1:n. Si Address inicializara un User en su constructor, User.Addresses no se rellenaría!

Entity Framework core

La corrección de relaciones en Entity Framework core (2.1 en el momento de escribir este artículo) no se ve afectada por las propiedades de navegación de referencia inicializadas en los constructores. Es decir, cuando se extraen usuarios y direcciones de la base de datos por separado, se rellenan las propiedades de navegación.
Sin embargo, la carga lenta no sobrescribe las propiedades de navegación de referencia inicializadas. Por lo tanto, en conclusión, también en EF-core inicializar propiedades de navegación de referencia en constructores puede causar problemas. No lo hagas. No tiene sentido de todos modos,

 62
Author: Gert Arnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-19 20:33:22

En todos mis proyectos sigo la regla - "Las colecciones no deben ser null. Están vacíos o tienen valores."

El primer ejemplo es posible cuando la creación de estas entidades es responsabilidad de código de tercera parte (por ejemplo, OR) y está trabajando en un proyecto de corto plazo.

El segundo ejemplo es mejor, ya que

  • está seguro de que la entidad tiene todas las propiedades establecidas
  • se evita tonto NullReferenceException
  • haces consumidores de tu código feliz

Las personas, que practican el Diseño Basado en Dominios, exponen las colecciones como de solo lectura y evitan los configuradores en ellas. (véase Cuál es la mejor práctica para listas de solo lectura en NHibernate)

P1: ¿Cuál es mejor? ¿Por qué? Pros y Contras?

Es mejor exponer colecciones no nulas ya que evita comprobaciones adicionales en su código (por ejemplo, Addresses). Es un buen contrato para tener en su base de código. Pero está bien para mí exponer nullable referencia a la entidad única (por ejemplo, License)

P2: En el segundo enfoque habría un desbordamiento de pila si la clase License también tiene una referencia a la clase User. Significa que deberíamos tener una referencia unidireccional.(?) ¿Cómo debemos decidir cuál de las propiedades de navegación debe eliminarse?

Cuando desarrollé data mapper pattern por mí mismo traté de evitar referencias bidireccionales y tenía referencias de hijo a padre muy raramente.

Cuando yo uso el Orm es es fácil tener referencias bidireccionales.

Cuando es necesario construir una entidad de prueba para mis pruebas unitarias con un conjunto de referencia bidireccional, sigo los siguientes pasos:

  1. Construyo parent entity con emty children collection.
  2. Luego añado evey child con referencia a parent entity en children collection.

En lugar de tener un constructor sin parámetros en el tipo License haría necesaria la propiedad user.

public class License
{
    public License(User user)
    {
        this.User = user;
    }

    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}
 4
Author: Ilya Palkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:41

Es redundante para new la lista, ya que su POCO depende de la Carga lenta.

Lazy loading es el proceso mediante el cual una entidad o colección de entidades se carga automáticamente desde la base de datos la primera vez que se accede a una propiedad que se refiere a la entidad/entidades. Cuando se usan tipos de entidad POCO, la carga lenta se logra creando instancias de tipos de proxy derivados y luego anulando propiedades virtuales para agregar el gancho de carga.

Si desea eliminar el modificador virtual, entonces desactivaría la carga lenta, y en ese caso su código ya no funcionaría (porque nada inicializaría la lista).

Tenga en cuenta que la carga lenta es una característica soportada por entity framework, si crea la clase fuera del contexto de un DbContext, entonces el código dependiendo obviamente sufriría de un NullReferenceException

HTH

 3
Author: bas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-24 10:28:47

P1: ¿Cuál es mejor? ¿Por qué? Pros y Contras?

La segunda variante cuando las propiedades virtuales se establecen dentro de un constructor de entidad tiene un problema definido que se llama "Llamada a miembro virtual en un constructor".

En cuanto a la primera variante sin inicialización de las propiedades de navegación, hay 2 situaciones dependiendo de quién / qué crea un objeto:

  1. Entity framework crea un objeto
  2. El consumidor de código crea un objeto

La primera variante es perfectamente válida cuando Entity Framework crea un objeto, pero puede fallar cuando un consumidor de código crea un objeto.

La solución para garantizar que un consumidor de código siempre cree un objeto válido es usar un método de fábrica estático :

  1. Hacer constructor predeterminado protegido. Entity Framework está bien para trabajar con constructores protegidos.

  2. Agregar un método de fábrica estático que crea un objeto vacío, por ejemplo, un User object, establece todas las propiedades, por ejemplo Addresses y License, después de la creación y devuelve un objeto User completamente construido

De esta manera Entity Framework utiliza un constructor predeterminado protegido para crear un objeto válido a partir de datos obtenidos de alguna fuente de datos y el consumidor de código utiliza un método de fábrica estático para crear un objeto válido.

 3
Author: Lightman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:44

Uso la respuesta de este ¿Por qué mi código Entity Framework First proxy collection es nulo y por qué no puedo configurarlo?

Tuvo problemas con la inicialización del constructor. La única razón por la que hago esto es para hacer el código de prueba más fácil. Asegurarse de que la colección nunca sea nula me ahorra inicializar constantemente en pruebas, etc

 2
Author: GraemeMiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:32

Las otras respuestas responden completamente a la pregunta, pero me gustaría agregar algo ya que esta pregunta sigue siendo relevante y aparece en las búsquedas de Google.

Cuando se utiliza el asistente "code first model from database" en Visual Studio, todas las colecciones se inicializan de la siguiente manera:

public partial class SomeEntity
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public SomeEntity()
    {
        OtherEntities = new HashSet<OtherEntity>();
    }

    public int Id { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}

Tiendo a tomar la salida del asistente como básicamente una recomendación oficial de Microsoft, por lo tanto, por qué estoy agregando a esta pregunta de hace cinco años. Por lo tanto, Inicializaría todas las colecciones como HashSet s.

Y personalmente, creo que sería bastante ingenioso modificar lo anterior para aprovechar los inicializadores de propiedad automática de C# 6.0:

    public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();
 0
Author: Jesse Hufstetler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 15:38:58