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

27 de diciembre de 2012

Problemas de performance con relaciones lazy en Entity Framework

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/12/27/entity-framework-performance-issues-with-lazy-relationships/

Durante las últimas semanas estuve trabajando en una aplicación que usa Entity Framework (code-first) y tuve algunos problemas de performance que pensé que podría ser útil compartirlos.

Luego de implementar algunos features en la aplicación noté ciertos problemas de performance, así que decidí agregar MiniProfiler y ver qué estaba pasando bajo el capó. La causa de los problemas de performance era que EF estaba ejecutando muchas queries innecesarias (o al menos eso pensaba) contra la BD. Así que, luego de investigar un poco y hacer algunas preguntas, descubrí que EF tiene una forma muy particular de manejar relaciones lazy "muchos a uno".

Déjenme explicar el problema con un ejemplo, supongamos que tenemos el siguiente modelo de dominio:

public class Order
{
    public Guid Id { get; set; }
 
    public string Description { get; set; }
 
    public virtual Customer Customer { get; set; }
}
 
public class Customer
{
    public Guid Id { get; set; }
 
    public string Name { get; set; }
}

Mapeado a las siguientes tablas:

 

En este modelo, si necesito acceder al ID del customer desde una order, es necesario pasar a través de la propiedad Customer, por ejemplo order.Customer.Id

El problema con esto es que EF va a cargar el objeto Customer desde la BD en cuanto tratemos de acceder a cualquier propiedad del objeto Customer. Ya que todo lo que necesitamos es el ID del customer, uno podría asumir que EF podría obtenerlo desde la FK Customer_Id en la tabla Orders sin necesidad de ejecutar queries extra contra la BD, pero desafortunadamente este no es el caso ...

Código como este:

foreach (var order in context.Orders)
{
    Console.WriteLine("Order {0}, Customer {1}", order.Description, order.Customer.Id);
}

Terminará ejecutando una gran cantidad de queries contra la BD, sólo para recuperar el ID del customer:

SELECT 
1 AS [C1], 
[Extent1].[Id] AS [Id], 
[Extent1].[Description] AS [Description], 
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [dbo].[Orders] AS [Extent1]

