René Pacios

/* Overflow My Brain & More */

Entity Framework Convenciones de Code First y una breve introducción a los ORM

Logo-efTras asistir la semana pasada, junto con otros 90 participantes, al WebCast sobre las novedades de EF 6 realizado por Unai Zorrilla, un auténtico crack que está haciendo una increíble aportación al proyecto, que como ya sabréis lo han liberado como proyecto open source, y Diego Vega, Developer Lead de Entity Framework, me he animado a escribir esta mini explicación de la potencia de EF CodeFirst

 

Un poco de Introducción, (si ya conoces como funciona un ORM puedes saltarte esta parte)

El 99'999999 …. % de las veces que desarrollamos un software necesitamos de algún modo guardar información entre una ejecución y otra del mismo, bien sea en un achivito de texto, xml, cookie, un base de Datos, etc.

Cuando realizamos una aplicación que guarda un considerable volumen de datos una de las elecciones más adecuadas suele ser un Sistema de Base de Datos Relacional (RDBMS) postulada por el señor Codd allá por el año 1970, pensadas para guardar información creada a lo largo del tiempo y cosechar resultados analizando esa información, lo que luego daría lugar a lo que hoy se conoce como Inteligencia de Negocio (Business Intelligence o BI)

Por otro lado los lenguajes de programación han y están en continua evolución, uno de los paradigmas que más se popularizó es la Programación orientada a objetos (POO o OOP), no voy a profundizar en este punto, la idea que quiero que tengáis clara es que: Un Objeto podemos decir que representa una entidad la cual puede cambiar de estado y tener un comportamiento y otro en función de este estado. Como os comentaba en el primer punto de esta introducción, una de los objetivos a cumplir cuando desarrollo un programa es persistir la información entre las distintas ejecuciones del programa, es decir almacenar la información contenida en el estado de mis objetos.

Bien tenemos por un lado un modelo relacional donde podemos guardar información basado en relaciones, tablas (que normalmente no pueden contener toda la información de una entidad),  y cuya información perdurará en el tiempo. Por otro lado tenemos el paradigma de POO que me permitirá modelar el estado del negocio durante la ejecución de mi aplicación, utilizando técnicas de herencia de clases, polimorfismo, etc.  Pero surge un problema, como uno estos dos mundos, técnicamente un profesional de BD podría plantear el mismo modelo que un desarrollador de un lenguaje de POO utilizando particularidades de cada mundo que no existen en el otro. Estas diferencias entre los modelos relacionales y  los modelos objetuales es lo que que se conoce como "desajuste de impedancias"   Impedance Mismatch. Existen muchas diferencias, pero alguna obvia podría ser las relaciones, en el mundo relacional las relaciones entre dos entidades A y B, se basan en la duplicidad de datos, es decir, duplicaríamos la información de un identificador de una de las entidades en la otra, sin embargo en POO las entidades no necesitan duplicar esa información, les basta con tener una referencia al objeto que representa la otra entidad, para que nos entendamos, podríamos verlo como un puntero a la posición de memoria donde se almacena la otra entidad.

Salvar este desajuste de impedancias ha sido siempre objetivo de múltiples herramientas e incluso teorías, quien no ha hecho alguna vez su herramientilla que le leía los objetos de una Base de datos y generaba el código necesario para realizar operaciones CRUD y nuestras clases que representaban cada una de las tablas. En esta idea es en la que se basan los ORM actuales, y que están en continua evolución para facilitarnos el trabajo a la hora de desarrollar un software centrado en datos.

 

Entity Framework, la evolución de ADO.NET hacia un ORM

No se si este subtítulo es muy correcto, pero EF a secas quedaba un poco escueto. El caso es que para solventar los problemas de desajuste de impedancia, la industria del software ha creado varios ORM, muchos de ellos gratuitos y open source. Debemos tener en cuenta que los ORM son dependientes de la tecnología subyacente, y dependen tanto del lenguaje de programación como de la base de datos a utilizar, si bien hay versiones para distintos lenguajes, como el caso de Hibernate (para java) y NHibernate (para .NET) incluso algunos ORM específicos para algún motor de BD  como ORAPOCO que nos permite trabajar de una forma sencilla contra Oracle, aunque Oracle ha publicado su proveedor para trabajar con EF

Existen también otros micro-ORM sencillos, que para muchos casos podrían sernos de gran utilidad como por ejemplo, PETAPOCO, Dapper, massive, Simple.Data,

