Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 14 de mayo de 2019
Entity Framework Core Seguimos viendo características novedosas de Entity Framework Core, y ahora llega el turno a otro detalle interesante si lo que buscamos es proteger nuestras entidades de modificaciones descontroladas y así asegurar en todo momento su consistencia: los constructores parametrizados.

En versiones anteriores a EF Core 2.1 y todos sus antecesores "clásicos", un problema que era difícilmente salvable era la necesidad de que en las entidades existiera un constructor público sin parámetros.

Esto tenía sentido, pues debíamos proporcionar al framework una vía para crear las instancias al materializarlas desde el almacén de datos. De hecho, al materializar, el marco de trabajo usaba dicho constructor para crear la instancia, y luego iba poblando las propiedades una a una utilizando sus setters (o estableciendo el valor de sus backing fields, como vimos en un post anterior)

Pues bien, a partir de EF Core 2.1, tanto es posible utilizar constructores privados o con cualquier otro tipo de visibilidad, como constructores parametrizados, siempre que dichos parámetros presenten nombres idénticos a los de las propiedades de la entidad (salvo en que utilizarán camel-casing), cuyos valores serán suministrados en tiempo de ejecución.
De momento, la configuración y selección de estos constructores es totalmente guiada por convenciones, aunque se prevé que podrá ser configurable en versiones futuras de EF.
Así, la siguiente entidad sería materializada usando el constructor definido en la misma, al que llegarán los valores de id y name obtenidos desde el almacén:
public class Friend
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private Friend(int id, string name)
    {
        Id = id;
        Name = name;
    }
}
Es importante tener en cuenta que no es necesario que existan parámetros en el constructor para todas las propiedades de la entidad. Las propiedades que no estén reflejadas en ellos serán cargadas de la forma tradicional, a través del setter; por tanto, en el siguiente código también será utilizado el constructor definido en la clase, aunque la propiedad Name será poblada directamente por EF:
public class Friend
{
    public int Id { get; private set; }      // Poblada en el constructor
    public string Name { get; private set; } // Poblada por EF

    private Friend(int id)
    {
        Id = id;
    }
}
Sin embargo, mucho ojo, porque el constructor no será utilizado si recibe algún parámetro cuyo nombre y tipo no coincida con el de alguna propiedad de la entidad, como en los siguientes ejemplos:
public class Friend
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    // Descartado: 'fullName' no es una propiedad
    private Friend(int id, string fullName) { ... }

    // Descartado: 'name' no es de tipo string
    private Friend(int id, object name) { ... }
}

¿Se pueden inyectar otro tipo de valores al constructor, aparte de los valores de los campos?

Sí se puede, aunque de momento el soporte es muy limitado, permitiéndose únicamente inyectar parámetros de tipos específicamente reconocidos por Entity Framework Core. En la documentación oficial se citan los siguientes tipos:
  • DbContext, que recibirán el contexto de datos actual.
  • ILazyLoader, el servicio que gestiona la carga diferida de propiedades.
  • Action<object, string>, un delegado para gestionar el lazy loading de la entidad.
  • IEntityType, que contiene metadatos sobre el tipo de la entidad actual.
En caso de especificar parámetros de alguno de estos tipos podremos darle cualquier nombre, pues EF sabrá que no nos estamos refiriendo a una propiedad de la entidad:
public class Friend
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private Friend(int id, string name, IEntityType entityType)
    {
        Id = id;
        Name = name as string;
        ... // Hacer algo con entityType aquí
    }
}
Por ejemplo, el siguiente código sería totalmente válido. Observad que aprovechamos el constructor para obtener una referencia al contexto de datos, que luego utilizamos en un método de lo que podría ser su lógica de negocio:
public class Friend
{
    private readonly MyContext _context;
    public int Id { get; private set; }
    public string Name { get; private set; }

    private Friend(int id, string name, MyContext context)
    {
        Id = id;
        Name = name as string;
        _context = context;
    }

    public async Task LogActionAsync(string what)
    {
        var action = new Action() {Action = what, Who = Name};
        _context.Actions.Add(action);
        await _context.SaveChangesAsync();
    }
}
Para futuras versiones de EF Core se está considerando la posibilidad de recibir también servicios de aplicación en el constructor.

¿Y puedo tener más de un constructor en mi entidad?

Pues sí, podrías tener por ejemplo un constructor privado para el uso interno de EF Core, y otros constructores públicos que permitan crear instancias de la entidad desde otros puntos del código.

Lo único a tener en cuenta aquí es la prioridad que EF Core dará a dichos constructores.
Según la implementación actual, que desconozco si podrá variar en un futuro, el constructor utilizado en el momento de la materialización será aquél que reciba mayor número de servicios y el menor número de parámetros asociados a propiedades escalares (ver en GitHub).

Por ejemplo, en la siguiente entidad, el constructor utilizado al materializarla sería el constructor #3, puesto que recibe más servicios que #1 y #2, pero el #4 es descartado al recibir un parámetro cuyo nombre no coincide con el de una propiedad:
public class Friend
{
    public int Id { get; set; }
    public string Name { get; set; }

    // Constructor #1
    public Friend() { }

    // Constructor #2
    public Friend(int id, string name)
    {
        Id = id;
        Name = name;
    }

    // Constructor #3
    public Friend(int id, string name, MyContext ctx)
    {
        Id = id;
        Name = name;
    }

    // Constructor #4
    public Friend(int id, string fullName, MyContext ctx, IEntityType entityType)
    {
        Id = id;
        Name = fullName;
    }
}
¡Y hasta aquí hemos llegado! Espero que esta característica os resulte interesante para sacarle mayor partido a Entity Framework Core en vuestros proyectos :)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!