miércoles, 5 de noviembre de 2014

[Entity Framework] Code First - Cargando Entidades Relacionadas

 

Las asociaciones pueden trabajarse por lazy load o definir explícitamente que cargue una propiedad relacionada, esos temas serán tratados en el video, así como se comportan las entidades cuando están fuera del contexto.

También veremos como analizar las queries que EF genera, utilizando Sql Server Profiler.

 

 

Link


image

martes, 4 de noviembre de 2014

[Entity Framework] Code First - Manejando Relaciones

 

Cuando se trabaja con entidades es muy raro que se encuentren aisladas por lo general estas se asocian con otras para colaborar.

Las asociaciones que trato en el video serán: uno a uno, uno a muchos o muchos a muchos.

 

 

Link


image

lunes, 3 de noviembre de 2014

[Entity Framework] Code First - Consultando entidades

 

Después de configurar el modelo será necesario aprender a operar con el mismo para obtener la información que nuestra aplicación necesita.

Las queries linq que definamos serán transformadas a consultas sql dinámicamente por EF, por eso es importante entender como funcionan, aquí verán como trabajar entidades simple, como agruparlas, herencia, tipos dinámicos, etc.

 

 

Link


image

domingo, 2 de noviembre de 2014

[Entity Framework] Code First - Fluent Api

 

En el video explico como por medio de código se define el mapping de las entidades pudiendo especificar: clave principal, tipos y precisión en los datos, relaciones entre entidades, herencia, etc.

Por medio de código podrán redefinir las relaciones cuando estas no siguen las convenciones.

 

 

Link


image

sábado, 1 de noviembre de 2014

[Entity Framework] Code First - Convenciones

 

Les comparto este video donde explico las convenciones que Entity Framework utiliza para descubrir los tipos de datos, la clave principal, las relaciones entre entidades, etc.

Podemos definir nuestro modelo sin necesidad de escribir explícitamente en código como mapea con la base de datos, solo será necesario seguir ciertas normas preestablecidas en las convenciones.

 

 

 

Link


image

miércoles, 1 de octubre de 2014

MVP C# 2014

 

Gracias por haber confiando una vez mas, no puedo cree que ya pasaron 5 años.

 

Tuttini MVP 2014

 

Tuttini MVP 2014  Tuttini MVP 2014

jueves, 22 de mayo de 2014

Comunicar Formularios

 

Introducción


Existen varias formas pasar información entre formularios, especialmente si queremos hacerlo sin generar una fuerte dependencia entre estos.

Este mismo tema lo había abordado en artículos anteriores por lo que retomaremos el análisis de las distintas formas de lograr la comunicación sin generar dependencia entre los forms

Los artículos anteriores en los cuales he tratado el tema:

Comunicar formularios de forma desacoplada

Comunicar formularios MDI

Para ejemplificar haremos uso de un form que permite listar y editar productos de la base de datos Northwind.

image

Analizaremos 3 formas de comunicar formularios y enviar datos entre ellos:

  • Usando Interfaces
  • Usando eventos
  • Usando el evento FormClosed

 

Usando interfaces


Una interfaz permite definir un contrato entre clases, permitiendo que una invoque la funcionalidad de cualquier otra que respete ese contrato definido.

Recordemos que los formularios en definitiva no son mas que clases que se instancian por lo que puede aplicarse los mismos conceptos de POO (Programación orientada a objetos).

La implementación de esta técnica se encuentra en la búsqueda de un proveedor en el form de edición de los productos.

image

El primer paso será definir la interfaz

image

siendo implementada por el formulario padre receptora de la información seleccionada por el usuario

SNAGHTML5a01d235

El form padre será quien instancie el form hijo pasando su instancia por parámetro en el constructor, además de algún otro valor adicional de ser necesario, como en este caso un filtro para limitar los ítems visualizados en el grid de selección.

 image

 

Desde el form hijo (frmFindSupplier) se define el constructor que recibirá los parámetro

SNAGHTML5a09bacf

La variable “caller” contendrá la instancia del form padre que implementa la interfaz, mientras que “_supplierName” tendrá el input del usuario para filtrar las entidades.

