August 2009 - Artículos

En esta tercera entrega, que concluye el presente artículo sobre .NET RIA Services, veremos cómo dotar de mayor funcionalidad al servicio de dominio, añadiéndole nuevos métodos con los que enriquecer la interacción entre las capas cliente y servidora de nuestra aplicación. También explicaremos el modo de rellenar con valores el control DataGrid, objetivo principal del ejemplo.

Ordenando la lista de valores mediante LINQ

Posiblemente nos hayamos percatado de que al desplegar la lista de valores del ComboBox, estos se muestran sin un orden preestablecido, cuando lo más adecuado sería que estuvieran ordenados alfabéticamente.

Aunque la solución más sencilla sería modificar la vista de la base de datos para que devolviera el conjunto de resultados ya ordenado, en esta ocasión vamos a optar por la vía difícil, es decir, realizar la ordenación desde el código de la aplicación. Disponemos de dos técnicas para lograrlo.

La primera de estas técnicas pasa por manipular el objeto EntityQuery que devuelve el método GetPaisesPedidosQuery del contexto de dominio, aplicándole la ordenación mediante la cláusula orderby de LINQ, obteniendo de esta manera un nuevo objeto EntityQuery, que pasaremos como parámetro al método NorthwindContext.Load.

public partial class MainPage : UserControl
{
    NorthwindContext oNorthwindContext = new NorthwindContext();

    public MainPage()
    {
        InitializeComponent();

        EntityQuery<PaisesPedido> qryConsulta = from oPaisPedido
                                                in oNorthwindContext.GetPaisesPedidosQuery()
                                                orderby oPaisPedido.ShipCountry
                                                select oPaisPedido;

        LoadOperation<PaisesPedido> oLoadOperation = oNorthwindContext.Load(qryConsulta);
        this.cboPaises.ItemsSource = oLoadOperation.Entities;
    }
    //....
}

La siguiente imagen muestra la lista de elementos del ComboBox ordenados gracias a esta técnica.

La segunda técnica consiste en crear un nuevo método en el servicio de dominio, que devuelva la lista de entidades, ordenada mediante el método OrderBy de LINQ.

[EnableClientAccess()]
public class NorthwindService : LinqToSqlDomainService<NorthwindDataContext>
{
    //....
    public IQueryable<PaisesPedido> GetPaisesPedidosOrdenados()
    {
        return this.Context.PaisesPedidos.OrderBy(oPaisPedido => oPaisPedido.ShipCountry);
    }
}

Volveremos a compilar la solución, de forma que .NET RIA Services genere el código correspondiente para la capa cliente de la aplicación, y desde el constructor de la página Silverlight invocaremos a este nuevo método.

//....
public MainPage()
{
    InitializeComponent();

    oNorthwindContext.Load(oNorthwindContext.GetPaisesPedidosOrdenadosQuery());
    this.cboPaises.ItemsSource = oNorthwindContext.PaisesPedidos;
}

Carga de registros en el DataGrid

El siguiente paso consistirá en cargar en el DataGrid aquellos registros de la tabla Orders, cuyo campo ShipCountry sea igual al valor seleccionado en el ComboBox. Actualmente, el servicio de dominio sólo dispone del método GetOrders para obtener los valores de esta tabla, por lo que tendremos que añadir un nuevo método, al que daremos el nombre GetOrdersCiudadEntrega, que recibirá como parámetro una cadena con el nombre de la ciudad, que actuará como filtro en la devolución de los registros de la tabla. Al igual que en las pasadas ocasiones, emplearemos LINQ para aplicar la condición de filtro.

