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

18 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, 28 de mayo de 2019
Entity Framework Core Otra característica bastante interesante de Entity Framework Core es que podemos introducir lógica personalizada a la hora de cargar el valor de las propiedades a partir de los datos obtenidos desde el almacén, y viceversa, usando los conversores de valores, o value converters.

Esta capacidad abre posibilidades bastante interesantes,que no eran tan inmediatas (o en algunos casos, ni siquiera posibles de forma directa) en versiones "clásicas" de Entity Framework, o EF Core anterior a 2.1. Gracias a ella podremos, por ejemplo, tener en nuestra entidad una propiedad de tipo enum mapeada a una cadena de caracteres en el almacén de datos, o introducir cualquier lógica de transformación, como podría ser la desencriptación y encriptación de valores, a la hora de leer y persistir información.

¡Bienvenidos, value converters!

Los value converters son clases donde se especifica la lógica de conversión de valores entre la propiedad de la entidad y el valor que procede del almacén de datos y viceversa, en forma de árboles de expresión.

Por ejemplo, supongamos que tenemos una entidad con una propiedad booleana que, por el motivo que sea, está almacenada en la base de datos en forma de cadena, con valores "SI" y "NO". Este sería un caso en el que los value converters nos vendrán de perlas :)

Para configurar esta transformación, podemos añadir a OnModelCreating() un código como el siguiente, donde usamos el método HasConversion() sobre la propiedad de la entidad para suministrarle dos árboles de expresión, el primero para transformar desde el valor de la entidad al esperado por el almacén, y luego para hacerlo en sentido contrario:
public class MyContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Friend>()
            .Property(f => f.IsGoodFriend)
            .HasConversion(
                value => value ? "SI" : "NO", // (1) Entity-->Provider conversion
                value => value == "SI"        // (2) Provider-->Entity conversion
            );
        ...
    }
}
De esta forma, cuando el valor de la propiedad IsGoodFriend vaya a ser enviado al almacén de datos, se transformará usando la primera expresión, mientras que para establecer dicha propiedad partiendo del valor almacenado, se utilizará la segunda.

Asimismo, estas transformaciones serán tenidas en cuenta al realizar consultas. Fijaos en el siguiente ejemplo, donde se puede observar cómo desde LINQ usamos la propiedad de la entidad para establecer un filtro que muy hábilmente es transformado por EF para adaptarlo a los datos que realmente están siendo almacenados:
Consulta LINQ:
==============
    var goodFriends = ctx.Friends.Where(f=>f.IsGoodFriend).ToList();

SQL Generado:
=============
    SELECT [f].[Id], [f].[IsGoodFriend], [f].[Name]
        FROM [Friends] AS [f]
        WHERE [f].[IsGoodFriend] = N'SI'
Ojo, hay que tener cuidado con esto: el uso de expresiones no reconocidas por el proveedor a la hora de especificar las conversiones podría dar lugar a la evaluación en cliente, y ya sabemos que esto puede causar problemas.

Value converters reutilizables

Otra forma más clara y reutilizable de especificar la conversión de valores es utilizando la clase ValueConverter<TModel, TProvider>, donde TModel es el tipo de dato usado en la entidad .NET, mientras que TProvider es el tipo usado por el proveedor del almacén de datos. Así, nuestro ejemplo anterior podría reescribirse de esta forma:
var boolToSiNo = new ValueConverter<bool, string>(
    value => value ? "SI" : "NO", // Entity-->Provider conversion
    value => value == "SI"        // Provider-->Entity conversion
);

builder.Entity<Friend>()
    .Property(f => f.IsGoodFriend)
    .HasConversion(boolToSiNo);

builder.Entity<Friend>()
    .Property(f => f.IsFamily)
    .HasConversion(boolToSiNo); // Reuse the same converter
Un aspecto interesante es que los valores nulos nunca serán convertidos a través de este mecanismo, por lo que no tendremos que tenerlos en cuenta en su implementación. Esto es así porque se utilizan los mismos conversores tanto para propiedades tipo valor como sus correspondientes anulables (por ejemplo, una propiedad int o int?).

Dado que existen necesidades de conversión que son muy frecuentes, el propio EF Core trae de serie un buen conjunto de conversores predefinidos. Por ejemplo, uno de ellos, BoolToStringConverter<TProvider>, podría habernos sido útil en el ejemplo anterior:
var converter = new BoolToStringConverter("NO", "SI");
builder.Entity<Friend>()
    .Property(f => f.IsGoodFriend)
    .HasConversion(converter);

En el espacio de nombres Microsoft.EntityFrameworkCore.Storage.ValueConversion podemos ver muchos conversores más:
  • BoolToStringConverter
  • BoolToTwoValuesConverter
  • BoolToZeroOneConverter
  • BytesToStringConverter
  • CastingConverter
  • CharToStringConverter
  • DateTimeOffsetToBinaryConverter
  • DateTimeOffsetToBytesConverter
  • DateTimeOffsetToStringConverter
  • DateTimeToBinaryConverter
  • DateTimeToStringConverter
  • DateTimeToTicksConverter
  • EnumToNumberConverter
  • EnumToStringConverter
  • GuidToBytesConverter
  • GuidToStringConverter
  • NumberToBytesConverter
  • NumberToStringConverter
  • StringToBoolConverter
  • StringToBytesConverter
  • StringToCharConverter
  • StringToDateTimeConverter
  • StringToDateTimeOffsetConverter
  • StringToEnumConverter
  • StringToGuidConverter
  • StringToNumberConverter
  • StringToTimeSpanConverter
  • TimeSpanToStringConverter
  • TimeSpanToTicksConverter
También el framework es capaz de inferir el uso de determinados conversores a partir de los tipos de datos utilizados. Por ejemplo, cuando no existe coincidencia entre el tipo de datos usado en la entidad y el utilizado por el proveedor, como en los siguiente casos, se usarán conversiones por defecto.

Aquí, implícitamente estaremos utilizando EnumToStringConverter para que FriendType pueda ser guardado como cadena en el almacén:
public class Friend
{
    ...
    [Column(TypeName = "nvarchar(10)")] 
    public FriendType Type { get; set; }
}
En este ejemplo, en cambio, indicamos en la definición del modelo que la propiedad Type la queremos almacenar en un int, lo que provocará que sea utilizado el conversor EnumToNumberConverter:
modelBuilder
    .Entity<Friend>()
    .Property(f => f.Type)
    .HasConversion<int>();
Si tenéis curiosidad, la lógica de selección de conversores por defecto podéis verla en GitHub.

¡Esto es todo de momento! Espero que lo visto aquí haya sido útil para daros una idea de lo que podemos hacer con esta característica de EF Core. Y si queréis leer más, os recomiendo echar un vistazo a la documentación oficial.

Publicado en Variable not found.

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