martes, 31 de julio de 2012

GridView, mostrando datos en tiempo de diseño

Algo que puede resulta molesto a la hora de trabajar con el GridView es que no vemos el aspecto que éste tendrá hasta que no ejecutamos la aplicación. Esto puede suponer una gran perdida de tiempo si estamos en una fase en la que estamos "depurando" nuestra UI.

Para mostrar datos en el diseñador debemos declararlos en el XAML en vez a través de la propiedad DataContext. Esto es así porque el diseñador parsea el XAML pero no ejecuta el code-behind. Existen varias alternativas para conseguir esto, así que vamos a ver una de ellas. Lo primero que haremos será definir nuestro origen de datos
public class MenuModel
{
    public int IdMenu { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

public class MenuModelList : List<MenuModel>
{
    public MenuModelList()
    {
        this.Add(new MenuModel() { IdMenu = 1, Name = "Name 1", Description = "Description 1" });
        this.Add(new MenuModel() { IdMenu = 2, Name = "Name 2", Description = "Description 2" });
    }
}
Después declararemos este objeto como un recurso para que pueda referenciado por otros elementos de la siguiente manera
<Page.Resources>
    <local:MenuModelList x:Key="MenuListData"/>
    <CollectionViewSource x:Name="MenuListResource"
        Source="{StaticResource MenuListData}"/>        
</Page.Resources>
Y lo referenciaremos desde nuestro GridView de la siguiente forma
<GridView x:Name="GridViewMenu"
            HorizontalAlignment="Left" 
            Margin="19,70,0,0" 
            Grid.Row="1" 
            VerticalAlignment="Top" 
            SelectionMode="None"
            IsItemClickEnabled="True"                                                       
            ItemsSource="{Binding Source={StaticResource MenuListResource}}">

    <!-- Resto de información del GridView -->
</GridView>
De esta manera veremos nuestro GridView en tiempo de diseño de la siguiente manera

GridView en tiempo de diseño

El único inconveniente del modelo planteado anteriormente es que esos datos serán los mismos que se usarán cuando nuestra aplicación esté en ejecución. Si queremos usar otros datos en ejecución podemos optar por cambiar el origen de datos en el constructor de nuestro modelo en base a si estamos en tiempo de diseño o en tiempo de ejecución. Para conseguir esto podemos hacer los siguiente
public class MenuModelList : List<MenuModel> {
    public MenuModelList()
    {
        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            GetSampleData();
        else
            GetRealData();
    }

    private void GetSampleData()
    {
        this.Add(new MenuModel() { IdMenu = 1, Name = "Name 1", Description = "Description 1" });
        this.Add(new MenuModel() { IdMenu = 2, Name = "Name 2", Description = "Description 2" });
    }

    private void GetRealData()
    {
        this.Add(new MenuModel() { IdMenu = 1, Name = "Elemento 1", Description = "Descripción del elemento 1" });
        this.Add(new MenuModel() { IdMenu = 2, Name = "Elemento 2", Description = "Descripción del elemento 2" });
    }
}
Si ejecutamos ahora nuestra aplicaciones veremos que obtenemos otros datos distintos a los que obteníamos en tiempo de diseño.

GridView en tiempo de ejecución

Como vemos es relativamente fácil mostrar información en nuestros GridView en tiempo de ejecución lo cual hace que el diseño de nuestra aplicación sea más fácil de llevar a cabo porque evitaremos infinidad de ejecuciones innecesarias.

Happy coding!

viernes, 27 de julio de 2012

Diferencias entre DataContext y ItemsSource en Metro

Para aquellos que no tengamos demasiada experiencia en WPF o Silverlight el bindeo de datos puede ser un poco confuso teniendo en cuenta que parece que se puede hacer de dos maneras distintas, tanto a través de la propiedad DataContext como de la propiedad ItemsSource. Pese a que puedan parecer propiedades idénticas nada más lejos de la realidad ya que ambas propiedades tienen un propósito bien distinto.

DataContext es una propiedad general de todos los descendientes de FrameworkElement. Es heredada a de padres a hijos y puede ser usada como origen de datos para el DataBinding.

ItemsSource es una propiedad que identifica la generación de elementos de los controles que derivan de ItemsControl. Cuando establecemos esta propiedad a través de DataBinding o vía código el control generará los elementos internamente. Estableciendo la propiedad DataContext en un ItemsControls no provocaremos el mismo efecto.

Bien, pero como no sólo de teoría vive el desarrollador vamos a ver esto en un ejemplo. Imaginemos que tenemos esta clase
public class MenuModel
{
    public int IdMenu { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}
Y que inicializamos una lista de elementos de la siguiente manera
List<MenuModel> items = new List<MenuModel>();
items.Add(new MenuModel() { IdMenu = 1, Name = "Elemento 1", Description = "Descripción del elemento 1" });
items.Add(new MenuModel() { IdMenu = 2, Name = "Elemento 2", Description = "Descripción del elemento 2" });
Bien con esto podremos hacer lo siguiente sobre un GridView
GridViewMenu.ItemsSource = items;
O sea, aplicaremos como origen de datos la colección de elementos única y exclusivamente al GridView. El resultado será este

GridView

Bien, ahora cambiaremos la llamada a ItemsSource por DataContext y veremos que ocurre. Correcto, lo que ocurre es que no se ha generado el GridView y eso porque el ItemsSource si lanza la generación del control pero el DataContext no. Para que se genere el control debemos modificar el DataBind del GridView  de la siguiente manera
<GridView x:Name="GridViewMenu"
            HorizontalAlignment="Left"
            Margin="19,70,0,0"
            Grid.Row="1"
            VerticalAlignment="Top"
            SelectionMode="None"
            IsItemClickEnabled="True"
            ItemTemplate="{StaticResource itemTemplate}"
            ItemsSource="{Binding}">

Bien, pero con DataContext podemos hacer algunas cosas más como hemos comentado antes. Vamos a crear un modelo que tenga nuestra lista de elemento y un texto y vamos a ver como podemos hacer el bindeo de datos de una manera muy simple.
public class MainPageModel
{
    public string Titulo { get; set; }
    public List<MenuModel> Items { get; set; }

    public MainPageModel()
    {
        this.Titulo = "Mi título";

        Items = new List<MenuModel>();
        Items.Add(new MenuModel() { IdMenu = 1, Name = "Elemento 1", Description = "Descripción del elemento 1" });
        Items.Add(new MenuModel() { IdMenu = 2, Name = "Elemento 2", Description = "Descripción del elemento 2" });
    }
}
Como hemos dicho antes DataContext es una propiedad que se programa por toda la jerarquía de hijos, así que la asignaremos al DataContext de la página
this.DataContext = model;
Y en el diseñador pondremos también un TextBox de la siguiente manera
<TextBlock HorizontalAlignment="Left" Height="24" Margin="18,16,0,0" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Titulo}" FontSize="24" VerticalAlignment="Top" Width="339"/>
<GridView x:Name="GridViewMenu"
            HorizontalAlignment="Left"
            Margin="19,70,0,0"
            Grid.Row="1"
            VerticalAlignment="Top"
            SelectionMode="None"
            IsItemClickEnabled="True"
            ItemTemplate="{StaticResource itemTemplate}"
            ItemsSource="{Binding Items}">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
El resultado de esto es el siguiente

GridView

En lineas generales podemos decir que usaremos ItemsSource si solo vamos a enlazar un ItemsControl con un único origen de datos y si tenemos varios elementos que van consumir nuestro origen de datos usaremos DataContext.

Nota: En el fichero StandardStyles.xaml tengo definida la siguiente Template para definir es aspecto del GridView
<!-- DataTemplates -->
<DataTemplate x:Key="itemTemplate">
    <Grid Background="DarkGray" Width="250" Height="250">
        <StackPanel Orientation="Vertical" VerticalAlignment="Bottom" Margin="0,0,0,10">
            <TextBlock Text="{Binding Name}" 
                    FontSize="24"  Margin="10,0,0,0" 
                    TextTrimming="WordEllipsis" TextWrapping="Wrap" 
                    HorizontalAlignment="Left"/>
            <TextBlock Text="{Binding Description}" 
                    FontSize="12" Margin="10,0,0,0" 
                    TextTrimming="WordEllipsis" TextWrapping="Wrap" 
                    HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</DataTemplate>

Happy coding!

jueves, 26 de julio de 2012

Introducción a Knockout (VII) - La propiedad $index

El otro día estaba revisando la documentación de binding foreach de knockout cuando recordé que una de las novedades que había traído la versión 2.1.0 era la inclusión de la propiedad $index dentro del contexto foreach. Pensando que utilidad podría tener dentro de una aplicación real y que no fuera la de simplemente mostrar un índice ya que eso lo podemos tener usando <ol>.

Pensando un poco se me ocurrió un ejemplo donde nos podría ser útil esta nueva propiedad, así que supongamos que tenemos una colección de elementos de este tipo
public class Item
{
 public int IdItem { get; set; }
 public string Description { get; set; }
 public int Quantity { get; set; }
}
Esta colección de elementos se la tenemos que presentar al usuario y este debe escribir las cantidades que quiere para cada Item, y enviarla de nuevo al servidor. Se podría ver como un versión muy reducida de un carrito de la compra, donde tenemos los elementos que queremos comprar y podemos modificar la cantidad de elementos. Bien, para mostrar esta información y para simplificar el ejemplo he creado la siguiente modelo y acciones

Modelo
public class Model
{
 public List<Item> Items { get; set; }
}
Acciones
public ActionResult Index()
{
 Model model = new Model();
 FillModel(model);

 return View(model);
}

[HttpPost()]
public ActionResult Index(Model model)
{
 return RedirectToAction("Index");
}

private void FillModel(Model model)
{
 model.Items = new List<Item>();
 for (int i = 1; i <= 10; i++)
  model.Items.Add(new Item() { IdItem = i, Description = string.Format("Item{0}", i), Quantity = 0 });
}
Vista
<h2>Items</h2>
@using (Html.BeginForm())
{ 
 for(int i = 0; i < Model.Items.Count; i++)
 {
  <div>
   @Html.HiddenFor(m => Model.Items[i].IdItem)
   @Html.HiddenFor(m => Model.Items[i].Description)
   @Model.Items[i].Description
   @Html.TextBoxFor(m => Model.Items[i].Quantity)
  </div>
 }
 <input type="submit" value="Enviar" />
}
El aspecto de este formulario sería el siguiente

Vista

Bien, si modificamos alguna de las cantidades y le damos a enviar, obtendremos esto en la controladora

Post

Como vemos el valor introducido por el usuario (10)  ha sido "bindeado" en nuestro modelo sin problemas. Bien, ahora supongamos que queremos hacer lo mismo pero usando knockout. Para eso, debemos modificar ligeramente la vista de la siguiente manera.
@using (Html.BeginForm())
{ 
 <!-- ko foreach: items -->
 <div>
  <input type="hidden" data-bind="value: IdItem"  />
  <input type="hidden" data-bind="value: Description"  />
  <span data-bind="text: Description"></span>
  <input type="text" data-bind="value: Quantity"  />
 </div>
 <!-- /ko -->
 <input type="submit" value="Enviar" />
}

<script type="text/javascript">
 $(document).ready(function () {
  function ViewModel(model) {
   var self = this;

   self.items = ko.observableArray(model.Items);
  }
  var viewModel = new ViewModel(@Html.Raw(Json.Encode(Model)));
  ko.applyBindings(viewModel);
 });
</script>
Con esto veremos que nuestra vista es igual a la obtenida anteriormente, pero al enviar el form nos daremos cuenta que nuestro modelo no se "bindea" correctamente.


Si sabemos como funciona el bind de ASP.NET MVC veremos que este error es lógico ya que los elementos que estamos poniendo en nuestra vista no tienen el atributo name definido, pero además hay que tener en cuenta que estamos intentando "bindear" una colección por lo que tendremos que especificar el índice que ocupa el elemento en la colección. Para esto es para lo que viene de maravilla el uso de la propiedad $index dentro del contexto foreach (no es que antes no se pudiera hacer, sino que era más complicado). Con esto la vista quedaría de la siguiente manera
<!-- ko foreach: items -->
<div>
 <input type="hidden" data-bind="value: IdItem, attr: {name: 'Items['+$index()+'].IdItem'}"  />
 <input type="hidden" data-bind="value: Description, attr: {name: 'Items['+$index()+'].Description'}"  />
 <span data-bind="text: Description"></span>
 <input type="text" data-bind="value: Quantity, attr: {name: 'Items['+$index()+'].Quantity'}"  />
</div>
<!-- /ko -->
Esto generaría el siguiente código html que vemos que cumple con lo dicho anteriormente

Vista HTML

Y si hacemos el post veremos que el modelo se ha "bindeado" de nuevo correctamente



NOTA
Hay otra forma de generar la vista de una manera más "elegante" y evitando así tener que usar un bucle y teniendo a nuestra alcance el Intellisense es la siguiente. Creamos una página parcial (Partial Page) llamada Item.cshtml  (mismo nombre que la clase que contiene la colección) en el directorio View/Home/RenderPartials o Shared/RenderPartials y en ella pondremos lo siguiente
@model test.web.mvc.cs.Models.Item

<div>
 @Html.HiddenFor(m => m.IdItem)
 @Html.HiddenFor(m => m.Description)
 @Model.Description
 @Html.TextBoxFor(m => m.Quantity)
</div>
Luego en la vista tan solo deberemos poner lo siguiente
@using (Html.BeginForm())
{ 
 @Html.EditorFor(x => x.Items)
 <input type="submit" value="Enviar" />
}

Happy coding!

martes, 24 de julio de 2012

Controles comunes en aplicaciones Metro

Desde hace un par de días estoy iniciándome en el desarrollo de aplicaciones Metro para Windows 8. Una vez instalado Windows 8 Release Preview y el Visual Studio 2012 RC tenemos todo lo necesario para comenzar con el desarrollo de este tipo de aplicaciones.

Una vez arrancado el Visual Studio seleccionaremos la plantilla Windows Metro Style en el lenguaje que más nos guste tal y como se muestra en la imagen


Tras esto se nos creará un proyecto de éste tipo con el siguiente aspecto


En este primer artículo voy a hablar un poco de los controles que tenemos a nuestra disposición para desarrollar para Metro ya que la lista puede agobiar un poco al principio, o al menos esa fue mi primera impresión ya que siempre he trabajado para aplicaciones web o como mucho alguna aplicación de escritorio de tipo WinForms y nunca he hecho nada serio para WPF o Silverlight.

Controles más comunes
  • Border: Dibuja un borde, un fondo o ambos alrededor de un objeto.
<Border BorderBrush="Black" BorderThickness="1" 
        Height="120" Width="60">
    <StackPanel>
        <Rectangle Fill="Red"/>
        <Rectangle Fill="Green"/>
    </StackPanel>
</Border>
  • Button: Representa un control de tipo botón que interpreta un click del usuario.
<Button .../>
-or-
<Button>
    singleObject
</Button>
-or-
<Button ...>stringContent</Button>
  • CheckBox: Representa un control que el usuario puede marcar (check) o desmarcar (uncheck). Este control también puede tener un valor indeterminado.
<CheckBox .../>
-or-
<CheckBox>
  singleObject
</CheckBox>
-or-
<CheckBox>stringContent</CheckBox>
  • ComboBox: Control de selección que combina un textbox no editable y un drop-down list que permite al usuario seleccionar un elemento de una lista.
<ComboBox x:Name="ComboColores" Height="20" Width="150" ItemsSource="{Binding}" />
ObservableCollection<string> colores = new ObservableCollection<string>();
colores.Add("Rojo");
colores.Add("Verde");
colores.Add("Azul");
     
ComboColores.DataContext = colores;
  • FlipView: Representa un control para mostrar elementos, pero que muestra un elemento cada vez, y tiene un comportamiento de "flip" cuando se pasa de uno a otro.
Control FlipView usado para mostrar una galería de imágenes
  • Grid: Define un área flexible organizada en filas y columnas. Los elementos hijos son organizados en base a la cantidad de filas y columnas definidas.
  • GridView: Muestra un Grid horizontal de elementos de datos.
Metro GridView
  • Image: Control que muestra una imagen. La imagen puede estar en varios formatos (Joint Photographic Experts Group [JPEG], Portable Network Graphics [PNG], Bitmap [BMP], Graphics Interchange Format [GIF], Tagged Image File Format [TIFF], JPEG XR, icons [ICO]).
  • ListView: Muestra una lista de elementos de manera vertical.
Metro ListView
  • RadioButton: Le permite al usuario seleccionar una opción de un grupo de opciones.
  • StackPanel: Organiza una lista de elementos hijo en una única fila que puede ser orientada de manera vertical u horizontal.
  • <StackPanel x:Name="MyStackPanel">
      <TextBlock x:Name="TextFirstName" Text="Nombre" Width="75" HorizontalAlignment="Left"/>
      <TextBlock x:Name="TextLastName" Text="Apellidos" Width="75" HorizontalAlignment="Left"/>
      <TextBlock x:Name="TextAddress" Text="Dirección" Width="150" HorizontalAlignment="Left"/>
    </StackPanel>
  • TextBlock: Control que nos ayuda a mostrar una pequeña cantidad de texto.
<TextBlock ...>text</TextBlock>
-or-
<TextBlock>
  oneOrMoreInlineElements
</TextBlock>
-or-
<TextBlock .../>
Otros controles que nos pueden ser útiles
  • AppBar: Representa un control contenedor que contiene los componentes de interfaz de usuario para ejecutar acciones.
Metro AppBar
  • Canvas: Define un área en la que pueden colocarse explícitamente objetos, usando coordenadas que son relativas al "Canvas".
<Canvas Width="640" Height="480" >
    <Rectangle Canvas.Left="30" Canvas.Top="30" 
       Fill="Red" Width="200" Height="200" />
</Canvas>
  • Frame: Representa un control de contenido que soporta la navegación.
  • MediaElement: Representa un objeto que contiene audio, vídeo o ambos.
<MediaElement Source="Media/video1.mp4" AutoPlay="True" />
  • SemanticZoom: Representa un control con scroll que contiene dos vistas que tienen una relación semántica.
<SemanticZoom ...>
  <SemanticZoom.ZoomedOutView>
    zoomedOutViewContent
  </SemanticZoom.ZoomedOutView>
  <SemanticZoom.ZoomedInView>
    zoomedInViewContent
  </SemanticZoom.ZoomedInView>
</SemanticZoom>
Metro SemanticZoom
Metro SemanticZoom
  • Slider: Representa un control que permite al usuario seleccionar un valor dentro de un rango.
Metro Slider
Con esto acabamos la introducción a los controles que tenemos a nuestra disposición en la aplicaciones Metro. En los próximos artículos iremos viendo como utilizarlos dentro de nuestras aplicaciones.


Happy coding!

miércoles, 4 de julio de 2012

Entity Framework y el borrado de entidades (II) - usando lambda y árboles de expresión

Como ya explique en el artículo anterior la forma más natural de borrar un registro es a través de su clave primaria, aunque eso no siempre tiene porque ser así. Un par de ejemplos sobre esto puede ser
  • Borrar aquellos registros que tengan más de un año de antigüedad. Práctico en tablas de logs.
  • Borrar aquellos registros que tengan un estado determinado.
  • O simplemente querer borrar registros en base a una clave ajena, como por ejemplo, borrar todas los lineas de una factura.
En todos estos casos la solución planteada en el artículo anterior no nos valdría ya que asumimos que tan sólo vamos a borrar un registro.

Borrado de varias entidades
Basándonos en este ejemplo
public class Bar
{
  public int IdBar { get; set; }
  public string Description { get; set; }
  public int IdState { get; set; }
}
y queriendo borrar aquellos registros que estén en el estado 1 podríamos hacer lo siguiente
using (DataContext context = new DataContext())
{
  var list = context.Bar.Where(m => m.IdState == 1);
  foreach (Bar bar in list)
    context.Bar.Remove(bar);
  context.SaveChanges();
}
Si analizamos el código anterior veremos que para borrar n elementos, debemos hacer n + 1 accesos a la base de datos. Lanzaremos n consultas de borrado, y necesitaremos una más para recuperar los elementos a borrar.

Como últimamente me he vuelto un inconformista esta solución no me gusta porque para borrar 1000 elementos necesito hacer 1001 acceso a base de datos y eso me parece demasiada carga para un servidor, que en mi caso, ya anda demasiado justo de recursos.

La primera solución que se me ocurrió fue usar un método de este estilo
using (DataContext context = new DataContext())
{
  context.Database.ExecuteSqlCommand("DELETE FROM Bar WHERE IdState = @IdState", new 
object[] { new System.Data.SqlClient.SqlParameter("IdState", 1) });
}
Esta solución es bastante buena aunque me deja atado a una tabla y a un filtro en concreto, lo cual me obliga a estar estableciendo métodos por cada tabla donde desee borrar un conjunto de datos y si uso varios filtros a estar usando parámetros opcionales (que suelen afear el código bastante).

Para mi lo ideal sería en un sólo método poder resolver esta situación, y tras investigar un poco se puede hacer con algo de código extra. Dado que estoy usando Code First he creado un método Delete en el contexto con el siguiente aspecto
public void Delete<T>(Expression<Func<T, bool>> condition)
{
  string where = generateWhere(condition.Body);
  string query = String.Format("DELETE FROM {0} WHERE {1}", typeof(T).Name, where);
  ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreCommand(query);
}
De esta manera puedo pasar que entidad estoy intentado borrar y los filtros que voy a usar en el borrado. Antes de continuar me gustaría aclarar que transformar un árbol de expresion a una condición SQL no es una tarea fácil y esta llena de pequeño matices que puede que hagan que no sea rentable realizar el esfuerzo. En mi caso las condiciones de borrado son muy simples y la mejora de rendimiento (que veremos al final) justificaban el pequeño esfuerzo que ha llevado el desarrollo de este método. Pero ojo, hacer un LINQ Provider no es una tarea que se pueda considerar trivial.

Bien, con esto podríamos llamar a nuestro método de borrado de la siguiente manera
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1);
}
Si analizamos nuestra Expression de entrada vemos como tiene una propiedad Body donde está la/s condiciones que hemos pasado y que tenemos que transformar a una condición Where de SQL. En esta propiedad tenemos tanto la parte izquierda como la parte derecha de la condición, así como el operador que se está usando. En el método generateWhere analizaremos todas estas partes para devolver un Where válido.
private string generateWhere(dynamic operation)
{
  string left = generateToken(ExpressionTypeEnum.Left, operation.Left);
  string right = generateToken(ExpressionTypeEnum.Right, operation.Right);

  // TODO: Completar operaciones (por ahora con estas es suficiente)
  var ops = new Dictionary();
  ops.Add(ExpressionType.Equal, "=");
  ops.Add(ExpressionType.GreaterThan, ">");
  ops.Add(ExpressionType.GreaterThanOrEqual, ">=");
  ops.Add(ExpressionType.LessThan, "<");
  ops.Add(ExpressionType.LessThanOrEqual, "<=");

  ops.Add(ExpressionType.And, "AND");
  ops.Add(ExpressionType.AndAlso, "AND");
  ops.Add(ExpressionType.Or, "OR");
  ops.Add(ExpressionType.OrElse, "OR");

  return string.Format("{0} {1} {2}", left, ops[operation.NodeType], right);
}
En mi caso estoy tomando como base que la Expression es de tipo BinaryExpression aunque más exactamente es de tipo LogicalBinaryExpression (esto lo podemos comprobar en tiempo de ejecución). En otras expresiones este análisis de la expresión fallaría por no tener alomejor una propiedad Right.

