Mostrando entradas con la etiqueta C#. Mostrar todas las entradas
Mostrando entradas con la etiqueta C#. Mostrar todas las entradas

viernes, 25 de marzo de 2011

Tutorial de MVC3

Como sabrán hace poco tiempo fue liberada la versión 3 de ASP MVC, y ya se pueden encontrar muchos recursos, tutoriales, videos, etc. acerca de las bondades de MVC3. Un tutorial muy recomendado es el de Scott Hanselman: Getting started with MVC3.

Si bien este tutorial se encuentra online y disponible para toda la comunidad he decidido compilar el tutorial completo en un documento PDF imprimible y publicarlo para su descarga:

image

Pueden descargar este tutorial desde: http://www.avanic.com.ar/downloads/docs/getting_started_with_mvc3_cs.pdf

Espero lo disfruten!
Gus

lunes, 8 de noviembre de 2010

IsNullOrWhiteSpace, that’s the question!

Seguramente muchos ya lo han observado en C# 4.0. Existe una nueva función aplicable al tipo String: IsNullOrWhiteSpace()

A la vieja y conocida función IsNullOrEmpty() le faltaba chequear los espacios en blancos o otros caracteres no visibles.

Esta nueva función agrega justamente eso. Es decir, no solo verifica lo que indica su nombre, sino también cadenas vacías y otros caracteres no visibles que son irrelevantes en muchos procesos.

Si antes, para considerar los espacios en blanco, teníamos que escribir lo siguiente:

if (string.IsNullOrEmpty(value) || value.Trim() == string.Empty)

ahora es suficiente y más eficiente escribir:

if (string.IsNullOrWhiteSpace(value))

Hace algún tiempo había escrito un extension method que hacía más o menos esto. Pero ahora esta nueva función integrada, es bienvenida y tendré que jubilar mi extension method (o usar la nueva función internamente).

Debo reconocer que al principio me guié solo por el nombre y pensé que no consideraba las cadenas vacías. Quizás esperaba un nombre como:

IsNullOrEmptyOrWhiteSpaceOrAnotherInvisibleChar()

Pero creo que es mejor el nombre actual. A propósito, esto me hace recordar un viejo método disponible desde las primeras versiones del framework:

FormsAuthentication.HashPasswordForStoreInConfigFile()

Lo he usado muchas veces, pero nunca para guardarlo en un archivo de configuración ;-)

Me pregunto: que habrán estado pensando cuando bautizaron a este método?

Espero que sirva.
Gus

viernes, 3 de septiembre de 2010

La familia crece: Nuevo Visual Studio LightSwitch

Dicen que con esta nueva tool podremos crear rápidamente aplicaciones de negocio de calidad profesional sin tener que escribir mucho código. Podremos crear aplicaciones de escritorio o en la nube en muy poco tiempo. Podemos elegir C# o VB.NET, SQL Server, SQL Azure o SharePoint. También podemos crear aplicaciones SilverLight dentro o fuera del browser y exportar datos a Excel programando casi nada. Y cuando la aplicación crezca podemos personalizarla en Visual Studio Profesional o Standard o el que sea.

Estas cosas ya las escuché muchas veces, pero no dejan de llamar la atención. Hay que probarlo: http://www.microsoft.com/visualstudio/en-us/lightswitch 

Mas adelante les cuento mi experiencia.

Nos vemos!
~Gus

domingo, 8 de marzo de 2009

ADO.NET Entity Framework: The version of SQL Server in use does not support datatype 'datetime2'

Trabajando con ADO.NET EF, en mi entorno de desarrollo tengo SQL Server 2008 y en producción está SQL Server 2005. Al publicar mi aplicación, en algunas páginas obtengo el siguiente error:

The version of SQL Server in use does not support datatype 'datetime2'

SQL Server 2008 dispone de un nuevo tipo de datos Datetime2 que agrega soporte mejorado de fechas y horas.