Al seleccionar un ítem del grid se dará por seleccionada la entidad y se enviara la acción al form padre a través de la instancia invocando el método definido en la interfaz.

image

Primero se valida que exista selección en el grid, se recupera la entidad seleccionada pasándola como parametro al método Selected() definido en la interfaz, por ultimo se cierra el formulario. El form padre recibe el valor seleccionado y actualiza los controles o entidades que hagan falta.

La secuencia completa quedaría de esta forma

SNAGHTML5b152bbf

  1. El form padre (frmEditProductos) implementa la interfaz.
  2. En el botón de búsqueda se crea la instancia del form hijo (frmFindSupplier), pasando como parámetro en el constructor la instancia del forma padre (utilizando this)
  3. El form hijo recibe en el constructor los valores por parámetro
  4. Se selecciona un ítem del grid y se invoca al método de la interfaz
  5. El form padre recibe la selección y actualiza los datos

Como ventaja se puede mencionar que cualquier form que implemente la interfaz podrá reutilizar la funcionalidad de búsqueda implementada al no quedar acoplados los formularios que interactuan.

 

Usando eventos


Permite enviar una acción directa al form que se subscriba al evento.

El ejemplo va a estar representado en la comunicación del form de edición de productos y la búsqueda de categorías.

image

Partamos analizando la implementación del form hijo (frmFindCategory), este expone el evento, que es lanzado cuando se selecciona una categoría del grid

SNAGHTML5a76cd3

Para definir datos específicos en el intercambio de información se crea una clase que hereda de EventArgs

image

 

Desde el form padre (frmEditProductos) simplemente se debe adjuntar al evento la instancia del form hijo

image

 

Repasemos como seria la ejecución en secuencia

SNAGHTML5c710fe

 

  1. El form hijo expone el evento, utilizado para informar la selección a quien lo haya invocado.
  2. Se define una clase que hereda de EventArgs para poder pasar información adicional como argumento del evento.
  3. El form padre que realiza la búsqueda crea la instancia del forma hijo y se adjunta al evento, además crea el método que recibirá la acción.
  4. Cuando se selecciona un ítem del grid se valida si alguien esta adjunto en el evento y se invoca.

 

Actualizar los datos al cerrar el form (evento FormClosed)


Para actualizar un grid luego de una acción en otro form podría utilizarse eventos existentes, como ser el FormClosed, con este evento se puede realizar una acción al cerrar un form

image

 

Analicemos los pasos

SNAGHTMLaeed38d

 

  1. En el form padre (frmAdminProducts) instancia el form hijo (frmEditProductos) y se adjunta el evento de cierre del form
  2. Cuando se presiona el boton de aceptar o cancelar se devuelve el DialogResult cerrando el form, esto lanza el evento FormClosed
  3. Desde el form padre se atrapa el evento y se realiza la recarga de los datos del grid

 

Descarga


El articulo fue desarrollando usando Visual Studio 2012, la base de datos es Sql Compact (.sdf)

Durante la compilación se descargaran las dependencias por medio de Nuget.