Pero hoy quiero hablaros de Entity Framework y sus convenciones utilizando Code First, si puedo porque con lo que me estoy liando en esta introducción. Por que digo, ¿utilizando Code First? , bien, Entity Framework tiene varios 3 modos de trabajar:

  • DataBaseFirst: El modelo se basa en un modelo relacional existente y se generan nuestras entidades a partir de este. Esto siempre tuvo el inconveniente de que si teníamos algún tipo de especialización hecha en nuestra base de datos, debíamos hacer el mapeo a mano, ya que EF no lo detectaba y a veces se podía complicar dependiendo que técnica de especialización hubiésemos definido.
  • ModelFirst: El modelo se basaba en un diseño de nuestras clases y a partir de él generábamos los scripts necesarios para crear el modelo relacional.
  • CodeFirst: Del que os voy a hablar en este post, tan sólo debemos crear nuestras clases entidades y definir los DBSet que representan los conjuntos de datos de las entidades y que nos va a permitir aplicar las acciones CRUD sobre ellas.

 

Si quieres sabes más sobre Entity Framework te aconsejo que le eches un vistazo a la documentación oficial del producto: http://msdn.microsoft.com/en-us/library/gg696172(v=vs.103).aspx

 

Entity Framework code First Code Conventions

En este post, voy a hacer un repaso a algunas de las características de EF, las convenciones. Básicamente la idea es: Usa la reglas de nombrado,  y definición que yo te indico para decirme lo que quieres hacer que si lo haces bien, me doy cuenta de lo quieres hacer y genero el modelo relacional como tu me digas.  Un chollo, EF hace inferencia sobre nuestro código para crear el modelo relacional que queramos sin tener que explicar nada, basándose en una serie de reglas preestablecidas

Por otro lado, es posible definir nuestro modelo utilizando "Data Anotations" (vermos algunas) o utilizando la Fluent API, que esto si va a ser material de otro post.

Comenzamos, voy a utilizar VB.NET como lenguaje, ya que la mayoría de ejemplos que hay por internet están en C# para que haya un poquito de todo Sonrisa

Vamos a definir un modelo sencillo para comenzar, una clase Persona que posee varias Tarjetas.

Imports System.Data.Entity

Public Class Entidades Inherits DbContext

Public Property Personas As DbSet(Of Persona)

End Class

Y las clases de nuestras entidades

Public Class Persona

    'Clave primaria
    Public Property PersonaID As Guid
    Public Property Nombre As String
    Public Property Apellidos As String

    'propiedade de navegación, tarjetas de esta persona
    Public Overridable Property Tarjetas As ICollection(Of Tarjeta)
End Class

Public Class Tarjeta

    'Clave primaria
    Public Property TarjetaID As Guid
    Public Property Numero As String
    Public Property FechaCaducidad As DateTime

    'Clave foránea
    Public Property PersonaID As Guid
    'propiedad de navegación al dueño de la tarjeta
    Public Property Persona As Persona
End Class

 

Pero porque creamos sólo un DBSet de persona en nuestro contexto y no también uno para Tarjetas? En este modelo la entidad tomamos la clase persona, la tarjeta es una Objeto Valor(VO) dependiente de persona que no tiene sentido sin ella, podríamos pensar para entender esto en una Factura y la Línea de Factura, una línea de factura por si sola no tiene sentido si no está acompañado de la información de la factura y las otras líneas. Visto de otro modo pensar en la dirección de una persona, en la mayoría de modelos, los distintos domicilios que pueda tener una persona no tiene entidad por si misma, no me aporta información si no esta relacionado con la persona, sin embargo si hablamos de una aplicación que gestiona recibos de la luz, la dirección si tendría entidad, ya que cada domicilio tendría su correspondiente enganche de luz, y la persona sería titular de ese enganche, recibo,etc. Esto es lo que se conoce como patrón Aggregate Roots

Como veis, el código de nuestras entidades es algo sencillo y cotidiano para cualquier programador de OO. bien, si ahora ejecutamos una operación sobre mi modelo de datos, EF se conectará a la BD y creará las tablas si estas no existen, pero vemos que nos ha creado:

 

image

Como podéis apreciar en la imagen de mi SQL Server, EF ha creado una tabla por cada una de las entidades, ha detectado que se trataba de una relación 1:N y lo que es más asombroso, ha sabido detectar cuales son las claves primarías y foráneas con los tipos que debía crear, bueno está bien, el nvarchar(max) para usar unicode tal vez sea pasarse para un nombre de persona, o un número de tarjeta.

Veamos cuales son las convenciones aquí:

EF crea una tabla por cada una de las entidades necesarias, bajo el esquema dbo y adjudicando el mismo nombre que la propiedade de navegación en el caso de la entidad hija, y el nombre del DBSet para la entidad principal. Existe una convención de pluralización de nombres, la cual podemos desactivar ya que no siempre va a pluralizar correctamente nuestros objetos cuando utlizamos la lengua de cervantes para nombrar nuestros objetos. En la nueva versión de EF 6 podremos escribir nuestras propias convenciones, como ejemplo podemos ver la aportación de UnaiZorrilla con un servicio de pluralización para el castellano