Por ejemplo, Datetime2 soporta fechas y horas desde 0001-01-01 00:00:00 hasta 9999-12-31 23:59:59.9999999 (nanosegundos de precisión), mientras que el conocido Datetime solo acepta rangos desde 1753-01-01 00:00:00 hasta 9999-12-31 23-59:59.999 (solo milisegundos de precisión). Pueden leer más sobre esto en el siguiente enlace (en inglés): DATETIME2 vs DATETIME in SQL Server 2008

Para resolver el problema de incompatibilidad usando ADO.NET Entity Framework hay que abrir el archivo .edmx con el editor de XML y cambiar el valor del atributo ProviderManifestToken a 2005.

<edmx:Runtime>
<edmx:StorageModels>
   <Schema Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005">

Luego guardamos los cambios en el modelo y recompilamos la aplicación. Esto resolverá el error de versiones.

Algo importante a saber es que si posteriormente actualizamos el modelo en nuestro entorno de desarrollo el atributo ProviderManifestToken volverá a establecerse al valor 2008 y deberemos cambiarlo manualmente antes de publicar nuestra aplicación.

Quizás con una tarea postbuild podríamos hacer este cambio automáticamente. Si alguien se anima a crear una tarea como esta será bienvenida.

Espero que sirva.
Saludos, Gus

viernes, 6 de marzo de 2009

Paginación y Ordenamiento del lado servidor con GridView y ADO.NET Entity Framework

Esta vez quiero mostrarles como actualmente estoy implementando paginación y ordenamiento del lado servidor, usando GridView, ObjectDataSource y ADO.NET EF. Aunque de una forma muy similar también es aplicable a LINQ2SQL.

 

Creando los métodos en la capa de negocio o servicios

El primer paso consiste en crear en nuestra capa de negocios o servicios un par de métodos que luego serán invocados por el control ObjectDataSource.

Básicamente necesito 2 métodos para lograr mi objetivo:

  1. Un método que me devuelva la cantidad total de registros
  2. Y otro método que me devuelva los registros paginados

Para el ejemplo voy a crear en una clase CatalogService, un método FindProducts() y otro método FindProductsCount().

public IList<Product> FindProducts(int? code, string description, int? categoryID, string sortExpression, int startRows, int maxRows)
       {
           using (var db = new StoreEntities())
           {
               var query = from p in db.Products select p;

               query = FindProductsFilter(query, code, description, categoryID);
               query = FindProductsSort(query, sortExpression);

               return query.Skip(startRows).Take(maxRows).ToList();
           }
       }

Aquí vemos que FindProducts() llamá a 2 métodos FindProductsFilter() y FindProductsSort() que se encargan de aplicar los filtros necesarios y ordenar los registros, y luego usa los métodos Skip y Take para paginar los resultados.

private static IQueryable<Product> FindProductsFilter(IQueryable<Product> query, int? code, string description, int? categoryID)
{
    if (code.HasValue)
        query = query.Where(p => p.Code== code);

    if (!string.IsNullOrEmpty(description))
        query = query.Where(p => p.Description== description);

    if (categoryID.HasValue)
        query = query.Where(p => p.Category.ID== categoryID);

    return query;
}

private IQueryable<Product> FindProductsSort(IQueryable<Product> query, string sortExpression)
{
    if (string.IsNullOrEmpty(sortExpression))
        return query.OrderBy(p => p.Description);

    return query.OrderBy(sortExpression);
}

Es importante saber que el método OrderBy() no acepta un parámetro string para realizar el ordenamiento. Sin embargo, aquí estoy usando unas extensiones de LINQ conocidas como Dynamic Query para lograr este objetivo.

Pueden leer un artículo de Scot Guthrie traducido al español sobre Dynamic Query y descargar el código fuente.

Sin Dynamic Query, tendría que parsear el parámetro sortExpression y escribir un switch o multiples ifs para aplicar el OrderBy correspondiente según el campo. En pocas palabras, tendría que escribir muuucho más código, entonces prefiero Dynamic Query.

Y ahora el método FindProductsCount() que no hace otra cosa más que contabilizar el total de productos. Este método lo utiliza el ObjectDataSource y la grilla para poder calcular el total de páginas necesarias, sin tener que traer todos los registros para luego paginarlos.