[C#]
 

sábado, 28 de diciembre de 2013

[ASP.NET MVC] Carga DropDownList dependientes - Usando un único action

 

Introducción


Este articulo propone una versión diferente a la planteada en el anterior

[ASP.NET MVC] Carga DropDownList dependientes

En este caso definiremos un único action para recibir la selección que realice el usuario.

La funcionalidad de la pantalla se conserva idéntico al articulo anterior, al igual que el modelo de datos.

 

Modelo de Vista


Para el modelo se define una clase que recibirá las propiedades con la selección del usuario, además de propiedades que permitirán cargar los controles de listas en la view

image

 

Definición de la View


La vista esta formada por un solo Form, el cual realiza el post al action Index() el cual se encuantra marcado con el atributo [HttpPost]

SNAGHTML19f9df10

 

Tanto si se selecciona un ítem combo, o se presiona el botón del submit (el cual selecciona los empleados) todas las acciones invocan al único action

A diferencia del articulo anterior ya no se requieren de hidden para conservar la selección de la acción anterior.

 

Definición del action


El action implementar bastante mas lógica que la usada en el articulo anterior, ya que deberá determinar que propiedades vienen con datos y cargar las listas en consecuencia.

 

[HttpPost]
public ActionResult Index(EmployeeModel model)
{
    //se carga la lista de regiones
    var regionList = regionRepository.GetAll();

    List<Territory> territoryList = null;
    List<Employee> employeeList = null;

    //si hay una region seleccionada 
    if (model.RegionId != null)
    {
        //se carga la lista de territorios
        territoryList = territoryRepository.Filter(x => x.RegionID == model.RegionId);

        //si hay un territorio seleccionado
        if (!string.IsNullOrEmpty(model.TerritoryId))
        {
            //se recupera el territorio, para poder obtener los empleados asociados
            employeeList = territoryRepository.GetEmployees(model.TerritoryId);
        }

    }


    model.RegionList = new SelectList(regionList, "RegionID", "Description");

    if (territoryList != null && territoryList.Count > 0)
    {
        model.TerritoryList = new SelectList(territoryList, "TerritoryID", "Description");
    }

    if (employeeList != null && employeeList.Count > 0)
    {
        model.EmployeeList = new SelectList(employeeList, "EmployeeID", "FullName");
    }


    return View("Index", model);

}

 

Si se selecciona una región se recibirá el valor en el parámetro del modelo

SNAGHTML1a04b6aa

 

Si luego se selecciona un territorio

SNAGHTML1a08bf80

 

Y al marcar empleados sobre el listbox y presionar el botón de submit se carga la lista

SNAGHTML1a0b84e0

 

En cada invocación al acción será necesario volver a cargar las listas del model utilizadas para definir los ítems de los controles la la view, ya que estas no participan en el proceso de binding donde son asignados los valores que se envían en al post a las propiedades del parámetro del action.

 

Model Binding


Para entender como se produce la asignación de la selección de los controles con las propiedades del modelo es necesario evaluar el html

Si las propiedades “name” de los controles que se renderizan en el html coinciden con los nombres de las propiedades de la clase definida como modelo la asignación del valor es automáticamente.

 

SNAGHTML1a315f77

si algún control no llegara a tener esta coincidencia se puede redefinir el name utilizando

image

 

Código


 

[C#]
 

viernes, 27 de diciembre de 2013

[ASP.NET MVC] Carga DropDownList dependientes (cascada o secuencial)

 

Introducción


Es muy común en las aplicaciones necesitar de controles que dependan unos de otros para cargar los ítems que serán desplegados al usuario.

En este articulo explicare como llevar a cabo esta tarea implementando la carga en cascada de combos y listas, además poder tomar en cada post la selección que realiza el usuario.

El ejemplo permitirá seleccionar una región la cual será utilizada como filtro para cargar los territorios, este ultimo cargar una lista de empleados, el usuario seleccionara uno o mas de la lista.

 

image

 

Modelo de datos


Haremos uso del siguiente modelo de datos el cual define las clases que serán mapeadas a las tablas utilizando Entity Framework

Vemos como interactúan las entidades para permitir la asociaciones que las vinculan.

image

 

Modelo de Vista


Para poder interactuar con al view de mvc creamos una clase que actuara de modelo, esta nos permitirá definir los ítems de las listas que desplegaremos al usuario.

En el modelo tendremos propiedades simples que contendrán la selección del usuario y también lista que nos permitirán cargar los dropdownlist y listbox que presentaremos al usuario.

 

image

Las propiedades de lista se definen del tipo SelectList, de esta forma desde el controlador indicamos cuales serán las propiedades que usara la lista como value y display en el dropdownlist

 

Lista de Regiones


Al iniciar la aplicación el primer controlador que se ejecuta es el Index(), en este invocamos al repositorio para obtener la lista de regiones

image

 

Creamos una instancia de la clase Model que el view requiere, a esta le asignamos la lista de regiones que mostrara en el combo

En la view se carga el dropdownlist y se controla con la ayuda de jquery cuando el usuario cambia la selección, lo cual produce el post del form.

En la imagen podemos ver como se relacionan los id del dropdownlist y del form con el evento change() de jquery para generar el submit al action del controlador

 

SNAGHTML146b09bb

 

Lista de Territorios


Al seleccionar una región se invoca el action definido en el BeginForm()

SNAGHTML14776cd1 

Si hacemos coincidir el name del control dropdownlist con el del parámetro del action este recibirá el valor. Usaremos la selección para filtrar la lista de territorios.

 

Lista de Empleados


Al seleccionar un ítem del combo de territorios se dispara el submit del forma que invoca el action del controlador, en este caso se pasaran como argumento el id de la región y del territorio para poder filtrar los datos y cargar las listas

 

SNAGHTML14cd3b57

Seguramente se habrá notado la definición de controles Hidden que permiten enviar en cada submit del form los datos seleccionados en una acción anterior

 

Selección de empleados


Cuando se selecciona los empleados de la lista y se presiona el botón se realiza el submit enviando la la selección del listbox al action

SNAGHTML14d57876

En cada invocación a los action se vuelve a crear una instancia del model asignando la selección y cargando las listas que requiere el view

 

Código


Se utilizo Visual Studio 2012, la base de datos de encuentra en la carpeta App_Data, se utilizo Sql Server Express 2012

[C#]
 

domingo, 3 de noviembre de 2013

[ASP.NET MVC] WebGrid Filtrado por DropDownList

 

Introducción


En el articulo aprenderemos :

  • como capturar la selección de un DropDownList
  • filtrar una query usando Entity Framework
  • desplegar los datos en un WebGrid

La idea es listas en un dropdownlist los clientes (customers) y al seleccionar alguno, listar las ordenes de compra realizadas.

image

 

Modelo de Dominio


Empezaremos analizando el dominio de entidades que usaremos en el ejemplo.

image

La entidad Orden tiene relación con el cliente (customer) y un empleado (employee), usaremos el primero para filtrar mientras que el segundo será información adicional que se mostrara en una columna del grid

 

Definición de la persistencia


El acceso a los datos lo realizaremos con al ayuda de Entity Framework, por lo que deberemos definir el DbContext y las clases de mapping para cada entidad

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
        Database.SetInitializer<NorthWindContext>(null);

        this.Configuration.LazyLoadingEnabled = false;
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Customer> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new CustomerMap());
        modelBuilder.Configurations.Add(new EmployeeMap());
        modelBuilder.Configurations.Add(new OrderMap());

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

        base.OnModelCreating(modelBuilder);
    }

}