La clave de nuestras entidades, EF en caso de no indicarle lo contrario tomará como PK de la tabla el dato de la propiedad ID, o el nombre sea igual que la clase que define la entidad agregándole el sufijo ID ( es igual si es mayúsculas, minúsculas, camelCase, yo lo pongo en mayúsculas por legibilidad), como puede ser en este ejemplo, PersonaID y TarjetaID

Propiedad de navegación y descubrimiento, como vemos sólo hemos agregado a la entidad Persona a nuestro contexto, sin embargo EF ha descubierto que tenemos una entidad relacionada, y ha creado los objetos necesarios en DB para tratarla. Más adelante expondré un ejemplo de herencia, y veremos como EF también nos lo pone súper fácil para mantener este tipo de escenarios.

Entidades hijas, hemos visto como EF hace descubrimiento de tipos relacionados a través de sus propiedades de navegación, pero como ha descubierto cual es la clave foránea? Bien antes de continuar cabe destacar que la propiedad PersonaID de la clase Tarjeta es opcional, (explicaré los motivos por los que es recomendable definir esta propiedad y no delegar su creación de EF en este y el siguiente post de esta serie). Por otro lado la propiedades de navegación de esta misma clase no tenía porque denominarse Persona, podríamos haber definido Propietario As Persona. Si no hubiésemos definido la propiedad PersonaID que define la clave foránea, EF es consciente de que necesita dicha clave, así que la crea, y como no le hemos indicado nada, haría inferencia en los tipos, sabría cual es la clave principal de la Entidad Persona y de que tipo, y en el caso de que la propiedad de navegación la hayamos definido con el nombre "Porpietario" EF crearía un campo en la BD que representaría la clave foránea llamada Persona_Propietario si no me falla la memoria.

Vale pero como sabe que esa propiedad es la clave foránea? pues por el mismo motivo que lo sabe en la entidad Persona, por el nombre de la propiedad de navegación seguida por el sufijo ID. LAs formas de nombrar esta clave foránea son: [nombrePropiedadeNavegacion][nombrePropiedadClavePrincipal] o [nombreClasePrincipalRelacion][NombrePropiedadClavePrincipal] o [nombrePropiedadClaveEntidadPrincipal] , entremos un poco en detalle en como funciona EF en esta parte que parece interesante.

En EF cada objeto puede tener varias propiedades de navegación (en el caso de que esté relacionado con varias entidades) y esta relación pude ser 1:1, 1:N, con carnalidades de 0 o 1 EF crea estas relaciones en función de como hayamos definido nuestras propiedades. Por ejemplo, en nuestro caso hemos definido la porpiedad Tarjetas como una colección y en la entidad tarjeta una propiedad persona como una propiedad de navegación, EF infiere sobre este diseño deduciendo que se trata de una relación 1:N.

Que pasa con las integridades referenciales? Bien, me alegro que me hagas esa pregunta René, ahí es donde entra la definición de las propiedades de clave foránea, si definimos esta propiedad como no nulable, tal y como está definido en nuestro modelo,  EF Code First, estdablece el borrado en cascada para esta relación, de forma que cuando eliminemos un registro de tipo persona, se eliminarán todas las tarjetas relacionadas con este sujeto. Si quisiésemos evitar este comportamiento podríamos definr la propiedad de clave foránea como nulable, de forma que cuando se haga un borrado de persona todas sus tarjetas se establecerá a null la clave foránea. Como ya he dicho, todas estas convenciones se pueden redefinir con decoradores en las propiedades o mediante el fluent API Relationships

Otra convención que no he mencionado es la de mapear tipos complejos, es decir podremos crear una clase con el objetivo de usarla como un Objeto Valor. Veamos un ejemplo:

Podremos crear una clase de este tipo:

Public Class Domicilio
    Public Property Calle As String
    Public Property Portal As String
    Public Property Piso As Integer
    Public Property Puerta As Char
End Class

 

Y en nuestra entidad crear una propiedad para indicar el domicilio:

Public Class Persona

    'Clave primaria
    Public Property PersonaID As Guid
    Public Property Nombre As String
    Public Property Apellidos As String

Public property Domicilio as Domicilio

    'propiedade de navegación, tarjetas de esta persona
    Public Overridable Property Tarjetas As ICollection(Of Tarjeta)
End Class

 

Si generamos la base de datos a partir de este modelo, vemos como EF ha creado la nueva tabla persona:

image

Como podéis ver, EF ha creado las propiedades del tipo complejo, como campos de la tabla que o contiene, esto nos pude ser bastante util a la hora de definir varias entidades/tablas con definiciones comunes de atributos. Pero, ¿porque EF no ha tomado esta clase como una entidad dependiente al igual que pasa con Tarjetas? Bien el motivo es que esta clase no tiene ninguna Clave Principal definida, y como sabéis el modelo relacional promulga que cada tupla sea inequívocamente identificable, así que por convención si EF no puede encontrar una clave para la clase, toma dicha clase como un tipo VO.

