martes, 8 de abril de 2014

Eliminar y actualizar eficientemente con Entity Framework

Después de llevar un tiempo programando con EF, me gustaría compartir un par de trucos para intentar evitar round-trips innecesarios al servidor, y de paso optimizar en la medida de lo posible el impacto de las instrucciones UPDATE y DELETE en nuestra base de datos.

Para el post utilizaré una base de datos con una sola tabla llamada Clientes.

clip_image001[8]

Imaginemos ahora unos típicos métodos para actualizar el nombre de un cliente y eliminar el registro.

static void EliminarCliente(int idCliente)

{

    using (var context = new GestionEntities())

    {

        var cliente = context.Clientes.Find(idCliente);

        context.Clientes.Remove(cliente);

        context.SaveChanges();

    }

}

 

static void ActualizarNombreCliente(int idCliente, string nombre)

{

    using (var context = new GestionEntities())

    {

        var cliente = context.Clientes.Find(idCliente);

        cliente.Nombre = nombre;

        context.SaveChanges();

    }           

}

Aunque pueden parecer métodos válidos (de hecho lo son), tiene algún problema de ineficiencia que podríamos paliar reescribiendo un poco el código y sobre todo, conociendo como funciona EF.

El motivo por el que podría acusarse el anterior código como ineficiente es que, tanto para actualizar como para eliminar, primero se está realizando un acceso a datos para cargar la entidad completa (el método Find). Cierto es que Find nos devolvería la entidad desde el contexto si ya hubiera sido previamente cargada, pero para este caso (donde se crea y destruye el contexto en cada método) y para el propósito del post, pensaremos que Find accede a datos porque no encuentra la entidad en memoria.

Un punto a favor de EF y que no quisiera dejar de señalar es que la instrucción de actualización sólo incluye los campos que hemos modificado, es decir, ActualizarNombreCliente tira la siguiente sentencia SQL:

exec sp_executesql N'UPDATE [dbo].[Clientes]

SET [Nombre] = @0

WHERE ([IdCliente] = @1)

',N'@0 nvarchar(50),@1 int',@0=N'Sergio',@1=1

 

Sin embargo, como nosotros queremos que nuestra actualización y eliminación sean lo más óptimas posibles, terminaríamos escribiendo este otro código:

static void EliminarCliente(int idCliente)

{

    var cliente = new Cliente { IdCliente = idCliente };

    using (var context = new GestionEntities())

    {

        context.Clientes.Attach(cliente);

        context.Clientes.Remove(cliente);

        context.SaveChanges();

    }

}

 

static void ActualizarNombreCliente(int idCliente, string nombre)

{

    var cliente = new Cliente { IdCliente = idCliente };

    using (var context = new GestionEntities())

    {

        context.Clientes.Attach(cliente);

        cliente.Nombre = nombre;

        context.SaveChanges();

    }

}

Con este código ahora sólo ejecutamos 2 sentencias en su más mínima expresión y sin previo acceso a datos para cargar la entidad en memoria.

exec sp_executesql N'UPDATE [dbo].[Clientes]

SET [Nombre] = @0

WHERE ([IdCliente] = @1)

',N'@0 nvarchar(50),@1 int',@0=N'Sergio',@1=1

 

exec sp_executesql N'DELETE [dbo].[Clientes]

WHERE ([IdCliente] = @0)',N'@0 int',@0=1

 

De DELETE no hay mucho más que hablar, sólo la precaución (también aplicable a UPDATE) de incluir en nuestra entidad todas las propiedades que son clave principal (Entity Key) o que están incluidas como parte de la concurrencia pesimista (Concurrency Mode = Fixed).

Sin embargo para UPDATE sí hay que comentar algo más al respecto. Si en nuestra tabla Clientes hubiera algún campo que no admitiera nulos y que no fuera parte de nuestra entidad (por ejemplo Descripcion o Pais fueran no nulables), la llamada a SaveChanges fallaría por la validación de EF y lanzaría un error del tipo System.Data.Entity.Validation.DbEntityValidationException.

No sé si habrá muchas más formas de solucionar esto, pero por mi parte yo lo hago deshabilitando la validación antes de la llamada a SaveChanges.