Vemos que tanto para la parte izquierda como para la parte derecha llamamos al método generateToken que se encarga de generarnos una cadena con el valor contenido en esa parte, e incluso, de llamar recursivamente al método generateWhere en caso que estemos combinado filtros de esta manera
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1 || m.IdState == 2);
}
El método generateToken tendría el siguiente aspecto
private string generateToken(ExpressionTypeEnum type, dynamic operation)
{
  string token = string.Empty;
  if (operation is BinaryExpression)
    token = generateWhere(operation);
  else
    token = (type == ExpressionTypeEnum.Left ? operation.Member.Name : operation.Value.ToString());
   
  return token;
}
Como único detalle hay que tener en cuenta que dependiendo de que parte de la expresión estemos analizando extraeremos el token de una u otra propiedad.

Con esto ya tenemos nuestro pequeño "parser" listo para ser usado en un entorno controlado y con condiciones simples. He detectados otras expresiones que poco a poco iré añadiendo. Estas expresiones son
  • UnaryExpression. Para filtros de tipo m => !m.IsActive. Donde IsActive es de tipo boolean.
  • PropertyExpression: Para filtros de tipo m=> m.IsActive. Donde IsActive es de tipo boolean.
  • MethodBinaryExpression: Para filtros de tipo m=>m.Date < DateTime.Now. Donde como es lógico Date es de tipo DateTime.