exec sp_executesql N'SELECT 
[Extent2].[Id] AS [Id], 
[Extent2].[Name] AS [Name]
FROM  [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE ([Extent1].[Customer_Id] IS NOT NULL) AND 
    ([Extent1].[Id] = @EntityKeyValue1)',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='FF947EF3-5A3F-4A26-BDB9-039C49F559A7'

exec sp_executesql N'SELECT 
[Extent2].[Id] AS [Id], 
[Extent2].[Name] AS [Name]
FROM  [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE ([Extent1].[Customer_Id] IS NOT NULL) AND 
    ([Extent1].[Id] = @EntityKeyValue1)',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='BDC1430B-A46B-4486-A946-2F3DAC3B69F5'

// ... and so on ...


Como en el pasado he estado trabajando con NHibernate (quien maneja este caso como es esperado - sin ejecutar queries innecesarias), para mí fue muy sorprendente encontrarme con este comportamiento.

Luego de investigar un poco encontré que la estrategia recomendada para estos casos es agregar una propiedad FK en el modelo de dominio.

public class Order
{
    public Guid Id { get; set; }
 
    public string Description { get; set; }
 
    public Guid CustomerID { get; set; }
 
    [ForeignKey("CustomerID")]
    public virtual Customer Customer { get; set; }
}

Realmente no es algo que me guste ya que el modelo de dominio se va a contaminar con propiedades que no deberían estar allí, pero mientras tanto no encuentre una forma mas elegante de solucionar este problema, creo que voy a tener que vivir con esto ;)

Aquí pueden encontrar una aplicación simple de consola que compara los diferentes comportamientos de Entity Framework y NHibernate en cuanto al manejo de relaciones lazy "muchos a uno"

1 de febrero de 2012

Cómo construir una interfaz fluent básica en 8 pasos

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/02/01/how-to-build-a-basic-fluent-interface-in-8-steps/

Aclaración: el objetivo de este post no es mostrar una lista completa de las diferentes técnicas existentes para escribir una API fluent ya que en la web se puede encontrar mucha información al respecto. Si estás interesado en el tema, te recomiendo que leas el libro de Martin Fowler Domain-Specific Languages.

Ahora que me saqué ese peso de encima, empecemos a escribir una interfaz fluent.

Imaginemos que estamos desarrollando una API para publicar blog posts en múltiples plataformas (Blogger, WordPress, TypePad, etc.), es muy probable que nuestro (super simplificado) modelo del dominio sea algo similar a esto:



Entonces, crear un blog post debería ser algo así:

var post = new BlogPost();
post.Title = "How to build a basic fluent interface in 8 steps";
post.Body = "...";
var author = new Author(); // We should check the authors repository
                           // before creating a new one
author.Name = "John Doe";
author.Email = "johndoe@email.com";
author.Twitter = "@johndoe";
post.Author = author;
post.Tags = "Fluent API, Internal DSL";

Nuestro modelo del dominio es muy sencillo y el código necesario para crear un blog post es fácil de leer y escribir asi que, en este caso implementar una interfaz fluent podría ser un tanto innecesario; pero... es perfecto para que podamos aprender a escribir una API fluent. Comenzamos?
 
Paso 1 - Escribiendo un borrador de la interfaz

Escribí (en notepad) un borrador de la interfaz fluent mostrando cómo querés que se vea (no pienses demasiado acerca de como vamos a implementar la interfaz, sólo jugá un poco con las ideas)

Esto es lo que escribí yo:

var post = Post
  .Title("How to build a basic fluent interface in 8 steps")
  .Body("...")
  .Author()
    .Name("John Doe")
    .Email("johndoe@email.com")
    .Twitter("@johndoe")
  .Tags("Fluent API, Internal DSL");

Paso 2 - Usando un enfoque "test-first"

Una vez que encontraste una opción que te satisface, pasala a un test en Visual Studio y agregá el código que será necesario para validar el resultado generado por la interfaz fluent. Dependiendo del tamaño de la API, quizás sea recomendable hacer esto gradualmente. En este caso vamos a dejar de lado, por ahora, la parte del Autor.

En el siguiente código estamos creando dos instancias de BlogPost, la primera usando la API standard y la segunda usando nuestra nueva API fluent. Luego de que ambas instancias son creadas, se verifica su equivalencia usando el método CheckEquivalence. Este método debe verificar si ambas instancias de BlogPost tienen el mismo título, cuerpo y tags.
[TestCase]
public void ConfiguringPostWithoutAuthor()
{
    var expected = new BlogPost();
    expected.Title = "How to build a basic fluent interface in 8 steps";
    expected.Body = "...";
    expected.Tags = "Fluent API, Internal DSL";

    var post = Post
        .Title("How to build a basic fluent interface in 8 steps")
        .Body("...")
        .Tags("Fluent API, Internal DSL");
    BlogPost actual = post.Build();

    Assert.IsTrue(TestsHelper.CheckEquivalence(expected, actual));
}

Este test, por supuesto, aún ni compila.
 
Paso 3 - Comenzando a implementar la interfaz

Ahora, comencemos a implementar la clase Post. Observar que el método Title es estático para que pueda ser invocado sin necesidad de crear una instancia de la clase Post.
public class Post
{
    public static Post Title(string title)
    {
        var post = new Post();
        post.TitleValue = title;
        return post;
    }
}

Paso 4 - Agregando más métodos a la interfaz fluent

Una vez que tenemos nuestro punto de partida, continuemos agregando el resto de los métodos (esta vez como métodos de instancia en lugar de estáticos).
public class Post
{
    ...

    public Post Body(string body)
    {
        this.BodyValue = body;
        return this;
    }

    public Post Tags(string tags)
    {
        this.TagsValue = tags;
        return this;
    }
}

Devolver la misma instancia del objeto que estamos construyendo, luego de cada invocación a un método, es una de las técnicas mas comunes usadas para escribir interfaces fluent, la misma es llamada Method Chaining.
Paso 5 - Completando la primer iteración y corriendo tests

Ahora, implementemos el método Build creando una instancia de BlogPost con los valores provistos a través de la interfaz fluent.
public class Post
{
    ...

    public BlogPost Build()
    {
        var blogPost = new BlogPost();

        blogPost.Title = this.TitleValue;
        blogPost.Body = this.BodyValue;
        blogPost.Tags = this.TagsValue;

        return blogPost;
    }
}

Nuestro test debería estar en verde ahora!  

Paso 6 - No nos olvidemos del Autor

Acá viene la parte un poco complicada, agreguemos al Autor...

Si continuamos lo mismo que veníamos haciendo con los métodos Title, Body y Tags, deberíamos hacer que el método Author devuelva una instancia de la clase Post; pero si hacemos eso, tendríamos que agregar los métodos Name, Email y Twitter en la clase Post, pero eso no es algo que querramos hacer. Lo que queremos es tener esos métodos en una clase separada, así que en lugar de devolver una instancia de Post, vamos a devolver una instancia de una nueva clase (AuthorSpec). Veamos cómo sería.
public class Post
{
 ...

 public AuthorSpec Author()
 {
  var authorSpec = new AuthorSpec();
  return authorSpec;
 }
}

public class AuthorSpec
{
 public AuthorSpec Name(string name)
 {
  this.NameValue = name;
  return this;
 }

 public AuthorSpec Email(string email)
 {
  this.EmailValue = email;
  return this;
 }

 public AuthorSpec Twitter(string twitter)
 {
  this.TwitterValue = twitter;
  return this;
 }
}

Ahora creemos un nuevo test, agregando el código de "Author":
public void ConfiguringPostWithAuthor()
{
 var expected = new BlogPost();
 expected.Title = "How to build a basic fluent interface in 8 steps";
 expected.Body = "...";
 expected.Tags = "Fluent API, Internal DSL";
 var author = new Author();
 author.Name = "John Doe";
 author.Email = "johndoe@email.com";
 author.Twitter = "@johndoe";
 expected.Author = author;

 var post = Post
  .Title("How to build a basic fluent interface in 8 steps")
  .Body("...")
  .Author()
   .Name("John Doe")
   .Email("johndoe@email.com")
   .Twitter("@johndoe")
  .Tags("Fluent API, Internal DSL");
 BlogPost actual = post.Build();
  
 Assert.IsTrue(CheckEquivalence(expected, actual));
}

Esperá! El compilador dice que el método Tags no es reconocido como un miembro de la clase AuthorSpec. Eso es porque estamos invocando al método Tags sobre el valor de retorno del método Twitter, el cual es una instancia de AuthorSpec y no de Post.

Al escribir APIs fluent, es muy común encontrarse con este tipo de problemas, las soluciones más comunes a esto son terminar la especificación del Autor con un método "end", o pasar un "nested closure" al método Author.
Paso 7 - Cerrando la especificación de Author y corriendo más tests

Opción A, usando un método "End".
public class Post
{
    ...

    public AuthorSpec Author()
    {
        this.authorSpec = new AuthorSpec(this);
        return authorSpec;
    }
}

public class AuthorSpec
{
    public AuthorSpec(Post parent)
    {
        this.parent = parent;
    }

    ...

    public Post End()
    {
        return this.parent;
    }
}

Cómo se ve:
var post = Post
    .Title("How to build a basic fluent interface in 8 steps")
    .Body("...")
    .Author()
        .Name("John Doe")
        .Email("johndoe@email.com")
        .Twitter("@johndoe")
        .End()
    .Tags("Fluent API, Internal DSL");

Opción B, usando un "nested closure".
public class Post
{
    ...

    public Post Author(Action<AuthorSpec> spec)
    {
        this.authorSpec = new AuthorSpec();
        spec(authorSpec);
        return this;
    }
}

Cómo se ve:
var post = Post
    .Title("How to build a basic fluent interface in 8 steps")
    .Body("...")
    .Author(a => a
        .Name("John Doe")
        .Email("johndoe@email.com")
        .Twitter("@johndoe"))
    .Tags("Fluent API, Internal DSL");

Yo personalmente prefiero la opción B. Creo que es ligeramente más fácil de leer y el programador no tiene que recordar llamar al método "End".
Luego de implementar una de estas dos opciones, nuestro test debería dar verde.

Paso 8 - Entrando en terrenos peligrosos

Imaginemos que queremos forzar a la gente que utiliza nuestra API para que asignen el nombre del autor primero y luego configuren la dirección de correo ó la cuenta de twitter, pero no ambos (un "o exclusivo"). Esto se puede hacer usando interfaces y así mostrar sólo un subconjunto de los métodos a medida que se va armando la cadena de métodos.
public class Post
{
    ...

    public Post Author(Action<IAuthorSpecAfterCreation> spec)
    {
        this.authorSpec = new AuthorSpec();
        spec(authorSpec);
        return this;
    }
}

public interface IAuthorSpecAfterCreation
{
    IAuthorSpecAfterName Name(string name);
}

public interface IAuthorSpecAfterName
{
    IAuthorSpecAfterEmailOrTwitter Email(string email);
    IAuthorSpecAfterEmailOrTwitter Twitter(string twitter);
}

public interface IAuthorSpecAfterEmailOrTwitter
{
}

public class AuthorSpec : IAuthorSpecAfterCreation, 
  IAuthorSpecAfterName, IAuthorSpecAfterEmailOrTwitter
{
    public IAuthorSpecAfterName Name(string name)
    {
        this.NameValue = name;
        return this;
    }

    public IAuthorSpecAfterEmailOrTwitter Email(string email)
    {
        this.EmailValue = email;
        return this;
    }

    public IAuthorSpecAfterEmailOrTwitter Twitter(string twitter)
    {
        this.TwitterValue = twitter;
        return this;
    }
}
Esta técnica puede volverse muy útil, pero la complejidad de la API puede crecer rápidamente con tantas interfaces aquí y allá... es recomendable usarlo con precaución.
Los fuentes completos pueden ser tomados desde acá.

Eso es todo, espero que sea útil :)