Y que pasa si soy un rebelde y no quiero nombrar las propiedades como indica la convención? Bien, para esto se puede utilizar decoradores en los miembros (Data Annotations) de nuestras clases o fluent API de EF. Veamos un ejemplo en código de como utilizar el primero de los métodos mencionados, mostrando algún detalle a mayores.

Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema

'estamos indicando el nombre que debe tener la tabla en el modelo relacional
<Table("Gente")> _
Public Class Persona

    'indicamos que esta propiedad será la clave principal de la tabla
    <Key>
    Public Property PersonaClave As Guid

    'Un string por definición puede ser nulo, así que creará un campo en la BD que permite nulos, para evitarlo decoramos la propiedad con el atributo required
    'Podemos indicar una restricción en nuestro modelo, e indicar un mensaje de error, este podría estar localizado en un archivo de recursos, util en aplicaciones multiidioma
    <Required()>
    <MinLength(4, errormessage:="El nombre debe tener al menos 4 letras")>
    Public Property Nombre As String

    'Indicamos el tamaño que debe tener, esto tendra repercusión en el campo en la base de datos 
    <MaxLength(128)>
    Public Property Apellidos As String

    'Con NotMapped le indicamos a EF que no cree el campo para esta propiedad en la base de datos, seguro que se os ocurren ocasiones donde esto puede ser util ;-)
    <NotMapped()>
    Public ReadOnly Property NombreCompleto As String
        Get
            Return String.Format("{0} {1}", Nombre, Apellidos)
        End Get
    End Property

    'Este campo es requerido por definición
    Public Property Edad As Integer

    'este campo se definirá como opcional, permitirá nulos en la BD
    Public Property CuantasVecesEntro As Integer?


    Public Property Domicilio As Domicilio

    'propiedade de navegación, tarjetas de esta persona
    Public Overridable Property Tarjetas As ICollection(Of Tarjeta)
End Class

Public Class Tarjeta

    'Clave primaria
    Public Property TarjetaID As Guid

    'definimos el nombre de la columna y el tipo de datos para usar con SQL Server
    <Column("Numero_de_tarjeta", TypeName:="varchar(50)")>
    Public Property Numero As String
    Public Property FechaCaducidad As DateTime

    'Clave foránea
    Public Property PersonaFK As Guid

    'propiedad de navegación al dueño de la tarjeta
    'le estoy indicando cal será el campo clave foránea para la entidad persona
    <ForeignKey("PersonaFK")>
    Public Property Persona As Persona
End Class

'le estoy indicando a EF que esta clase es un tipo por valor, no debe considerarlo una entidad
<ComplexType()>
Public Class Domicilio
    Public Property Calle As String
    Public Property Portal As String
    Public Property Piso As Integer
    Public Property Puerta As Char
End Class

Como podéis ver he ampliando un poco el modelo anterior para que podáis ver la potencia, y lo sencillo que es indicar a EF como inferir en nuestro código. Espero que con los comentarios que he dejado en el código se entienda el objetivo de cada atributo. El uso de Data Anotatiosn puede resultarnos muy útil si estamos migrando un viejo modelo a Entity Framework, y no podemos hacer uso de las convenciones pues el nombre de las tablas de la BD o el de las propiedades no tiene por que regirse por las mismas convenciones que EF, en ese caso tan sólo debeos coger nuestras entidades decorarlas con los atributos para que se mapee correctamente con la base de datos, que por otro lado tal vez ya exista.

Por otro lado si debes realizar una aplicación desde 0 sobre una base de datos existente, y te gustaría utilizar Code First en vez de DataBase First te aconsejo que descarges las Entity Framework Power Tools Beta 3 que serán de gran utilidad, sobre todo en este último escenario comentado.

 

Con la llegada del nuevo EF 6, y la gran cantidad de novedades, podremos inferir más en como se comportará EF, por poner un ejemplo podríamos crear nuestras propias reglar o convenciones, trataré de escribir un post sobre estas y algunas novedades de esta nueva versión, si el tiempo me lo permite claro Sonrisa.

Espero que os haya resultado interesante, nos leemos, René Pacios.

Acerca de René

René Pacios es un apasionado de la tecnología, autodidacta, emprendedor, le encanta el desarrollo web, para moviles, aplicaciones, todo aquello que automatice tareas y haga que las máquinas trabajen para él. Es un gran fan de las tecnologías Microsoft, y le encanta estar a la última siempre que el tiempo se lo permite. Siempre quiso ser cantante, pero creo que en esta vida se va a quedar sólo en canta-mañanas

               

Agregar comentario

Loading