Para ver la mejora de rendimiento he realizado un borrado iterativo y un borrado con esté método usando el siguiente código
Iterativo
using (DataContext context = new DataContext())
{
  var list = context.Bar.Where(m => m.IdState == 1);
  foreach (Bar bar in list)
    context.Bar.Remove(bar);
  context.SaveChanges(); 
}
Directo
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1);
}

He lanzado estos métodos borrado 10, 100, 1000 y 10000 elementos. Cada bloque lo he lanzado tres veces y he calculado la media para dar la duración de la duración. Con estas condiciones estos son los datos que he obtenido

Registros a borrar - Tiempo de ejecución en ms 

Conclusiones
Como vemos en la tabla para borrados de más de 1000 registros el borrado iterativo se puede vuelve un poco pesado, ya que nos lleva casi 4 segundos ejecutarlo (producto de tener que lanzar 1001 consultas a la base de datos), mientra que nuestro borrado directo no llega a los 12 ms. En condiciones más extremas (10.000 registros) el borrado iterativo llega a casi 2 minutos de ejecución mientras que el directo no nos pasa de un cuarto de segundo. Interesante, ¿verdad?

En la mayoría de los escenarios no merece la pena complicarnos la vida tanto ya que tan solo borraremos un par de registros y podemos optar por el método iterativo, ya que como vemos entre ambos métodos para 10 registros casi no tenemos diferencias de tiempos. Para borrados de más registros siempre podemos optar procedimientos almacenados o lanzar directamente nosotros la consultas desde nuestro programa, pero si queremos tener a nuestra disposición mucho filtros para borrar está opción nos puede valer.