Claro está que si el contexto no es de usar y tirar (que será lo más normal), deberíamos después restaurar el valor de la propiedad ValidationOnSaveEnabled a su valor original.

Dicho esto, el código de actualizar quedaría así:

static void ActualizarNombreCliente(int idCliente, string nombre)

{

    var cliente = new Cliente { IdCliente = idCliente };

    using (var context = new GestionEntities())

    {

        context.Clientes.Attach(cliente);

        cliente.Nombre = nombre;

        context.Configuration.ValidateOnSaveEnabled = false;

        context.SaveChanges();

    }

}

Un saludo!

sábado, 5 de abril de 2014

Introducción a NUnit (IV): Tests parametrizados

Ya en un post anterior de la serie se habló por encima del concepto de tests parametrizados y como podíamos ejecutar un test n veces con la ayuda del atributo [TextFixture] con distintos parámetros y un constructor personalizado.

En cualquier caso, como la ejecución de tests parametrizados es una característica muy importante, bien merece su propio post en la serie.

Un test parametrizado permite ejecutar un mismo test con un conjunto de valores distintos.

 

class Pruebas

{

    [Test]

    public void Test([Values(1,2,3)] int num1)

    {

        Assert.Pass();

    }

}

image

Los atributos que permiten la ejecución de tests parametrizados son:

  • Values
  • ValuesSource
  • Combinatorial
  • Sequential
  • Random
  • Range
  • TestCase
  • TestCaseSource

El atributo [Values] sirve para indicar por cada parámetro un conjunto de valores. Al ser un atributo, los valores deben ser un tipo por valor, una constante o un typeof (en definitiva un valor interpretable en tiempo de compilación). Esto no es una limitación de NUnit, es simplemente que los atributos en .NET funcionan así.

Si el método de test tiene varios parámetros decorados con el atributo [Value], por defecto NUnit combina todos los valores (es como si hubiéramos puesto el atributo [Combinatorial] al método de test):

 

[Test]

public void Test([Values(1, 2, 3)] int num1, [Values(4, 5)] int num2)

{

    Assert.Pass();

}

image

Si por el contrario ponemos al método de test el atributo [Sequential], ahora iterará cogiendo un valor a la vez por cada parámetro y cuando no hay coincidencia pondrá el valor por defecto:

 

[Test]

[Sequential]

public void Test([Values(1, 2, 3)] int num1, [Values(4, 5)] int num2, [Values(5, 7, 3)] int resultado)

{

    var calculadora = new Calculadora();

    Assert.AreEqual(resultado, calculadora.Sumar(num1, num2));

}

image

Aunque ReSharper nos muestra null en el tercer test, realmente lo que se le pasa es un 0 que es el default del tipo int.

Si lo que queremos es un conjunto de datos aleatorio tenemos [Random]

 

[Test]

public void Test([Random(1, 100, 10)] int num1)

{

    Assert.Pass();

}

Lógicamente, ReSharper no muestra la lista de los tests y sus valores hasta que no se ejecutan.

image

Otra opción es [Range]

 

[Test]

public void Test([Range(1, 10, 2)] int num1)

{

    Assert.Pass();

}

image

Tanto con [Random] como con [Range], si hay varios parámetros “cuidadito” con [Combinatorial] (por defecto), casi mejor poner [Sequential].

Entrando ya en modo lista (IEnumerable) podemos trabajar con [ValuesSource]. [ValuesSource] permite especificar un campo, propiedad o método (de la misma clase del test o de otro clase) que sea o devuelva un tipo IEnumerable.

 

class Pruebas

{

    private IEnumerable<int> _valores = new List<int>() {1, 2, 3, 4, 5};

    [Test]

    public void Test([ValueSource("_valores")] int num1)

    {

        Assert.Pass();

    }

}

o también

class ValoresSource

{

    private IEnumerable<int> _valores = new List<int>() { 1, 2, 3, 4, 5 };

    public IEnumerable<int> Valores { get { return _valores; } }

}

class Pruebas

{

    [Test]

    public void Test([ValueSource(typeof(ValoresSource), "Valores")] int num1)

    {

        Assert.Pass();

    }

}

Si en vez de asignar valores a parámetros y combinarlos con [Combinatorial] o [Sequential], queremos especificar de forma explícita que valores tomará cada parámetro, podemos utilizar [TestCase] (con este atributo además podemos ahorrarnos decorar el método también con [Test]).

 

