domingo, 30 de junio de 2013

[Entity Framework][Code First] Complex Type

 

Introducción


Cuando se modelan entidades suele plantearse la necesidad de definir propiedades que se agrupen para definir un tipo de dato, ejemplo de estos podrían ser los datos de contacto, direcciones, etc

Estos tipos de dato no tienen una entidad por si mismos, o sea no tienen un id o código que los identifique, sino que son parte de otra entidad.

La idea es poder representar algo como lo siguiente

image

Las entidades de proveedores y empleados se asocian a otras que permiten agrupar propiedades bajo un mismo concepto.

Pero a nivel de persistencia la vista es bastante diferente

image

Los campos que se agrupan en entidades como ser Localidad o Contacto ahora son campos individuales en cada tabla.

Existen varias formas de definir este tipo de persistencia en Entity Framework según se la quiere utilizar para una sola entidad o compartir entre varias.

 

Definición de las entidades


Tanto la entidad empleado como el proveedor defienden un grupo de propiedades que representa la localización, pero el proveedor además define un grupo adicional denominado contacto

La definición del proveedor se realiza en Supplier.cs

public class Supplier
{
    public Supplier()
    {
        this.Localization = new Localization();
        this.Contact = new Contact();
    }

    public int Codigo { get; set; }
    public string CompanyName { get; set; }

    public Localization Localization { get; set; }
    public Contact Contact { get; set; }

}

public class Contact
{
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }

    public string Phone { get; set; }
    public string Fax { get; set; }
    public string HomePage { get; set; }

    public bool HasValue
    {
        get
        {
            return this.ContactName != null
                || this.ContactTitle != null
                || this.Phone != null
                || this.Fax != null
                || this.HomePage != null;
        }
    }
}

La definición de empleado se realiza en Employee.cs

public class Employee
{
    public Employee()
    {
        this.Localization = new Localization();
    }

    public int EmployeeID { get; set; }

    public string LastName { get; set; }
    public string FirstName { get; set; }

    public Localization Localization { get; set; }

}

Y ambos hacen uso de la clase que define la localización, definida en LocationComplexType.cs 

public class Localization
{
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }

    public bool HasValue
    {
        get
        {
            return this.Address != null
                || this.City != null
                || this.Region != null
                || this.PostalCode != null
                || this.Country != null;
        }
    }

}

Toda esta separación se realiza para demostrar que hay varias formas forma de separar la clase, se puede realizar físicamente en un .cs o puede definirse junto a la clase que la utiliza.

 

Definición básica del modelo


Si solo definimos las entidades sin ningún otro tipo de especificación.

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
    }

    public DbSet<Supplier> Suppliers { get; set; }
    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

        base.OnModelCreating(modelBuilder);
    }

}

creara los siguiente estructura de datos

image

Aplicara la definición por convención para los campos a los cuales les agregara un prefijo según el tipo complejo donde estén definida la propiedades.

Sin definir prácticamente nada ya obtenemos un modelo de datos en donde EF aplica las convenciones, pero puede que esto no nos guste por lo que a continuación vamos a ver como personalizar la persistencia de la entidad.

 

Definir ComplexType para una sola entidad


Analicemos la entidad Supplier, en la cual se puede especificar la definición del tipo complejo directamente en la entidad, es por eso que se utiliza:

Property(x => x.Contact.ContactName)…

Se accede a la propiedad que define la clase compleja y se mapea cada una de las propiedad.

 

public class SupplierMap : EntityTypeConfiguration<Supplier>
{
    public SupplierMap()
    {
        HasKey(x => x.SupplierID);
        Property(x => x.CompanyName).HasMaxLength(40).IsRequired();


        Property(x => x.Contact.ContactName).HasColumnName("ContactName").HasMaxLength(30);
        Property(x => x.Contact.ContactTitle).HasColumnName("ContactTitle").HasMaxLength(30);
        Property(x => x.Contact.Phone).HasColumnName("Phone").HasMaxLength(24);
        Property(x => x.Contact.Fax).HasColumnName("Fax").HasMaxLength(24);
        Property(x => x.Contact.HomePage).HasColumnName("HomePage").HasColumnType("ntext");

    }
}

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
    }

    public DbSet<Supplier> Suppliers { get; set; }
    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new SupplierMap());

        base.OnModelCreating(modelBuilder);
    }

}

 

la línea:

modelBuilder.Configurations.Add(new SupplierMap());

define la clase que tiene las especificaciones del mapping de la entidad