[EnableClientAccess()]
public class NorthwindService : LinqToSqlDomainService<NorthwindDataContext>
{
    //....
    public IQueryable<Order> GetOrdersCiudadEntrega(string sCiudadEntrega)
    {
        return this.Context.Orders.Where(oOrder => oOrder.ShipCountry == sCiudadEntrega);
    }
    //....

Después de generar nuevamente la solución, en el code-behind de la página Silverlight crearemos un manipulador para el evento SelectionChanged del ComboBox. Dentro del código de este manipulador, la primera acción será limpiar la colección de entidades Orders, ya que cada vez que hagamos una nueva carga de datos, la colección de valores que hubiera previamente no será eliminada de forma automática.

A continuación invocaremos al nuevo método que hemos creado para cargar los registros de Orders filtrados por ciudad. Debido a que la lista de elementos del ComboBox es una colección de objetos de tipo PaisesPedido, tendremos que hacer una operación de type-casting sobre el elemento seleccionado (ComboBox.SelectedItem), para obtener el valor de su propiedad ShipCountry.

Finalmente, asignaremos al DataGrid la colección de entidades de tipo Order, que se habrán cargado en la colección Orders del contexto de dominio.

//....
public MainPage()
{
    InitializeComponent();

    oNorthwindContext.Load(oNorthwindContext.GetPaisesPedidosOrdenadosQuery());
    this.cboPaises.ItemsSource = oNorthwindContext.PaisesPedidos;

    this.cboPaises.SelectionChanged += new SelectionChangedEventHandler(cboPaises_SelectionChanged);
}

void cboPaises_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    oNorthwindContext.Orders.Clear();
oNorthwindContext.Load(oNorthwindContext.GetOrdersCiudadEntregaQuery(((PaisesPedido)this.cboPaises.SelectedItem).ShipCountry));
    this.grdDatos.ItemsSource = oNorthwindContext.Orders;
}
//....

La siguiente imagen muestra el resultado obtenido. 

 

Visualizando la tabla Orders al completo en el DataGrid

Llegados al punto actual podríamos dar por concluido el ejemplo, pero existe un pequeño aspecto de la aplicación que podemos retocar para hacer que mejore su funcionalidad.

En su estado actual, nuestra aplicación permite seleccionar y presentar los registros de la tabla Orders, filtrados por el campo ShipCountry, pero es posible que en alguna ocasión también nos interese visualizar todos los registros de la tabla, lo que actualmente no es posible.

Para solucionar este pequeño inconveniente, vamos a eliminar de la base de datos la vista PaisesPedidos, creándola nuevamente con la siguiente sentencia.

CREATE VIEW PaisesPedidos
AS
SELECT DISTINCT ShipCountry FROM Orders
UNION
SELECT '----' AS ShipCountry

De esta forma, cuando el usuario seleccione el elemento del ComboBox que contiene los guiones, cargaremos en el DataGrid todos los registros de la tabla Orders usando el método GetOrders del servicio de dominio. El código que realiza la carga de los datos quedará a partir de ahora como vemos a continuación.

void cboPaises_SelectionChanged(object sender, SelectionChangedEventArgs e) { oNorthwindContext.Orders.Clear(); string sShipCountry = ((PaisesPedido)this.cboPaises.SelectedItem).ShipCountry; if (sShipCountry == "----") { oNorthwindContext.Load(oNorthwindContext.GetOrdersQuery()); } else {
oNorthwindContext.Load(oNorthwindContext.GetOrdersCiudadEntregaQuery(sShipCountry)); } this.grdDatos.ItemsSource = oNorthwindContext.Orders; }

A continuación podemos observar el DataGrid visualizando todos los registros de la tabla.

 

Y tras este retoque damos por finalizado este artículo donde nos hemos introducido en el acceso a datos desde Silverlight, utilizando .NET RIA Services. Espero que os resulte de interés y utilidad. Al igual que en las anteriores entregas, el proyecto de ejemplo está disponible en los enlaces C# y VB.

Un saludo. 

En la primera entrega de este artículo acabábamos de crear el servicio de dominio de la aplicación, con los métodos para el lado servidor de la misma, que se ocupaban de interactuar con la base de datos. Si en este momento volvemos a compilar la solución, Visual Studio arrojará un error relacionado con el servicio de dominio que acabamos de crear.

 

El motivo de dicho error radica en que la entidad PaisesPedido, perteneciente al contexto de datos, carece de una clave; sin embargo, el servicio de dominio requiere que todas las entidades que expone tengan al menos una clave definida. Este problema lo solucionaremos abriendo el diseñador del modelo de datos, y asignando a la propiedad Primary Key, de la entidad PaisesPedido, el valor True.

De esta forma, al volver a compilar la aplicación ya no se producirá este error.

DomainContext. El código generado por .NET RIA Services para la capa cliente

La siguiente fase en el desarrollo de nuestro ejemplo corresponde a la interfaz de usuario. Situándonos en el proyecto Silverlight de la solución, al hacer clic en el icono Show All Files de la ventana Solution Explorer veremos que la carpeta Generated_Code contiene un archivo llamado SeleccionComboBox.Web.g.cs, en cuyo interior se encuentra el código correspondiente a las clases generadas por el servicio de dominio, pero en esta ocasión para el lado cliente de la aplicación.

 

