Código de EF primero: ¿Debo inicializar las propiedades de navegación?

He visto algunos libros (por ejemplo , el código del framework de entidad de progtwigción primero Julia Lerman ) definen 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 { 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
(); this.License = new License(); } public int Id { get; set; } public string UserName { get; set; } public virtual ICollection
Addresses { get; set; } public virtual License License { get; set; } }

Q1: ¿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; } } 

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

Colecciones: no importa.

Hay una clara diferencia entre colecciones y referencias como propiedades de navegación. Una referencia es una entidad. Una colección contiene entidades. Esto significa que inicializar una colección no tiene sentido en términos de lógica comercial: no define una asociación entre entidades. Establecer una referencia sí.

Por lo tanto, es solo una cuestión de preferencia si, o no, inicializa las listas incrustadas.

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

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

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. Este último puede hacer una diferencia cuando una clase tiene relativamente muchas colecciones. La desventaja es que se necesita relativamente mucha plomería, especialmente. cuando se compara con propiedades automáticas sin inicialización. Además, la llegada del operador de propagación nula 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 dificulta comprobar si una colección fue cargada por Entity Framework o no. Si se inicializa una colección, una statement como …

 var users = context.Users.ToList(); 

… creará objetos de User que tengan colecciones de Addresses vacías, no nulas (cargando lazy a un lado). Comprobar si la colección está cargada requiere un 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 comprobación null servirá. Entonces, cuando la carga selectiva explícita es una parte importante de su práctica de encoding, es decir …

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

… 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 aún, si los inicia en el constructor, EF no los sobrescribirá al materializar su objeto o mediante la carga diferida. 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 corrección de relaciones no ocurrirá. Corrección de relación es el proceso mediante el cual EF conecta a todas las entidades en el contexto por sus propiedades de navegación. Cuando un User y una Licence se cargan por separado, aún se completará User.License y viceversa. A menos, por supuesto, si la License se inicializó 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 User.Addresses !

Núcleo de Entity Framework

La correlación de relaciones en el núcleo de Entity Framework (2.1 en el momento de la escritura) no se ve afectada por las propiedades de navegación de referencia inicializadas en los constructores. Es decir, cuando los usuarios y las direcciones se extraen de la base de datos por separado, las propiedades de navegación se completan.
Sin embargo, la carga diferida no sobrescribe las propiedades de navegación de referencia inicializadas. Por lo tanto, en conclusión, también en EF-core la inicialización de las propiedades de navegación de referencia en los constructores puede causar problemas. No lo hagas No tiene sentido de todos modos,

En todos mis proyectos , sigo la regla: “Las colecciones no deben ser nulas. Están vacías o tienen valores”.

Es posible tener el primer ejemplo cuando la creación de estas entidades es responsabilidad del código de terceros (por ejemplo, ORM) y está trabajando en un proyecto de corta duración.

El segundo ejemplo es mejor, ya que

  • está seguro de que la entidad tiene todas las propiedades establecidas
  • evitas la tonta NullReferenceException
  • haces que los consumidores de tu código sean más felices

Las personas que practican el Diseño Dirigido por Dominio exponen las colecciones como de solo lectura y evitan que los coloquen en ellas. (vea cuál es la mejor práctica para las listas de solo lectura en NHibernate )

Q1: ¿Cuál es mejor? ¿por qué? ¿Pros y contras?

Es mejor exponer las recostackciones no nulas ya que evita verificaciones adicionales en su código (por ejemplo, Addresses ). Es un buen contrato tener en su base de código. Pero está bien para mí exponer la referencia anulable a una sola entidad (por ejemplo, License )

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

Cuando desarrollé el patrón de mapeo de datos por mi cuenta, traté de evitar las referencias bidireccionales y tuve muy pocas referencias de niño a padre.

Cuando uso ORMs 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 la children collection emty children collection .
  2. Luego agrego todo child con referencia a parent entity en la children collection .

Insistiendo en tener un constructor sin parámetros en el tipo de License , haría que user propiedad del user requerida.

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

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

La carga diferida 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 hace referencia a la entidad / entidades. Cuando se utilizan tipos de entidad POCO, la carga diferida se logra creando instancias de tipos proxy derivados y luego anulando las propiedades virtuales para agregar el gancho de carga.

Si eliminara el modificador virtual, desconectaría la carga diferida, y en ese caso su código ya no funcionaría (porque nada inicializaría la lista).

Tenga en cuenta que la carga diferida es una característica admitida por el marco de entidad, si crea la clase fuera del contexto de un DbContext, entonces el código dependiente obviamente sufrirá una NullReferenceException

HTH

Q1: ¿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 de miembro virtual en un constructor “.

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

  1. El marco de la entidad 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 crea un objeto válido es usar un método de fábrica estático :

  1. Haga que el constructor predeterminado esté protegido. Entity Framework está bien para trabajar con constructores protegidos.

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

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

Utilizo la respuesta de esto ¿Por qué mi Entity Framework Code First First es nula y por qué no puedo configurarlo?

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

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

Cuando utiliza el asistente “codificar el primer modelo desde la base de datos” en Visual Studio, todas las colecciones se inicializan así:

 public partial class SomeEntity { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public SomeEntity() { OtherEntities = new HashSet(); } public int Id { get; set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection 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 cinco años? Por lo tanto, inicializaría todas las colecciones como HashSet s.

Y, personalmente, creo que sería muy hábil modificar lo anterior para aprovechar los inicializadores de propiedad de auto de C # 6.0:

  public virtual ICollection OtherEntities { get; set; } = new HashSet();