martes, 7 de mayo de 2019
Para los desarrolladores que hemos utilizado Entity Framework 6.x y anteriores durante años, lo normal es que al dar el salto a Entity Framework Core continuemos utilizando más o menos las mismas cosas que ya conocíamos, y muchas veces no nos detenemos a ver qué novedades interesantes acompañan al nuevo marco de trabajo.
Ya llevamos varios posts dedicados a comentar algunas características novedosas o que cambian bastante respecto a las versiones anteriores (como la evaluación en cliente o las shadow properties, y vamos a continuar en esta línea presentando ahora otra interesante novedad: el soporte para campos de respaldo o backing fields.
Por ejemplo, en la siguiente entidad tenemos una propiedad llamada
Sin embargo, este código tiene un problema: cada vez que una entidad
Otro caso frecuente lo podemos encontrar en el siguiente ejemplo. Echad un vistazo, ¿qué impide añadir una línea a la factura directamente, obviando todos los controles y lógica que el método
Pues aquí es donde entran en escena los backing fields, cuya idea de es permitir que el framework sea capaz de leer y escribir directamente en campos privados de las entidades, sin que tengan que existir propiedades públicas o setters que abran la puerta a accesos arbitrarios a sus datos.
Entity Framework detectará los campos privados de las entidades, bien mediante convenciones o bien porque así lo hayamos indicado expresamente en la configuración, y los usará para almacenar los datos directamente durante el proceso de materialización.
¿Y cómo podemos aprovechar esta característica también para proteger las colecciones, como en el escenario que describíamos algo más arriba? Pues en este caso, lo que nos interesa es que el campo privado sea de tipo
Publicado en Variable not found.
Ya llevamos varios posts dedicados a comentar algunas características novedosas o que cambian bastante respecto a las versiones anteriores (como la evaluación en cliente o las shadow properties, y vamos a continuar en esta línea presentando ahora otra interesante novedad: el soporte para campos de respaldo o backing fields.
¿Backing fields? ¿No me suena eso de algo?
Pues claro, el concepto de backing fields no es nuevo, ni propio de Entity Framework Core: lo usamos en .NET desde el principio de los tiempos. Como se define en la guía oficial de programación con C#:Un campo de respaldo es un campo privado que almacena los datos expuestos por una propiedad pública.Vaya, básicamente es lo que utilizábamos a diario antes de que se introdujeran en el lenguaje las propiedades automáticas:
public class Friend
{
private string _name; // <-- backing field
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Vale, ¿y por qué necesitamos backing fields en Entity Framework?
Si habéis trabajado ya con Entity Framework "clásico", probablemente que alguna vez habréis necesitado proteger las entidades de accesos no controlados a algunas de sus propiedades. Aunque algunos de esos escenarios eran salvables jugando con la visibilidad de las propiedades, hay otros donde esto no era técnicamente posible, suponiendo un peligro desde el punto de vista de la encapsulación de datos, el mantenimiento de invariantes o la consistencia de la información almacenada.Por ejemplo, en la siguiente entidad tenemos una propiedad llamada
LastUpdate
para mantener la fecha en que los datos de la persona son actualizados. Para asegurar la consistencia, en el setter de Name
actualizamos la fecha de actualización:public class Friend
{
private string _fullName;
public string Name
{
get => _fullName;
set
{
_fullName = value;
LastUpdate = DateTime.Now;
}
}
public int Id { get; set; }
public DateTime LastUpdate { get; private set; }
}
De esta forma, cuando desde código asignemos la propiedad Name
, se actualizará su fecha de modificación. Además, para evitar que se modifique desde fuera, hacemos el setter privado: aparentemente, todo correcto.Sin embargo, este código tiene un problema: cada vez que una entidad
Friend
sea materializada desde la base de datos se establecerá la propiedad Name
, y esto provocará que a su vez se actualice el contenido de LastUpdate
con la fecha actual. Por tanto, estaremos machacándola continuamente y perderemos el valor original de la propiedad en cuanto salvemos cambios.Otro caso frecuente lo podemos encontrar en el siguiente ejemplo. Echad un vistazo, ¿qué impide añadir una línea a la factura directamente, obviando todos los controles y lógica que el método
AddLine()
pudiera contener?public class Invoice
{
...
public HashSet<InvoiceLine> Lines { get; private set; }
public void AddLine(InvoiceLine line)
{
if (!IsEditable())
{
throw new InvalidOperationException("You can only add lines to editable invoices");
}
if (line.Product.Stock <= 0)
{
throw new InvalidOperationException("You can't add products out of stock");
}
Amount += (line.Quantity * line.UnitPrice);
Lines.Add(line);
}
}
En este escenario, el ideal sería que la propiedad Lines
fuera de algún tipo que no permitiera alterar el contenido de la colección, como un IEnumerable<InvoiceLine>
.Pues aquí es donde entran en escena los backing fields, cuya idea de es permitir que el framework sea capaz de leer y escribir directamente en campos privados de las entidades, sin que tengan que existir propiedades públicas o setters que abran la puerta a accesos arbitrarios a sus datos.
Entity Framework detectará los campos privados de las entidades, bien mediante convenciones o bien porque así lo hayamos indicado expresamente en la configuración, y los usará para almacenar los datos directamente durante el proceso de materialización.
Uso de backing fields
La configuración de este tipo de campos la debemos hacer desde el métodoOnModelCreating()
del contexto de datos, donde asociaremos los backing fields con las propiedades de la entidad, como en el siguiente ejemplo:// En el contexto de datos, indicamos el backing field para "Name":
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Friend>()
.Property(f => f.Name)
.HasField("_fullName");
...
}
// En la entidad:
public class Friend
{
private string _fullName;
public int Id { get; set; }
public string Name
{
get => _fullName;
set
{
_fullName = value;
LastUpdate = DateTime.Now;
}
}
public DateTime LastUpdate { get; private set; }
}
Hecho esto, cuando EF vaya a materializar un objeto Friend
desde el almacén, escribirá directamente sobre el campo privado _fullName
en lugar de hacerlo sobre la propiedad Name
, por lo que el valor de LastUpdated
no será actualizado, aglo que si ocurrirá cuando establezcamos manualmente el nombre.¿Y cómo podemos aprovechar esta característica también para proteger las colecciones, como en el escenario que describíamos algo más arriba? Pues en este caso, lo que nos interesa es que el campo privado sea de tipo
HashSet
o similar y permita todas las operaciones de adición y eliminación de elementos, pero que hacia fuera nuestra entidad exponga un IEnumerable
de sólo lectura, como en el siguiente código:public class Invoice
{
private HashSet<InvoiceLine> _invoiceLines;
public IEnumerable<InvoiceLine> Lines => _invoiceLines;
public void AddLine(InvoiceLine line)
{
// Hacer algo y añadir la línea a _lines
}
...
}
En esta ocasión, al tratarse de una propiedad de navegación, la forma de configurarlo será ligeramente diferente:// En el contexto de datos:
protected override void OnModelCreating(ModelBuilder builder)
{
var navigation = builder.Entity<Invoice>().Metadata.FindNavigation(nameof(Invoice.Lines));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
navigation.SetField("_invoiceLines");
...
}
Convenciones de nombrado de backing fields
Por defecto, Entity Framework Core detectará backing fields de forma automática cuando en la entidad exista un campo privado cuyo nombre coincida con el de una propiedad incluida en el modelo según una convención de nombres predeterminada. Por ejemplo, a una propiedad llamadaCustomerEmail
le asociará automáticamente, sin necesidad de indicarlo expresamente en el OnModelCreating()
, un backing field llamado (en orden de prioridad):_customerEmail
_CustomerEmail
m_customerEmail
m_CustomerEmail
Publicado en Variable not found.
Patrocinador
Oferta de empleo: Desarrollador web .NET (Barcelona / posibilidad en remoto)
Si tienes más de dos años de experiencia desarrollando aplicaciones web ASP.NET/.NET Core y te gustan los retos tecnológicos, ven a trabajar con
nosotros a un proyecto de escala global que está revolucionando la atención al cliente en plataformas e-commerce.
2 Comentarios:
¡Muy útil! Detalles que le faltaba a EF. Gracias por compartir :)
Hola!
Me alegra saber que lo ves útil, Albert.
Muchas gracias por comentar!
Enviar un nuevo comentario