Este código recibe también el nombre de contexto de dominio (DomainContext) y en él podemos encontrar la clase que lo representa: NorthwindContext, que hereda de la clase base DomainContext.

Dentro de NorthwindContext hallaremos las versiones cliente de los métodos de consulta del servicio de dominio. El nombre de estos métodos será igual que el existente en el lado servidor de la aplicación, pero incluyendo el sufijo Query.

public sealed partial class NorthwindContext : DomainContext
{
    //....
    public EntityQuery<Order> GetOrdersQuery()
    {
        return base.CreateQuery<Order>("GetOrders", null, false, true);
    }
    
    public EntityQuery<PaisesPedido> GetPaisesPedidosQuery()
    {
        return base.CreateQuery<PaisesPedido>("GetPaisesPedidos", null, false, true);
    }
    //....    
}

Definiendo la interfaz de usuario

La primera acción a desempeñar en la construcción de nuestra interfaz de usuario Silverlight, consistirá en añadir los controles de datos necesarios en la página XAML; en concreto, un ComboBox y un DataGrid con las columnas de la tabla Orders que queramos visualizar.

<UserControl 
    x:Class="SeleccionComboBox.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
    <Grid x:Name="LayoutRoot">
        <StackPanel Background="LightGreen">
            <ComboBox Name="cboPaises" Width="200" DisplayMemberPath="ShipCountry" Margin="5" />
            <data:DataGrid Name="grdDatos" 
                           Width="600" Height="370" 
                           AutoGenerateColumns="False" IsReadOnly="True">
                <data:DataGrid.Columns>
                    <data:DataGridTextColumn Header="Pedido" Binding="{Binding OrderID}" />
                    <data:DataGridTextColumn Header="Cliente" Binding="{Binding CustomerID}" />
                    <data:DataGridTextColumn Header="Destinatario" Binding="{Binding ShipName}" />
                    <data:DataGridTextColumn Header="País entrega" Binding="{Binding ShipCountry}" />
                    <data:DataGridTextColumn Header="Ciudad entrega" Binding="{Binding ShipCity}" />
                </data:DataGrid.Columns>
            </data:DataGrid>
        </StackPanel>
    </Grid>
</UserControl>

Pasando seguidamente al code-behind de esta página, la primera operación que debemos realizar será durante la carga de la misma, y se basará en rellenar el ComboBox con la lista de países obtenidos de la vista PaisesPedidos. Para ello, crearemos una instancia del contexto de dominio (clase NorthwindContext) con un ámbito que sea accesible a todo el código de la página XAML. A continuación, en el constructor de la página, llamaremos al método Load del contexto de dominio, pasándole como parámetro el método GetPaisesPedidosQuery, también de este mismo contexto, que devuelve las entidades resultantes de ejecutar la vista de la base de datos. Finalmente, asignamos como fuente de datos para el ComboBox, la propiedad PaisesPedidos del contexto de dominio, que contendrá la colección de entidades, una vez que el método NorthwindContext.Load haya finalizado.

//....
using SeleccionComboBox.Web;

namespace SeleccionComboBox
{
    public partial class MainPage : UserControl
    {
        NorthwindContext oNorthwindContext = new NorthwindContext();

        public MainPage()
        {
            InitializeComponent();

            oNorthwindContext.Load(oNorthwindContext.GetPaisesPedidosQuery());
            this.cboPaises.ItemsSource = oNorthwindContext.PaisesPedidos;
        }
    }
}

En la siguiente imagen podemos ver el ComboBox en tiempo de ejecución, mostrando la lista de valores obtenidos de la base de datos.

¿Cuándo se completa la carga de entidades en el lado cliente?

Debemos tener en cuenta que la ejecución de NorthwindContext.Load se produce de modo asíncrono (que es el modo de ejecución en el que Silverlight trabaja con servicios), por lo que la asignación a la propiedad cboPaises.ItemsSource, no significa que el control ComboBox pueda mostrar inmediatamente la lista de valores.

Si deseamos saber el momento exacto en el que la operación de carga se ha completado, para así poder empezar a utilizar el ComboBox, debemos utilizar una técnica consistente en obtener, a partir del método DomainContext.Load, el tipo genérico LoadOperation<T> que dicho método devuelve, y que representa a la operación de carga de datos; creando a continuación un manipulador para el evento Completed del objeto LoadOperation. Lo primero que haremos será añadir en el código XAML un TextBlock, que utilizaremos para informar al usuario del momento a partir del cual puede usar el ComboBox.