[TestCase(1, 2)]

[TestCase(3, 4)]

[TestCase(5, 6)]

public void Test(int num1, int num2)

{

    Assert.Pass();

}

image

Además [TestCase] tiene un montón de sobrecargas que permiten especificar:

  • Category
  • Description
  • ExpectedException
  • Explicit
  • Ignore
  • TestName, que puede ser útil para dar un nombre a un TestCase (recuerda que lo podemos repetir para ejecutar n veces un test, y con este atributo podemos diferenciarlo)

[TestCase(1, 2,TestName = "1_and_2_are_the_first_two_numbers")]

[TestCase(3, 4)]

[TestCase(5, 6)]

public void Test(int num1, int num2)

{

    Assert.Pass();

}

image

Como podemos ver [TestCase] casi reemplaza/aúna en un solo atributo a los principales atributos de NUnit

Además con [TestCase] también podemos especificar un valor esperado de retorno del método de test (con lo que ahora un test no tendría ya que devolver void)

 

[TestCase(3, 4, ExpectedResult = 7, TestName = "sum_3_4_should_be_7")]

[TestCase(5, 6, ExpectedResult = 11, TestName = "sum_5_6_should_be_11")]

public int Test(int num1, int num2)

{

    return num1 + num2;

}

Como último atributo en lo relativo a tests parametrizados, encontramos [TestCaseSource]. Con [TestCaseSource] podemos:

  • Especificar un tipo que implementa IEnumerable
  • Especificar un tipo y un miembro que implementa IEnumerable
  • Especificar un miembro que implementa IEnumerable

Fijarse que aunque esto suena muy parecido a [ValuesSource], pero hay diferencias. [TestCaseSource] no se aplica a un parámetro sino a todos los parámetros en una llamada (al igual que [Values] vs [TestCase]) y además [TestCaseSource] permite especificar un tipo que, directamente, ya implemente IEnumerable (para evitar harcodear un miembro en una string).

 

class ValoresTestDataSource

{

    private IEnumerable<int> _valores = new List<int>() { 1, 2, 3, 4, 5 };

    public IEnumerable<int> Valores { get { return _valores; } }

}

 

class Pruebas

{

    [TestCaseSource(typeof(ValoresTestDataSource), "Valores")]

    public void Test(int num1)

    {

        Assert.Pass();

    }

}

image

Si ahora cambiamos el test y pedimos 2 argumentos, también tenemos que cambiar el origen de datos porque si no tendremos un error como el siguiente:

 

[TestCaseSource(typeof(ValoresTestDataSource), "Valores")]

public void Test(int num1, int num2)

{

    Assert.Pass();

}

image

Cambiamos el origen pues:

    class ValoresTestDataSource

    {

        private IEnumerable<int[]> _valores = new List<int[]>

        {

            new int[]{1,2},

            new int[]{3,4}

        };

        public IEnumerable<int[]> Valores { get { return _valores; } }

    }

 

    class Pruebas

    {

        [TestCaseSource(typeof(ValoresTestDataSource), "Valores")]

        public void Test(int num1, int num2)

        {

            Assert.Pass();

        }

    }

Este estilo de pasar parámetros está bien pero empieza a ser un poco complicado de leer (bajo mi punto de vista). Para solucionar esto, NUnit permite devolver instancias del tipo [TestCaseData] que representan todo lo que podíamos hacer con el atributo [TestCase]. Además [TestCaseData] implementa una interface fluida y es bastante cómodo configurar el caso.

class ValoresTestDataSource

{

    public IEnumerable<TestCaseData> GetValores()

    {

        yield return new TestCaseData(1, 2)

            .SetName("1_2_should_be_3")

            .Returns(3);

        yield return new TestCaseData(3, 4)

            .SetName("3_4_should_be_7")

            .Returns(7);

    }

}

 

class Pruebas

{

    [TestCaseSource(typeof(ValoresTestDataSource), "GetValores")]

    public int Test(int num1, int num2)

    {

        return num1 + num2;

    }

}

image

A partir de aquí, crear una clase que lea los valores desde un CSV o una bd (como ya hace de serie MSUnit) no debería ser problema.

Mas posts de esta serie:

Un saludo!