public class CustomerMap : EntityTypeConfiguration<Customer>
{
    public CustomerMap()
    {
        HasKey(x => x.CustomerID);

        Property(x => x.CustomerID)
                .HasColumnType("nchar")
                .HasMaxLength(5);


        Property(x => x.CompanyName)
                .HasMaxLength(40)
                .IsRequired();

        Property(x => x.ContactName)
                .HasMaxLength(30);

        Property(x => x.ContactTitle)
                .HasMaxLength(30);

    }
}

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        HasKey(x => x.EmployeeID);

        Property(x => x.LastName)
                .HasMaxLength(20)
                .IsRequired(); ;

        Property(x => x.FirstName)
                .HasMaxLength(10)
                .IsRequired();

        Ignore(x => x.FullName);

    }
}

public class OrderMap : EntityTypeConfiguration<Order>
{
    public OrderMap()
    {
        HasKey(x => x.OrderID);

        Property(x => x.CustomerID)
            .HasColumnType("nchar")
            .HasMaxLength(5)
            .IsOptional();

        HasOptional(x => x.Customer)
                .WithMany()
                .HasForeignKey(x => x.CustomerID);

        HasOptional(x => x.Employee)
                .WithMany()
                .HasForeignKey(x => x.EmployeeID);

        Property(x => x.ShipName)
                .HasMaxLength(40);

        Property(x => x.ShipAddress)
                .HasMaxLength(60);

    }
}


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

 

En este caso estamos utilizando una db existente por lo tanto importante definir la línea:

Database.SetInitializer<NorthWindContext>(null);

de esta forma indicamos que no se debe crear la db cuando ejecutemos la aplicacion

 

Definir datos del dropdownlist


Para la vista definiremos un modelo que nos permita especificar los datos que mostraremos al usuario.

public class CustomerOrdersModel
{

    public SelectList CustomerList { get; set; }

    public List<Order> OrderList { get; set; }

}

 