<!--....-->
<StackPanel Background="LightGreen">
    <TextBlock Name="txtAviso" Text="" Margin="5" HorizontalAlignment="Center" />
    <ComboBox Name="cboPaises" Width="200" DisplayMemberPath="ShipCountry" Margin="5" />
    <!--....-->

A continuación, desde el code-behind de la página, recogeremos el valor devuelto por la llamada al método NorthwindContext.GetPaisesPedidosQuery: el objeto LoadOperation<PaisesPedido>. De este objeto crearemos un manipulador para su evento Completed, el cual, al ser ejecutado, asignará un mensaje de aviso al TextBlock de la interfaz de usuario.

//....
using SeleccionComboBox.Web;
using System.Windows.Ria.Data;

namespace SeleccionComboBox
{
    public partial class MainPage : UserControl
    {
        NorthwindContext oNorthwindContext = new NorthwindContext();

        public MainPage()
        {
            InitializeComponent();

            LoadOperation<PaisesPedido> oLoadOperation = oNorthwindContext.Load(oNorthwindContext.GetPaisesPedidosQuery());
            oLoadOperation.Completed += new EventHandler(oLoadOperation_Completed);
            this.cboPaises.ItemsSource = oLoadOperation.Entities;
        }

        void oLoadOperation_Completed(object sender, EventArgs e)
        {
            this.txtAviso.Text = "ComboBox Cargado!!!!";
        }
    }
}

Observe el lector, que para poder utilizar la clase LoadOperation<T>, debemos declarar en el archivo de código el espacio de nombres System.Windows.Ria.Data. Por otro lado, para asignar al ComboBox los valores a mostrar emplearemos la propiedad LoadOperation<T>.Entities, que será la que contenga la colección entidades una vez completado el proceso de carga.

Finalizamos aquí la segunda parte de este artículo. En la tercera y última entrega explicaremos la forma de ordenar la lista de elementos del ComboBox, así como la carga de datos en el DataGrid con los registros de la tabla Orders. El ejemplo completo está disponible en los siguientes enlaces: C# y VB.

Un saludo. 

El desarrollo de una aplicación Silverlight, además de las ventajas que ofrece en cuanto al tratamiento de video y gráficos se refiere, precisará, con toda probabilidad, de la implementación de una cierta cantidad de operaciones de manipulación contra un origen de datos, pongamos como ejemplo una base de datos SQL Server.

Los retos del desarrollo bajo el modelo RIA

Si aplicamos a nuestro desarrollo un enfoque basado en el modelo RIA, nos encontraremos con que tendremos que realizar un importante esfuerzo de codificación en la creación de un conjunto de servicios y clases, las cuales se ocupen del trasiego de datos entre la interfaz de usuario Silverlight y la fuente de datos situada en el lado servidor del diseño de nuestra aplicación.

Como factor adicional, debemos tener en cuenta que dadas las características de funcionamiento de Silverlight, todos los elementos que forman parte de esta maquinaria deberán de funcionar de modo asíncrono, lo cual introduce un nivel añadido de complejidad al desarrollo de la aplicación.

La llegada de .NET RIA Services a la arena del desarrollo Silverlight, sirve para mitigar la carga de trabajo que el desarrollador debe realizar, ya que se trata de un producto que se ocupa de generar automáticamente las clases y servicios necesarios para estas operaciones de mantenimiento de datos, a partir del modelo de datos que hayamos definido en nuestro proyecto.

Aplicando .NET RIA Services

El mejor modo de ilustrar el uso de .NET RIA Services pasa por realizar un proyecto de ejemplo, en el que paso a paso, podamos ver las fases principales de su creación. Antes de comenzar, resulta necesario resaltar que para trabajar con .NET RIA Services debemos tener instalado  Silverlight 3.

Pongamos como caso que necesitamos desarrollar una página Silverlight, en la que tenemos que visualizar, dentro de un DataGrid, algunas de las columnas de la tabla Orders (perteneciente a la base de datos Northwind), con la particularidad de que en lugar de mostrar todos los registros de la tabla, filtraremos por el campo ShipCountry, visualizando solamente aquellos que tengan un mismo valor para este campo, que previamente el usuario habrá seleccionado mediante un ComboBox, que contendrá la lista de posibles valores.

A la mencionada lista de valores del campo ShipCountry accederemos mediante una vista, que crearemos empleando la siguiente sentencia SQL.