public int FindProductsCount(int? code, string description, int? categoryID)
{
    using (var db = new StoreEntities())
    {
        var query = from p in db.Products select p;

        query = FindProductsFilter(query, code, description, categoryID);

        return query.Count();
    }
}

 

Consumiendo los métodos desde la UI

Ahora creo una página aspx con unos campos para filtrar, un botón y una grilla asociada a un ObjectDataSource para los resultados. El formulario se vería mas o menos así:

image

No deseo explicar cómo enlazar la grilla con un ObjectDataSource, solo bastaría con decir que debemos asociar al método FindProducts() creado anteriormente y asociar los parámetros del método con los controles de la página. Y luego hay que habilitar Paging y Sorting en la grilla.

Las propiedades establecidas para el ObjectDataSource aparecen en negrita:

image 

Las propiedades sortExpression, startRows y maxRows serán pobladas y pasadas al método FindProducts y/o FindProductsCount automáticamente.

La última pieza es escribir código para el botón Buscar:

protected void FindButton_Click(object sender, EventArgs e)
{
    if (!Page.IsValid) return;

    try
    {

    ProductsGridView.DataSourceID = ProductsDataSource.ID;
    ProductsGridView.DataBind();
}
catch (Exception ex)
{
    ShowError(ex.Message);
}

}

Debe observarse que solo en runtime estoy enlazando la grilla con el control ObjectDataSource. Es decir, después de configurar la grilla y el ObjectDataSource, en tiempo de diseño, quito la referencia al ObjectDataSource en la propiedad DataSourceID de la grilla. De esta forma, cuando la página se carga no se hace una búsqueda automática, sino solo cuando se completan los filtros de búsqueda y se presiona el botón Buscar.

Pero esto es una particularidad que quise implementar y no tiene ningún efecto sobre la paginación y ordenamiento, motivo de este post.

Bueno creo que ya está todo, ahora hay que probar.

image

A mí me funcionó de maravillas ;-).

Mirando con el SQL Profiler veo que solo se consultan 10 registros por página. Las consultas al SQL Server devuelven solo los registros que el usuario quiere ver. Esto permite que nuestras aplicaciones sean mucho más escalables, pues solo traemos los registros necesarios y se minimiza la carga de trabajo en la BD.

Bueno espero que sirva.
Comentarios son bienvenidos!

Saludos, Gus

jueves, 29 de enero de 2009

ADO.NET Entity Framework: ¿Cómo actualizar un objeto desconectado?

Un escenario común es el siguiente:

"Al enviar un formulario web, creo una entidad Pedido con los datos proporcionados por el usuario y lo entrego a mi capa de negocios o servicios para que se valide y persista en el almacén de datos."

En este escenario la entidad Pedido enviada desde la página web (o enviada desde un Web Service), se encuentra en estado Deattached. Para que podamos persistir sus cambios necesitamos asociarla a un contexto y luego llamar al método SaveChanges().

Si la entidad aún no existe en el almacén de datos, debemos agregarla. Esto es muy simple, usando los métodos AddTo* generados en el modelo:

context.AddToOrders(order);
context.SaveChanges();

En cambio si la entidad ya existe en el almacén de datos, entonces debemos asociarla al contexto y guardar los cambios. Esta tarea parece ser simple y podríamos intentar resolverla de la siguiente manera:

context.AttachTo("Orders", order);
context.SaveChanges();

Sin embargo, esto no funciona, pues el objeto order se asocia exitosamente al contexto pero en el estado Unchanged. Luego el método SaveChanges() no persiste ningún cambio.

La forma de resolver este problema es recuperando la entidad original desde el almacén de datos, aplicar los cambios, y volver a guardarla.

Buscando en la web encontré diferentes enfoques para resolver este problema. Muchos utilizan Reflection para recorrer todas las propiedades y asignar las que han cambiado. La que más me ha interesado la encontré en el blog de Cesar de la Torre.

Se trata de un extension method llamado AttachUpdated() aplicado al ObjectContext. Me he tomado el atrevimiento de renombrar le método AttacheUpdated() por UpdateObject(), pues me pareció más consistente con otros métodos que ya existen en el ObjectContext como AddObject() y DeleteObject(). El código es el siguiente:

public static void UpdateObject(this ObjectContext context, string entitySetName, EntityObject entity)
{
    var key = context.CreateEntityKey(entitySetName, entity);

    object originalEntity;
    if (context.TryGetObjectByKey(key, out originalEntity))
    {
        context.ApplyPropertyChanges(entitySetName, entity);
        context.ApplyReferencePropertyChanges(entity, (IEntityWithRelationships)originalEntity);
    }
    else
    {
        throw new ObjectNotFoundException();
    }
}

Este método primero crea la EntityKey asociada al objeto usando el método CreateEntityKey() y luego intenta recuperar el objeto desde el almacén de datos usando el método TryGetObjectByKey(). A continuación aplica los cambios usando el método ApplyPropertyChanges() y luego llama al método ApplyReferencePropertyChanges() que se encarga de aplicar los cambios a las entidades relacionadas. Este último método se trata de otro extension method:

private static void ApplyReferencePropertyChanges(this ObjectContext context, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
{
    foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
    {
        var oldRef = relatedEnd as EntityReference;

        if (oldRef != null)
        {
            var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
            if (newRef != null) oldRef.EntityKey = newRef.EntityKey;
        }
    }
}

Aquí no solo se resuelve el problema de aplicar los cambios a las propiedades del objeto en cuestión sino también los cambios en sus objetos relacionados.

Luego de crear estos extensions methods, el código final para actualizar una entidad desconectada quedaría como:

context.UpdateObject("Orders", order); //Extension method
context.SaveChanges();

He probado esto y funcionó para mí. Espero que también les sea de utilidad.

Saludos, Gus

miércoles, 28 de enero de 2009

ADO.NET Entity Framework: ¿Cómo cambiar la ForeignKey de un objeto?

Tengo un formulario de edición de un Producto, y entre muchos otros datos, se debe seleccionar, por ejemplo, la Categoría desde un DropDownList. Al guardar el producto, se debe establecer el ID de la categoría asociada.

La intuición nos llevaría a escribir el siguiente código:

product.Category.Id = Convert.ToInt32(CategoryDropDownList.SelectedValue);

Desafortunadamente, esto provocará un error, porque la propiedad Category es null, o bien porque no se puede cambiar la propiedad Id.

Para resolver el problema podemos escribir lo siguiente:

var categoryId = Convert.ToInt32(CategoryDropDownList.SelectedValue);
product.CategoryReference.EntityKey = new EntityKey("ContosoEntities.Categories", "Id", categoryId);

En lugar de la propiedad Category estamos usando la propiedad CategoryReference y estamos asignando una nueva EntityKey. Esto permitirá que cuando ejecutemos el método SaveChanges, los cambios de la categoría asociada al producto se persistan en el almacén de datos.

Particularmente esta forma de resolver el problema me parece poco intuitiva y elegante. Algo tan simple como cambiar una clave externa implica escribir todo ese código. Sin embargo, hasta ahora, parece ser la única forma de resolver el problema.

Si nos interesa mejorar un poco nuestro código, y escribir algo más simple, podríamos crear una clase Partial de Product, y agregar una propiedad CategoryId:

public int CategoryId
{
   get
   {
       return Convert.ToInt32(CategoryReference.EntityKey.EntityKeyValues[0].Value);
   }

   set
   {
       var qualifiedEntitySet = "ContosoEntities.Categories";
       CategoryReference.EntityKey = new EntityKey(qualifiedEntitySet, "Id", value);
   }
}

Esta propiedad adicional, permitirá escribir algo como:

product.CategoryId = Convert.ToInt32(CategoryDropDownList.SelectedValue);

Muy similar a los que habíamos intentado al principio!

Antes de terminar, alguien podrá decir:

"Usando LINQ TO SQL, estas propiedades se generan automáticamente cuando se crea el modelo."

Es cierto! Pueden dirigir sus comentarios y sugerencias al Blog del Equipo de ADO.NET Entity Framework, y quizás tengamos suerte para la siguiente versión.

Hasta la próxima! Espero que sirva.
Saludos, Gus

lunes, 26 de enero de 2009

ADO.NET Entity Framework: ¿Cómo incluir los objetos relacionados?

Una tarea común en el desarrollo con ADO.NET Entity Framework es escribir una consulta que incluya una o más entidades relacionadas. Por ejemplo, quiero obtener todas las fotos de un autor incluyendo los datos del autor:

var photos = from p in db.Photos.Include("Author")
             where (p.AuthorId == authorId)
             select p;

El método Include especifica que se incluya la entidad Author asociada a cada Photo.

Si además del autor, quisiera incluir otra entidad relacionada a la foto como su Categoría, podría agregar otro include:

var photos = from p in db.Photos.Include("Author").Include("Category")
             where (p.AuthorId == authorId)
             select p;

En este caso se incluirá también la categoría asociada a cada Photo.

Otro método que también permite cargar entidades relacionadas es el método Load(). Supongamos que deseo obtener los datos de una foto y sus palabras claves asociadas, podría escribir algo como:

var photo = db.Photos.First(p => p.Id == photoId);
photo.Tags.Load();

En otro escenario, podría interesarme verificar primero, antes de ejecutar el método Load():

var photo = service.GetPhoto(id);
if (!photo.Tags.IsLoaded) photo.Tags.Load();

La propiedad IsLoaded permite comprobar si se han cargado o no los objetos relacionados en la colección de Tags.

Para simplificar este trabajo, he creado un extension method EnsureLoad() que aplica a EntityCollection, así el código anterior se reduce a:

var photo = service.GetPhoto(id);
photo.Tags.EnsureLoad();

El código del extension method es el siguiente:

public static void EnsureLoad<T>(this EntityCollection<T> entityCollection)
where T: EntityObject
{
    if (!entityCollection.IsLoaded)
    {
        entityCollection.Load();
    }
}

Bueno, espero que sirva.
Saludos, Gus

martes, 20 de enero de 2009

Control Extensions

Continuando con los mini-aportes sobre extensions methods, en esta oportunidad quiero compartir algunos que simplifican la carga de datos en controles de lista como DropDownList, CheckBoxList o RadioButtonList.

El código habitual para llenar una lista desplegable con datos obtenidos desde algún origen de datos es el siguiente:

var countries = GlobalServices.GetCountries();
CountryDropDownList.DataValueField = "Id";
CountryDropDownList.DataTextField = "Name";
CountryDropDownList.DataSource = countries;
CountryDropDownList.DataBind();

Habitualmente solemos agregar un item más al principio que diga, por ejemplo: (Seleccione). Entonces, al código anterior podemos agregar:

CountryDropDownList.Items.Insert(0, new ListItem("(Seleccione)", String.Empty)); 

Si bien esto no es muy complejo, se vuelve tedioso cuando hay que repetirlo para llenar otras listas adicionales. Un simple extension method puede reducir todo esto a solo 1 línea de código:

CountryDropDownList.Fill(countries, "Id", "Name");

Si quisieramos agregar el item adicional de (Seleccione), podríamos usar una sobrecarga de este método:

CountryDropDownList.Fill(countries, "Id", "Name", "(Seleccione)");

El código del extension method Fill() es muy simple:

public static void Fill(this ListControl list, object dataSource, string valueField, string textField) 
{
if (list == null) return;

list.DataValueField = valueField;
list.DataTextField = textField;
list.DataSource = dataSource;
list.DataBind();
}

Como se puede observar, el extension method se aplica a los controles de tipo ListControl, esto incluye, además del control DropDownList, los controles CheckBoxList, RadioButtonList, BulletedList, y cualquier otro control que herede de ListControl.

Es un pequeño aporte que puede reducir, de una forma sencilla y elegante, una cantidad considerable de código y el consecuente ahorro de tiempo.

Pueden descargar la clase ControlExtensions.cs con estos métodos desde el siguiente link. Más adelante intentaré agregar nuevos métodos aplicables a otros controles.

Espero que sirva.

Saludos, Gus

lunes, 15 de diciembre de 2008

Propiedades y métodos públicos en la MasterPage

Este truco ya es un poco viejo, sin embargo, aún puede ser útil para muchos.

En muchas situaciones deseamos exponer propiedades y métodos públicos en la página maestra o página principal para accederlas desde las páginas de contenido. Sin embargo, al hacerlo vemos que no son accesibles. No aparecen en la lista de Intellisense.

Existe una directiva poco conocida llamada MasterType que podemos agregar fácilmente en nuestras páginas de contenido y así poder acceder a las propiedades y métodos públicos definidos en nuestra página maestra.

image

Una vez definida esta directiva podemos acceder en el código por detrás de nuestras páginas de contenido, a las propiedades y métodos públicos de la MasterPage.

image

Enjoy it!

¿Cómo recuperar el objeto DataItem del control ListView?

Cada elemento o fila de los controles de datos como GridView, DataList o Repeater disponen de una propiedad DataItem que permite recuperar el registro de datos enlazado a la lista. Esta propiedad es muy útil para procesar los elementos y controles durante el evento de enlace de datos.

Sin embargo, al trabajar con el control ListView incluído en ASP.NET 3.5 habrán notado que no existe una propiedad similar o equivalente para recuperar los datos del ítem enlazado para procesarlos durante el evento ItemDataBound.

Investigando un poco sobre cómo resolver esta ausencia, encontré una forma poco intuitiva:

image

El truco es que el elemento e.Item de tipo ListViewItem se puede castear a ListViewDataItem y entonces ahí podemos acceder a la propiedad DataItem para luego castearlo nuevamente a la clase de datos que estamos enlazando.

Este doble casting no me agrada mucho, pero es la única forma que encontré para recuperar el elemento de datos que está siendo enlazado al control ListView.

Bueno, espero que sirva y ahorre algunos dolores de cabeza.

Saludos, Gus

domingo, 30 de noviembre de 2008

String Extensions

Como sabemos, los Extensions Methods son una de las nuevas características de C# 3.0. Podemos verlos como métodos estáticos que permiten agregar nuevas funcionalidades a clases ya existentes. No es mi intención explicar cómo crear Extensions Methods (pueden aprender más aquí y aquí), sino compartir varios extensions methods que uso habitualmente en mis proyectos de desarrollo.

Convirtiendo Strings

Convertir cadenas de texto a un número o fecha es una necesidad habitual en todos los proyectos. El código típico es el siguiente:

int number = int.Parse(NumberTextBox.Text);
DateTime date = DateTime.Parse(DateTextBox.Text);

El mismo resultado podríamos obtener usando la clase Convert:

int number = Convert.ToInt32(NumberTextBox.Text);
DateTime date = Convert.ToDateTime(DateTextBox.Text);

En este caso tenemos la ventaja de que si el argumento es null, el valor devuelto es cero. Esto puede comprobarse con Reflector.

image

Ninguno de los métodos anteriores resuelve el problema de conversión de datos cuando el usuario ingresa letras donde se esperaba números o fechas con formatos no válidos.

image

Para evitar esta situación podemos usar validators de ASP.NET, controles de máscara o bien el método TryParse:

int number;
if (int.TryParse(NumberTextBox.Text, out number))
{
    //TODO: Do something
}

El único problema con esta solución es que implica escribir mucho más código, generando rutinas más extensas y difíciles de leer.

La situación se complica un poco más con los Guid, pues no disponen de un método TryParse, entonces habría que usar Try/Catch, lo que complica aún más el código resultante.

String Extensions

Es aquí donde los Extensions Métodos pueden proporcionar una solución efectiva, elegante e intuitiva. La siguiente figura muestra cómo convertir una cadena a un número usando un extension method:

image 

La implementación del extension method es muy simple:

image

He creado una clase StringExtensions.cs con varios extensions methods para convertir a byte, short, long, float, double, decimal, boolean, datetime, guid, etc. Incluí también versiones compatibles con tipos nullables. Seguiré agregando nuevos métodos en la medida que encuentre aplicación práctica y aporten simplicidad en el código.

A continuación les dejo el link para descargar la clase StringExtensions.cs. En próximos post, publicaré otros extensions methods aplicables a controles de ASP.NET. Espero que sirva.

Saludos, Gus