Es por eso que encontraremos dos listas, una para cargar los ítems del combo y la segunda para las ordenes del grid.

Al ingresar al action Index(), el cual responde a los pedidos GET, del controlador debemos cargar la lista del combo, nos ayudaremos con el repositorio para esta tarea.

public ActionResult Index()
{

    CustomerOrdersModel model = new CustomerOrdersModel()
    {
        CustomerList = new SelectList(repository.GetAllWithoutSelection(), "CustomerID", "CompanyName")
    };

    return View(model);
}

 

la vista hará uso de las propiedades del modelo para cargar el dropdownlist

SNAGHTML978badb8

 

Capturar selección dropdownlist


La captura de la selección puede realizarse de varias formas, pero no debe perderse de vista que es la propiedad “name” de los controles html los que participan en el post que se realiza al servidor, no hay que confundirse con la propiedad “id” esta solo es útil para manipular el control en el cliente usando jquery.

Para capturar la acción del usuario al cambiar la selección en el dropdownlist nos ayudamos de jquery, para poder definir el selector definimos un id en el tag <form>

SNAGHTML97918cef

Es en ese momento cuando forzamos un submit para invocar a un action concreto

image

en el controlador encontraremos

[HttpPost]
public ActionResult Index(string CustomerList)
{
    //
    // usamos el metodo del repositorio el cual nos permite recuperar las odenes de compra del cliente
    // se incluye en esta la informacion del cliente y el empleaod que realizo la venta
    //
    var orders = orderRepository.Filter(x => x.CustomerID == CustomerList,
                                            new List<Expression<Func<Entities.Order,object>>>() { x=>x.Employee, x=>x.Customer });

    CustomerOrdersModel model = new CustomerOrdersModel()
    {
        CustomerList = new SelectList(repository.GetAllWithoutSelection(), "CustomerID", "CompanyName"),
        OrderList = orders
    };


    return View(model);
}

 

El nombre del parámetro debe coincidir con la propiedad “name” del control para poder tomar el dato, en este caso hemos dejado el valor por defecto que crear asp.net mvc, pero podríamos redefinirlo.

SNAGHTML97af81c9

Si en la vista cambiamos la propiedad “name” debemos también hacerlo en el parámetro del action.

SNAGHTML97a1f0d0

 

 

Definición del WebGrid


El post al action devolverá un nuevo modelo con la lista de ordenes cargada

image

En la definición del grid es cuando indicamos que propiedad del modelo define los datos

var grid = new WebGrid(Model.OrderList, canPage: false, canSort: false);

luego se define el lugar donde ira el grid y que columnas se mostrara

 

@if (Model.OrderList != null)
{
    @grid.GetHtml(
                tableStyle: "webgrid-table",
                headerStyle: "webgrid-head",
                alternatingRowStyle: "webgrid-alternating-row",
                selectedRowStyle: "webgrid-selected-row",
                
                columns: grid.Columns(
                    grid.Column(columnName: "Employee.FullName", header: "Empleado", style: "webgrid-column-employee"),
                    grid.Column(columnName: "OrderDate", header: "Fecha Pedido", format: x=>x.OrderDate.ToString("dd/MM/yyyy")),
                    grid.Column(columnName: "RequiredDate", header: "Fecha Requisito", format: x=>x.RequiredDate.ToString("dd/MM/yyyy")),
                    grid.Column(columnName: "ShippedDate", header: "Fecha Envio", format: x=>x.ShippedDate != null ? x.ShippedDate.ToString("dd/MM/yyyy") : ""),
                    grid.Column(columnName: "ShipName", header: "Nombre Envio"),
                    grid.Column(columnName: "ShipAddress", header: "Direccion Envio") 
                
                ))
}
else
{
    <p>No hay datos</p>
}

se podría resaltar como se especifica una columna cuando la entidad esta relacionada con otra

grid.Column(columnName: "Employee.FullName", header: "Empleado", style: "webgrid-column-employee"),

al necesitar el nombre del empleado se realiza por medio de la relación

También se definió formato para las columnas fecha

grid.Column(columnName: "ShippedDate", header: "Fecha Envio", format: x=>x.ShippedDate != null ? x.ShippedDate.ToString("dd/MM/yyyy") : ""),