CREATE VIEW PaisesPedidos
AS
SELECT DISTINCT ShipCountry FROM Orders

Una vez creada esta vista dentro de la base de datos Northwind, abriremos Visual Studio 2008 y crearemos un nuevo proyecto de tipo SilverlightApplication, al que daremos el nombre SeleccionComboBox, marcando además, la casilla Enable .NET RIA Services en el cuadro inicial de creación del proyecto.

 

Definición del modelo de datos de la aplicación

El primer paso a dar consistirá en la creación del modelo de datos de la aplicación, para lo cual nos situaremos en el proyecto Web (SeleccionComboBox.Web) de la solución recién creada,  seleccionaremos la opción de menú Project > Add New Item, y dentro de la categoría Data añadiremos un nuevo elemento de tipo LINQ to SQL Classes, al que daremos el nombre Northwind.dbml.

 

 A continuación, desde la ventana Server Explorer, crearemos una nueva conexión contra la base de datos Northwind, completando el diálogo de creación de conexión que se muestra al hacer clic en el icono Connect to Database

 Como resultado, la ventana Server Explorer mostrará la conexión recién creada a partir del nodo Data Connections. Desplegando los nodos correspondientes a las tablas y vistas de esta base de datos, arrastraremos hasta el área de diseño del modelo de datos la tabla Orders y la vista PaisesPedidos. 

 Compilando la solución se generará la clase NorthwindDataContext, que representa al contexto/modelo de datos actualmente reflejado en el diseñador; y las clases Order y PaisesPedido, que identifican respectivamente a la tabla y vista seleccionadas de la base de datos.

DomainService. Creando el servicio de dominio

El siguiente paso consiste en crear el servicio de dominio para la aplicación, por lo que, nuevamente, desde el proyecto Web de la solución, seleccionaremos la opción de menú Project > Add New Item, y esta vez, desde la categoría Web, elegiremos un elemento de tipo Domain Service Class, al que daremos el nombre NorthwindService.cs. 

 

Inmediatamente a continuación se abrirá un cuadro de diálogo para configurar el servicio de dominio, dentro del cual podremos elegir el contexto de datos (modelo de datos) con el que deseamos trabajar. En este ejemplo solamente disponemos del contexto de datos que acabamos de crear: NorthwindDataContext, que se mostrará de manera predeterminada.

Justo debajo del contexto de datos se muestran las entidades que lo componen, junto a una casilla para marcar aquellas con las que vamos a trabajar desde el lado cliente de la aplicación. Para que el código necesario sea generado en la parte cliente (proyecto Silverlight de la solución) deberemos marcar la casilla Enable client access.

Si queremos realizar operaciones de edición sobre las entidades, también podemos marcar la casilla Enable editing, aunque para la aplicación que estamos desarrollando no será necesario, ya que se trata de crear un ejemplo sencillo, basado exclusivamente en operaciones de consulta.

Tras aceptar este cuadro de diálogo generaremos la solución, que para el lado servidor de la aplicación (proyecto Web) tendrá como resultado la generación, por parte de .NET RIA Services, de la clase NorthwindService, derivada de LinqToSqlDomainService,  que representa al servicio de dominio. Esta última clase hereda a su vez de DomainService, clase base que contiene el funcionamiento esencial, común a todos los servicios de dominio.

La clase NorthwindService contendrá un método de consulta por cada una de las entidades existentes en el contexto de datos. La convención de notación seguida por .NET RIA Services para asignar los nombres a estos métodos se basa en utilizar el prefijo Get, seguido del nombre de la entidad. Cada uno de estos métodos devolverá una colección de entidades que representa a los registros de la tabla o vista de la base de datos, según el método que hayamos utilizado.

[EnableClientAccess()]
public class NorthwindService : LinqToSqlDomainService<NorthwindDataContext>

   public IQueryable<Order> GetOrders()
   {
       return this.Context.Orders;
   }
   public IQueryable<PaisesPedido> GetPaisesPedidos()
   {
       return this.Context.PaisesPedidos;
   }

Y llegados a este punto damos por concluida esta primera parte del artículo. En la próxima entrega abordaremos el código generado por .NET RIA Services a partir del servicio de dominio para la capa cliente de la aplicación y la creación de la interfaz de usuario. El ejemplo completo está disponible en los siguientes enlaces: C# y VB.

Un saludo.

Publicado por Luis Miguel Blanco | con no comments