28 de enero de 2012

API fluent para configurar Windows Azure ACS

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/01/28/fluent-api-for-setting-up-windows-azure-acs/

Durante los últimos meses he estado trabajando para la gente de p&p de Microsoft, desarrollando la aplicación de ejemplo para una nueva guía acerca de la integración de aplicaciones híbridas en Windows Azure.

Uno de los componentes de la solución de ejemplo es una aplicación de consola que realiza todos los pasos requeridos para tener los namespaces de ACS y Service Bus correctamente configurados. Esta aplicación usa una serie de wrappers alrededor de la API de management de ACS.

Mientras que esto es un gran paso en dirección de simplificar la vida del desarrollador, luego de mirar el código resultante me di cuenta que mi vida se podría haber simplificado aún más. Esa es la razón por la cual decidí invertir algo de mi tiempo desarrollando una API fluent para configurar los namespaces de ACS.

Así es como se ve la API:

var namespaceDesc = new AcsNamespaceDescription(
 "somenamespace", "ManagementClient", "T+bQtqP21BaCLO/8D1hanRdKJF8ZYEV8t32odxP4pYk=");

var acsNamespace = new AcsNamespace(namespaceDesc);

acsNamespace
 .AddGoogleIdentityProvider()
 .AddServiceIdentity(
  si => si
   .Name("Vandelay Industries")
   .Password("Passw0rd!"))
 .AddRelyingParty(
  rp => rp
   .Name("MyCoolWebsite")
   .RealmAddress("http://mycoolwebsite.com/")
   .ReplyAddress("http://mycoolwebsite.com/")
   .AllowGoogleIdentityProvider()
   .SwtToken()
   .TokenLifetime(120)
   .SymmetricKey(
    Convert.FromBase64String("yMryA5VQVmMwrtuiJBfyjMnAJwoT7//fCuM6NwaHjQ1="))
   .AddRuleGroup(rg => rg
    .Name("Rule Group for MyCoolWebsite Relying Party")
    .AddRule(
     rule => rule
      .Description("Google Passthrough")
      .IfInputClaimIssuer().Is("Google")
      .AndInputClaimType().IsOfType(ClaimTypes.Email)
      .AndInputClaimValue().IsAny()
      .ThenOutputClaimType().ShouldBe(ClaimTypes.Name)
      .AndOutputClaimValue().ShouldPassthroughFirstInputClaimValue())
    .AddRule(
     rule => rule
      .Description("ACS rule")
      .IfInputClaimIssuer().IsAcs()
      .AndInputClaimType().IsAny()
      .AndInputClaimValue().IsAny()
      .ThenOutputClaimType().ShouldPassthroughFirstInputClaimType()
      .AndOutputClaimValue().ShouldPassthroughFirstInputClaimValue())));

acsNamespace.SaveChanges(logInfo => Console.WriteLine(logInfo.Message));


Pueden encontrar más información en el repositorio del proyecto en github.

Ah, y por favor no olviden pegarle una mirada a la guía sobre integración de aplicaciones híbridas cuando la misma esté disponible (se puede ir descargando un borrador desde aquí), estos muchachos están haciendo un trabajo increíble !

Eso es todo, espero que sea útil :)