no solo indicando la representación de la fecha sino también validando cuando esta pueda tener un valor nulo

 

WebGrid referencia en asp.net mvc


Según el tipo de proyecto que crees en el Visual Studio puede que no tengas acceso al WebGrid, si este es el caso deberá agregar la referencia a la librería, para esta tarea NuGet nos dará una mano

Solo es cuestión de buscar: microsoft-web-helper

en la herramienta de nuget y proceder a instalarlo

SNAGHTML988cc7c2

 

Código


Para el ejemplo hemos utilizado Visual Studio 2012, la base de datos se encuentra en la carpeta App_Data del proyecto web

[C#]
 

martes, 1 de octubre de 2013

MVP C# 2013

 

Simplemente quería agradecer por ser reconocido por cuarto año.

Esto marca que el camino es el correcto, así que seguiré ayudando y aportando mi granito de arena.

 

Leandro Tuttini - mvp2013

lunes, 9 de septiembre de 2013

[Entity Framework][Code First] Dividir Tabla (Table Splitting)

 

Introducción


Cuando se definen entidades estas pueden contener atributos que no siempre se quiere recuperar, ya que de hacerlo podría afectar la performance. Un ejemplo se observa en aquellos atributos donde se persisten datos de un volumen importante, como ser campos del tipo image, varbinary o ntext, los cuales por lo general no son necesarios recuperar cuando se carga un listado de la entidad en un grid, pero si son necesarios cuando se edita una entidad individual.

Por supuesto no solo esta limitado a este uso, se podría dividir simplemente porque un conjunto de propiedades representan la información clave de la entidad la cual es frecuentemente utilizada, separándola de aquellos atributos donde, salvo en algún uso puntual, no siempre se requieren.

Esta división permite mapear una tabla simple en múltiples entidades.

 

Complex Type Vs Table Splitting

Seguramente se estén preguntando, porque simplemente no se usa un Tipo Complejo? definiendo en este las propiedades que se consideran especiales.

El tema pasa porque los Tipo Complejos no permiten la carga retardada (lazy load), mientras que la división de la tabla en varias entidades si lo permite.

Al recuperar una entidad los tipo complejo serán siempre devueltos, no hay control sobre esta entidad, mientras que una tabla dividida en entidades se le puede indicar cuando se necesita recuperar este bloque de datos.

 

Definición del modelo


El objetivo es permitir separar un conjunto de propiedades en una entidad distinta

image

a pesar de esta dividido en varias entidades todas son mapeadas a la misma tabla, por lo que resulta

image

 

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 virtual Localization Localization { get; set; }

    public virtual EmployeeExtended EmployeeExt { get; set; }

}

public class EmployeeExtended
{
    public int EmployeeID { get; set; }

    public byte[] Photo { get; set; }
    public string PhotoPath { get; set; }

    public string Notes { get; set; }
    
}

La entidad principal define una propiedad que referencia a la entidad extendida, a la vez que ambas entidades definen la misma propiedad como key, ya que por medio de esta se relacionan.

 

Definición del Mapping


Al definir el mapping ambas entidades persisten en la misma tabla, por eso el uso del ToTable(). También hacen uso de la misma key.

El HasRequired() permite indicar cual será la propiedad que representaran los datos extendidos en la entidad principal.

 

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        ToTable("Employees");
        HasKey(x => x.EmployeeID);

        Property(x => x.LastName)
            .HasMaxLength(20)
            .IsRequired();

        Property(x => x.FirstName)
            .HasMaxLength(10)
            .IsRequired();

        HasRequired(x => x.EmployeeExt)
                    .WithRequiredPrincipal();
    }
}

public class EmployeeExtendedMap : EntityTypeConfiguration<EmployeeExtended>
{
    public EmployeeExtendedMap()
    {
        ToTable("Employees");
        HasKey(x => x.EmployeeID);

        Property(x => x.Notes)
            .HasColumnType("ntext")
            .IsOptional();

        Property(x => x.Photo)
            .HasColumnType("image")
            .IsOptional();

        Property(x => x.PhotoPath)
            .HasColumnType("nvarchar")
            .HasMaxLength(255)
            .IsOptional();

    }
}