Estas modificaciones cambian el aspecto de la tabla generada en base al modelo

image

 

Definir ComplexType compartido entre entidades


Tanto la entidad Supplier como Employee  definen un tipo complejo en común, definido en la clase Localization.

El mapping de esta entidad puede realizarse de dos formas:

  • Se puede definir mediante ComplexTypeConfiguration<>  en el OnModelCreating()

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
    }

    public DbSet<Supplier> Suppliers { get; set; }
    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new SupplierMap());

        ComplexTypeConfiguration<Localization> complexLocalizacion = modelBuilder.ComplexType<Localization>();
        complexLocalizacion.Property(x => x.Address).HasColumnName("Address").HasMaxLength(60);
        complexLocalizacion.Property(x => x.City).HasColumnName("City").HasMaxLength(15);
        complexLocalizacion.Property(x => x.Region).HasColumnName("Region").HasMaxLength(15);
        complexLocalizacion.Property(x => x.PostalCode).HasColumnName("PostalCode").HasMaxLength(10);
        complexLocalizacion.Property(x => x.Country).HasColumnName("Country").HasMaxLength(15);

        modelBuilder.Configurations.Add(new EmployeeMap());

        base.OnModelCreating(modelBuilder);
    }

}
  • Definir una clase de mapping que especifique la configuración

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
    }

    public DbSet<Supplier> Suppliers { get; set; }
    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new SupplierMap());

        modelBuilder.Configurations.Add(new LocationComplexMap());

        modelBuilder.Configurations.Add(new EmployeeMap());

        base.OnModelCreating(modelBuilder);
    }

}
public class LocationComplexMap : EntityTypeConfiguration<Localization>
{
    public LocationComplexMap()
    {
        Property(x => x.Address).HasColumnName("Address").HasMaxLength(60);
        Property(x => x.City).HasColumnName("City").HasMaxLength(15);
        Property(x => x.Region).HasColumnName("Region").HasMaxLength(15);
        Property(x => x.PostalCode).HasColumnName("PostalCode").HasMaxLength(10);
        Property(x => x.Country).HasColumnName("Country").HasMaxLength(15);
    }
}

 

Por medio de la línea:

modelBuilder.Configurations.Add(new LocationComplexMap());

se asocia la clase de mapping con la definición del contexto

La ejecución del Test generara en la base de datos las tablas con al estructura y tipos de datos que buscamos:

image

 

Test - Recuperar todos los proveedores


Los diferentes test que confeccionemos nos ayudara a validar la definición del mapping de cada entidad

Recuperamos la lista de todos los proveedores

SNAGHTML52fcba0f

La ejecución del test inserta un nuevo proveedor

image

Y luego lo recupera

image

Se puede ver a simple fácilmente como se incluye en las queries los campos que forman las entidad complejas.

 

Test - Recuperar un solo empleado


En el test del empleado se crea una nueva entidad empleado, esto luego se recupera para validar que los campos son correctamente mapeados contra la tabla.

 

image

image

Al igual que en el test anterior la consulta filtra por el id de la entidad que se quiere recuperar incluyendo los campos que conforman la entidad mas los que definen los tipos complejos.

image

 

Código


Se utiliza Visual Studio 2012, la base de datos es creada por el mismo Entity Framework cuando se ejecutan los test

[C#]
 

5 comentarios:

  1. Buenisimo! la verdad que si haces articulos como estos mas seguido dejo la facultad!! :)

    ResponderEliminar
  2. Leo ! te hago una consulta, si queres setear una columna como unique o darle un valor default, lo podes hacer, en el archivo de mapeo de cada entidad o lo tenes que hacer en la bd?

    ResponderEliminar
  3. hola Patos

    aqui

    http://social.msdn.microsoft.com/Forums/es-ES/0a2e2ac6-f78b-40e9-bb25-c72bdba755a9/indice-uniqe-con-fluent-api-code-first

    respondi sobre el mismo tema


    saludos

    ResponderEliminar
  4. Hola Leandro, gracias por el articulo, en la clase Supplier esta la propiedas Codigo pero deberia ser SupplierID como esta en el codigo de ejemplo.

    ResponderEliminar
  5. hola andres

    tienes razon en las primeras imagenes se puede visualizar la propiedad Codigo en lugar de SupplierID, es que al principio queria plantear el articulo mostrando la definicion por fuera de la convencion que define EF y despues lo cambie, pero olvide actualizar esa parte

    lo voy a editar asi lo defino de forma correcta
    gracias por la correccion

    saludos

    ResponderEliminar