Eso si, como ejercicio de programación ha estado realmente bien y me he permitido ver por encima como funcionan los árboles de expresión.
Happy coding!

martes, 3 de julio de 2012

Entity Framework y el borrado de entidades (I)

A la hora de borrar entidades en Entity Framework nos damos cuenta que podemos actuar de dos maneras diferentes sobre todo si tenemos en cuenta factores como el número de consultas que tenemos que lanzar contra nuestra base de datos.

Borrado de entidades por la clave primaria
Quizás es la forma más natural de realizar un borrado aunque no por ello es la única. Supongamos que tenemos la siguiente entidad POCO con la cual hemos montado nuestra base de datos a través de Code First.
public class Bar
{
  public int IdBar { get; set; }
  public string Description { get; set; }
  public int IdState { get; set; }
}
El código para borrar una entidad por su clave primera podría ser algo así
private void DeleteByID(int idBar)
{
  using (DataContext context = new DataContext()) {
    Bar bar = context.Bar.Where(m => m.IdBar == idBar).SingleOrDefault();
    context.Bar.Remove(bar);
    context.SaveChanges();
  }
}
¿Sencillo verdad? La verdad es que si, aunque si somos un poco curiosos o simplemente sabemos lo que estamos haciendo y usamos el SQL Server Profiler veremos que la ejecución de ese código tan simple genera estas dos consultas
exec sp_executesql N'SELECT TOP (2) 
[Extent1].[IdBar] AS [IdBar], 
[Extent1].[Description] AS [Description], 
[Extent1].[IdState] AS [IdState]
FROM [dbo].[Bar] AS [Extent1]
WHERE [Extent1].[IdBar] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=1
exec sp_executesql N'delete [dbo].[Bar]
where ([IdBar] = @0)',N'@0 int',@0=1
O sea, que estamos lanzando dos consultas contra nuestra base de datos para simplemente hacer un borrado. Esto desde mi punto de vista es un poco absurdo, aunque menos mal que con un pequeño truco podemos resolver este pequeño inconveniente. El truco consiste en adjuntar a nuestro contexto una entidad con la clave que queramos borrar, luego la borramos y grabamos los cambios. Con esto se lanzará la consulta de borrado con la información de la clave que deseamos borrar. El código para esto es el siguiente
private void DeleteByID(int idBar)
{
  using (DataContext context = new DataContext())
  {
    Bar bar = new Bar() { IdBar = idBar };
    context.Bar.Attach(bar);
    context.Bar.Remove(bar);
    context.SaveChanges();
  }
}
Si usamos el SQL Profiler con este método veremos que tan solo se ejecuta esta consulta
exec sp_executesql N'delete [dbo].[Bar]
where ([IdBar] = @0)',N'@0 int',@0=1
Como vemos, con un pequeño cambio hemos podido resolver de una manera fácil el problema de rendimiento que podríamos tener si hacemos muchos borrados en nuestra aplicación.

En el próximo artículo trataré el borrado de registros cuando no estamos borrando por la clave primaria y donde una vez más el número de accesos a la base de datos puede ser un factor muy importante a tener en cuenta.

Happy codding!