Definición del Repositorio


Hay cierta funcionalidad que requiere un tratamiento especial para estos casos es que se crea funcionalidad en el repositorio de le entidad empleado.

Se define esta en la interfaz, para luego implementarla en la clase concreta.

 

public interface IEmployeeRepository : IRepository<Employee>
{
    EmployeeExtended GetExtendedById(int id);

    void DeleteIncludeExtended(Employee entity);
}

 

public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
    /// <summary>
    /// Recupera solo la entidad Extendida
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public EmployeeExtended GetExtendedById(int id)
    {
        using (NorthWindContext context = new NorthWindContext())
        {
            return context.EmployeeExtendeds.FirstOrDefault(x => x.EmployeeID == id);
        }
    }

    /// <summary>
    /// Elimina la entidad incluyendo la informacion extendida 
    /// en caso de tenerla
    /// </summary>
    /// <param name="entity"></param>
    public void DeleteIncludeExtended(Employee entity)
    {
        using (NorthWindContext context = new NorthWindContext())
        {

            if (entity.EmployeeExt == null)
                entity.EmployeeExt = new EmployeeExtended() { EmployeeID = entity.EmployeeID };

            context.Employees.Attach(entity);
            context.Employees.Remove(entity);

            context.SaveChanges();

        }
    }

}

Estos método serán utilizados en los Test, donde se probara su funcionalidad

 

Configuración del proyecto de Test


Para probar la funcionalidad se requiere que un archivo que representa la foto que será asignada a la propiedad de la entidad. Este archivo forma parte del proyecto de Test, pero debe ser copiado a la carpeta de output.

Sera necesario definir un archivo de TestSetting el cual no ayudara a configurar el entorno de prueba, para ello agregamos este archivo usando la opción

image

 

SNAGHTML4800a637

En este configuramos las opciones de deploy para que copie el archivo de la imagen a la carpeta que defina el test cuando ejecute

SNAGHTML480139af

Asignamos cual será el archivo usado en la ejecución de los test

image

 

SNAGHTML47f6aaaa

 

Test – Eliminar empleado con y sin información extendida


Si eliminamos una entidad haciendo uso del método Delete() del repositorio base obtendremos un error

image

Se produce porque se requiere no solo marcar para eliminar la entidad principal sino también la extendida, por esta razón en el repositorio de la entidad empleado se creo un método especifico para este caso.

El método DeleteIncludeExtended() elimina la entidad y también define la entidad extendida a pesar que no se envíe como dato en la entidad que se asigna como parámetro.

 

[TestMethod]
public void Delete_Employee()
{
    EmployeeRepository repoEmployee = new EmployeeRepository();

    //
    //se crea un empleado con info extendida
    //
    CreateEmployeeWithPhoto();

    //elimina el empleado y su informacion extendida
    repoEmployee.DeleteIncludeExtended(new Employee() { EmployeeID = employeeNew.EmployeeID } );

    //se recupera el empleado que fue eliminado en el paso anterior
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);

    Assert.IsNull(employeeSel);


    //
    //se crean el empleados sin informacion extendida
    //
    Employee employee = new Employee()
    {
        FirstName = "name 1",
        LastName = "lastname 1",
        EmployeeExt = new EmployeeExtended()
    };
    repoEmployee.Create(employee);

    repoEmployee.DeleteIncludeExtended(employee);

    //se recupera el empleado que fue eliminado en el paso anterior
    employeeSel = repoEmployee.Single(x => x.EmployeeID == employee.EmployeeID);

    Assert.IsNull(employeeSel);

}

El test tiene dos partes, la primera crea un empleado con funcionalidad extendida y lo elimina usando el método que hemos creado en el repositorio para este propósito, la segunda parte crea una entidad sin información extendida y también elimina esta entidad, demostrando que sin importar la información con la cual se cree la entidad puede ser eliminada.

Como se vera para ambos casos es necesario definir el id de la entidad extendida para que sea marcada tanto la entidad principal como la extendida para ser eliminadas.

 

Test – Obtener todos los empleados


Se crean dos empleado, uno con información extendida y el otro sin ella.

En el test recupera la lista de empleados, pudiéndose analizarse como la información extendida no se obtiene, mientas que la propiedad que representa el complex type si es devuelta al recuperar la entidad.

Este caso es el típico que uno usaría para cargar un listado o grid con la información base de la entidad.

 

[TestMethod]
public void GetAll_Employee()
{
    string foto = Path.Combine(this.TestContext.DeploymentDirectory, "foto.jpg");

    EmployeeRepository repoEmployee = new EmployeeRepository();

    //se eliminan las entidades que pudieran quedar de la ejecucion de test anteriores
    repoEmployee.GetAll().ForEach(x => repoEmployee.DeleteIncludeExtended(x));


    //se crean dos empleados
    CreateEmployeeWithPhoto();

    Employee employee2 = new Employee()
    {
        FirstName = "name 2",
        LastName = "lastname 2",
        EmployeeExt = new EmployeeExtended()
    };
    repoEmployee.Create(employee2);


    List<Employee> list = repoEmployee.GetAll();

    Assert.AreEqual(list.Count, 2);

    Assert.IsNull(list[0].EmployeeExt);
    Assert.IsNotNull(list[0].Localization);
    Assert.IsTrue(list[0].Localization.HasValue);

    Assert.IsNull(list[1].EmployeeExt);
    Assert.IsNotNull(list[1].Localization);
}

Test – Obtener entidad SIN información extendida


Aunque se haya creado un empleado con información adicional al recuperar la entidad esta no forma parte de los datos.

 

[TestMethod]
public void GetById_Employee()
{
    //se crea el empleado con informacion extendida
    CreateEmployeeWithPhoto();


    //Se recupera la entidad
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);


    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsTrue(employeeSel.Localization.HasValue);
    Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
    Assert.AreEqual(employeeSel.Localization.City, "City 1");
    Assert.AreEqual(employeeSel.Localization.Country, "Country 1");

    Assert.IsNull(employeeSel.EmployeeExt);

}

La consulta recupera el empleado y deja evidencia de la inclusión de los campos que define el complex type pero no se incluyen los de la entidad extendida.

image

 

Test – Obtener entidad CON información extendida


En este caso el test define un “include” de la propiedad extendida, permitiendo recuperar la foto asignada al empleado

 

[TestMethod]
public void GetById_EmployeeExt()
{
    //se crea el empleado con informacion extendida
    CreateEmployeeWithPhoto();

    //se recupera la entidad y sus propiedad extendida
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID,
                                                    new List<Expression<Func<Employee, object>>>() 
                                                    { 
                                                        x=>x.EmployeeExt 
                                                    });
                        


    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsTrue(employeeSel.Localization.HasValue);
    Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
    Assert.AreEqual(employeeSel.Localization.City, "City 1");
    Assert.AreEqual(employeeSel.Localization.Country, "Country 1");

    Assert.IsNotNull(employeeSel.EmployeeExt);
    Assert.IsNotNull(employeeSel.EmployeeExt.Photo);
    Assert.AreEqual(employeeSel.EmployeeExt.PhotoPath, employeeNew.EmployeeExt.PhotoPath);

}

la consulta resultante es bastante mas compleja ya que debe unir los campos de la entidad extendida

image

Lo importante a destacar es que uno controla cuando es necesario recuperar los datos extendido para así mejorar la performance de la aplicación.

 

Test – Recuperar SOLO la información extendida


Si ya se dispone de la entidad con los datos básicos y se requiere solo los datos extendidos se podría recuperar únicamente estos.

 

[TestMethod]
public void GetById_Employee_RecoverOnlyExtended()
{

    CreateEmployeeWithPhoto();


    EmployeeExtended employeeSel = repoEmployee.GetExtendedById(employeeNew.EmployeeID);


    Assert.IsNotNull(employeeSel);
    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsNotNull(employeeSel.Photo);
    Assert.AreEqual(employeeSel.PhotoPath, employeeNew.EmployeeExt.PhotoPath);

}

El método GetExtendedById() hace uso de la propiedad del contexto que accede a esta entidad extendida de forma directa.

image

Es la misma consulta que recupera al empleado pero solo define los campos de la entidad extendida.

 

Documentación de referencia


 Associations in EF Code First: Part 4 – Table Splitting

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

 

[C#]