¿Qué novedades nos trajo Microsoft Connect 2018?

Si bien Microsoft ha tenido varias conferencias a lo largo del año donde ha ido revelando varios productos se ve que aún tenía novedades para mostrarnos antes de que el año se termine.

Vamos a ver cuales son las más interesantes.

.NET Core 2.2

El framework multiplataforma de Microsoft ha ganado una pequeña actualización que ya se encuentra en release. Lo más destacado de esta actualización es el soporte para ARM32. Además contiene mejoras de diagnóstico en la runtime y la compatibilidad con Azure Active Directory para SQL Client. La mayoría de las mejoras en esta release están en ASP.NET Core (ver más adelante).

Se puede descargar y empezar a programar con .NET Core 2.2 en Windows, macOS, y Linux:

.NET Core está soportado por Visual Studio 15.9, Visual Studio para Mac y Visual Studio Code.

Las imágenes de docker se encuentran en microsoft.com/dotnet

Se pueden ver los detalles completos en las notas de release.

Compilación en capas

Esta es una caracteristica de performance para el compilador JIT que viene agregado desde 2.1 se pensó que podría activarse por defecto en 2.2 pero al final no va a estar activado por defecto ya que aún no está completamente listo. Si lo estará para la proxima versión mayor, .NET Core 3.0

Eventos de runtime

A veces necesitamos acceder a los eventos de runtime y monitorear algunos servicios tales como el GC, JIT, o el ThreadPool del proceso actual para entender cuales servicios se estan ejecutando en tu aplicación.

Se puede ver como suscribirse en el siguiente ejemplo

internal sealed class SimpleEventListener : EventListener
  {
      // Called whenever an EventSource is created.
      protected override void OnEventSourceCreated(EventSource eventSource)
      {
          // Watch for the .NET runtime EventSource and enable all of its events.
          if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
          {
                  EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)(-1));
          }
      }

      // Called whenever an event is written.
      protected override void OnEventWritten(EventWrittenEventArgs eventData)
      {
          // Write the contents of the event to the console.
          Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventName}");
          for (int i = 0; i < eventData.Payload.Count; i++)
          {
              string payloadString = eventData.Payload[i] != null ? eventData.Payload[i].ToString() : string.Empty;
              Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
          }
          Console.WriteLine("\n");
      }
  }

Soporte para AccessToken en Sql Connection

Como adelantamos antes, SQL Connection ahora soporta utilizar un Access Token para autenticarse con Azure Active Directory

Acá un ejemplo de como hacerlo:

// get access token using ADAL.NET
var authContext = new AuthenticationContext(authority);
var authResult = await authContext.AcquireTokenAsync(appUri, clientCredential);
// setup connection to SQL Server
var sqlConnection = new SqlConnection(connectionString);
sqlConnection.AccessToken = authResult.AccessToken;
await sqlConnection.OpenAsync();

Posibilidad de inyectar código antes que Main

.NET Core ahora permite inyectar código antes que se ejecute el método main de una aplicación mediante un Startup Hook. Los Startup Hooks hacen que sea posible para el host personalizar el comportamiento de las aplicaciones cuando ya han sido desplegada sin la necesidad de recompilar o cambiar la aplicación.

Ver Host startup hook para más información.

Windows ARM32

Así como venía el soporte para Linux ARM32 en la versión 2.1 ahora también se puede ejecutar en Windows.

Windows soporta ARM32 en Windows IOT Core desde hace un tiempo y ahora también como parte de Windows Nano Server 2019.

Las imagenes se pueden bajar en el Docker Hub en microsoft/dotnet

ASP.NET Core 2.2

Esta es una de los que mas han obtenido mejoras tambien en release. Vamos a hacer un resumen de algunas mejoras con links para más información.

La compatiblidad con esta nueva versión se espera que estará disponible en Azure App Service antes de que termine 2018.

Entity Framework Core 2.2

El framework de ORM tambien le tocó su actualización en release y trae algunas novedades interesantes.

Además se arreglaron mas de 100 bugs.

Soporte para datos espaciales

El soporte para datos espaciales se puede usar para representar la ubicación física y la forma de los objetos. Muchas bases de datos pueden almacenar de manera nativa, indexar y consultar datos espaciales. Los escenarios comunes es hacer una consulta para objetos a cierta distancia o verificar si un polígono tiene una localización dada. En EF Core 2.2 ahora se soportan trabajar con datos espaciales utilizando los tipos de libería NetTopologySuite (NTS).

Primero debemos instalar la extensión de soporte del provider, actualmente estan disponibles para SQL Server, SQLite, y PostgreSQLhttps://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite/. Además se pueden usar de manera directa con el EF Core in-memory provider sin la necesidad de extensiones adicionales.

Una vez que está instalada la extensión se puede usar como el siguiente ejemplo

using NetTopologySuite.Geometries;

namespace MyApp
{
  public class Friend
  {
    [Key]
    public string Name { get; set; }

    [Required]
    public Point Location { get; set; }
  }
}

Y después persistir las entidades con datos espaciales:

using (var context = new MyDbContext())
{
    context.Add(
        new Friend
        {
            Name = "Bill",
            Location = new Point(-122.34877, 47.6233355) {SRID = 4326 }
        });
    context.SaveChanges();
}

y se puede ejecutar consultas en la base de datos basadas en estos datos espaciales

var nearestFriends =
      (from f in context.Friends
      orderby f.Location.Distance(myLocation) descending
      select f).Take(5).ToList();

Colecciones de entidades propias

EF Core 2.0 agregó la posibilidad de modelar relaciones uno a uno. En EF Core 2.2 se extiende la habilidad para expresar la propiedad de relaciones uno a muchos.

Las entidades propias (owned entities) solo pueden aparecer como propiedad de otra entidad y ser trackeadas por el mismo DbContext.

En las bases de datos relaciones se mapean a diferentes tablas como las asociaciones uno a muchos comunes. En bases de datos orientadas a documentos, se planea que se aniden estas entidades dentro del poseedor.

Se puede utilizar la caracteristica llamando al método OwnsMany:

modelBuilder.Entity<Customer>().OwnsMany(c => c.Addresses);

Para más información ver la documentación

Query Tags

Esta caracteristíca simplifica la correlación entre las consultas LINQ y el código SQL generado que se captura en los logs. Si ponemos un tag a un consulta podemos buscarla por ese Tag en el código SQL generado.

Se anota de la siguiente forma (usando el ejemplo anterior de consulta de datos espaciales):

 var nearestFriends =
      (from f in context.Friends.TagWith(@"This is my spatial query!")
      orderby f.Location.Distance(myLocation) descending
      select f).Take(5).ToList();

Esta consulta LINQ produce el siguiente código SQL:

-- This is my spatial query!

SELECT TOP(@__p_1) [f].[Name], [f].[Location]
FROM [Friends] AS [f]
ORDER BY [f].[Location].STDistance(@__myLocation_0) DESC

Para más información ver la documentación de query Tags

¿Cómo obtener EF Core 2.2?

Los paquetes de EF Core están disponibles en la Galería Nuget y también como parte de ASP.NET Core 2.2 y el nuevo SDK de .NET Core.

Se recomienda actualizar a ASP.NET Core a 2.2 para utilizar en conjunto.

Para agregar el paquete de nuget, desde la consola:

$ dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 2.2.0

O desde la Consola del administrador de paquetes de Visual Studio

PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.2.0

En general es compatible hacia atrás con versiones previas. Por las dudas habría que ver el issue #13986.

¿Qué sigue? EF Core 3.0

Ahora el equipo está enfocado en terminar EF Core 3.0. Como todavía no ha habido grandes avances los paquetes de preview que están en Nuget solo contienen pequeños cambios.

Hay varios detalles de la siguiente release mayor que están en discusión por mencionar algunos.

  • Mejoras en LINQ
  • Soporte para CosmosDB
  • Soporte para C# 8.0
  • Ingeniería inversa de vistas de base de datos en tipos de query
  • Entidades property bag (como un diccionario)
  • EF 6.3 en .NET Core, se esta trabajando para llevar soporte de EF 6.3 a .NET Core, ya que ahora .NET Core va a soportar Winforms y WPF pero no se van a agregar características nuevas.

C# 8.0

El lenguaje de programación estrella de Microsoft tiene una nueva versión donde tambien vienen varias cosas interesantes.

Para poder utilizar las características nuevas de C# 8.0 se necesita instalar Visual Studio 2019 Preview y .NET Core 3.0. El tema es que no todas las características nuevas están disponibles y acá solo veremos algunas. Si se quieren ver todas las características lean el siguiente post Building C# 8.0 o vean el video corto “¿Qué hay de nuevo con C# 8.0?” en Channel 9 o Youtube

Preparándonos

Primero que nada, descarguemos e instalemos la Preview 1 de .NET Core 3.0 y la Preview 1 de Visual Studio 2019. En Visual Studio, aseguremonos que tenemos seleccionado el workload “.NET Core cross-platform development”. Si nos olvidamos se puede agregar después abriendo el Visual Studio Installer.

Lancemos el Visual Studio 2019 y creemos un nuevo proyecto de consola de C#. Una vez que el proyecto ha sido creado cambiemos el target framework a .NET Core 3.0 haciendo click derecho en el proyecto en el explorador de soluciones y poniendo propiedades

Tipos de referencia nullables

Los tipos de referencia nullables es una característica que nos advierte cada vez que nos encontre con código null-unsafe (que pueda dar NullReferenceException). Dado que es algo que nose hacía antes, sería un breaking change empezar ahora. Por lo cual es necesario que hagamos activemos esa característica, no viene activada por defecto.

Antes de activarla escribamos un código realmente malo.

using static System.Console;

class Program
{
    static void Main(string[] args)
    {
        string s = null;
        WriteLine($"The first letter of {s} is {s[0]}");
    }
}

Si la ejecutamos vamos a tener, por supuesto, una NullReferenceException. Si bien en este corto ejemplo sabemos que s era null y eso iba a causar la excepción, en la vida real no tenemos ese lujo. Muchas veces los elementos que vienen null vienen de una librería o de algun código que no controlamos.

Con la nueva característica podemos saber si algo tiene riesgo de ser null y controlarlo. Entonces activemosla. De hecho, deberíamos activarla para cada proyecto nuevo ya que nos ayuda a tener código más limpio y menos propenso a errores. Para proyectos que ya tienen código puede que se necesiten de cambios para adaptar el código por lo cual eso habría que hacerlo con tiempo.

En el archivo csproj del proyecto, agreguemos la siguiente línea.

    <NullableReferenceTypes>true</NullableReferenceTypes>

Guardemos el achivo .csproj y volvamos al programa: ¿Qué pasó? Ahora obtenemos dos advertencias. Cada una representa la mitad de la característica. La primera es el null en esta línea:

    string s = null;

Se queja de que estamos asignando null a un tipo no nullable. ¿Qué?? se supone que los string son nullables. Bueno, resulta que desde el momento en que activamos la característica los tipos de referencia ordinarios no admiten valores null Porque, al activar esta opción, todos los tipos son por defecto no nullables (hasta los tipos de referencia).

Entonces los null estan prohibidos salvo que los pidamos explicitamente. ¿Cómo los pedimos? Usando un tipo de referencia nullable. El sufijo del signo de pregunta marca la señal de que null está permitido.

string? s = null;

La advertencia se va afuera: hemos expresado la intención de que queremos que esta variable pueda ser null.

Pero en la siguiente línea queda una advertencia.

    WriteLine($"The first letter of {s} is {s[0]}");

Eso es porque intentamos acceder al valor de algo que podría ser null. El compilador es suficientemente inteligente para advertirnos.

Una solución posible es:

    if (s != null) WriteLine($"The first letter of {s} is {s[0]}");

Lo que hace que la referencia se vaya. Otra es:

    WriteLine($"The first letter of {s} is {s?[0] ?? '?'}");

Esta es otra característica nueva de C# el operador null condicional, se ve en s?[0] que evita intentar acceder al valor y devuelve directamente null si la referencia era null.

Con esto el compilador está feliz y no hay más advertencias.

Esta característica nos permite ser honestos con nuestro código, nos fuerza a expresar de manera explícita nuestra intención de si queremos null en el sistema, usando un tipo de referencia nullab.e Y una vez que null está ahí, nos forza a tratar con el de manera responsable, haciendo chequeos cada vez que haya el riesgo de estar intentando acceder a algo que de null reference.

¿Estámos completamente a salvo? No, hay ciertos casos donde pueden darse null reference. Como código previo a que esta característica existiera. Otro caso es que el análisis tiene ciertos casos donde por terminos de practicidad no se controla que null existe.

Pero si se usa esta característica de manera extensiva, debería encargase de la mayoría de las referencias null.

Rangos e indices

C# está ganando unos tipos para representar rangos e indices. ¿Necesitabamos una sintaxis simple para cortar una parte de un array, string, o span? ¡Ahora podemos!

using System.Collections.Generic;
using static System.Console;

class Program
{
    static void Main(string[] args)
    {
        foreach (var name in GetNames())
        {
            WriteLine(name);
        }
    }

    static IEnumerable<string> GetNames()
    {
        string[] names =
        {
            "Archimedes", "Pythagoras", "Euclid", "Socrates", "Plato"
        };
        foreach (var name in names)
        {
            yield return name;
        }
    }
}

Vamos a cambiar un poco ese código, cambiemos el segundo foreach de la siguiente manera.

    foreach (var name in names[1..4])

En este caso lo que hace es iterar del cero hasta el cuatro. Si lo ejecutamos vamos a ver que el limite derecho es exclusivo, es decir que el elemento 4 no está incluido en el rango. 1..4 es una expresión de rango y no solo puede estar dentro de una expresión de indización. Tiene su propio tipo, llamado Range. Y se puede poner en su propia variable:

 Range range = 1..4; 
 foreach (var name in names[range])

Los límites del intervalo no tienen que ser ints. De hecho tienen su propio tipo. Index, que es no negativo. Se pueden crear indices con el nuevo operador ^ que significa “desde el final”. Así que el siguiente ^1 es contar uno desde el final:

    foreach (var name in names[1..^1])

Esto produce un array con los tres elementos del medio.

Los rangos pueden estar abiertos en uno o en ambos lados, ..^1 significa lo mismo que 0..^1 1.. significa lo mismo 0..^0 de principio a fin. Si los probamos lo podemos comprobar.

Streams asíncronos

IEnumerable juega un rol especial en C#. “IEnumerables” representan toda clases de diferentes secuencias de datos, y el lenguaje tiene construcciones especiales para construirlos y consumirlos.

Como vimos en los ejemplos anteriores, se pueden consumir a través de un statement foreach, que se encarga de obtener el enumerator, avanzarlo de manera repetiva, extrayendo los elementos a lo largo del camino y finalmente haciendo dispose sobre el enumerator. Y ellos pueden ser producidos con iteradores: Métodos que hacen yield return de sus elementos a medida que van siendo solicitados por el consumidor. Sin embargo ambos son síncronos, los resultados van a tener que estar cuando son solicitados sino el hilo se bloquea.

async y await se agregaron a C# para lidiar con resultados que no necesariamente están listos cuando los solicitamos. Ellos pueden ser awaited (esperados) de manera asíncrona y el hilo puede ir a hacer otras cosas hasta que estén listos. Pero eso solo funciona para valores únicos y no para secuencias que se producen de manera gradual y asíncronica, como las medidas de un sensor IoT o hacer streaming de datos de un servicio.

Los streams asíncronos nos juntan los enumerables con lo async.

Para este ejemplo agreguemos otra directiva using en la parte superior del archivo:

using System.Threading.Tasks;

Ahora simulemos que GetNames hace algún trabajo asíncrono agregando un pequeño retardo antes de que el yield se retornado:

 await Task.Delay(1000);
 yield return name;

Por supuesto que vamos a obtener un error de que solo podemos utilizar await en un método async. Así que hagamos que sea async:

    static async IEnumerable<string> GetNames()

Pero ahora nos dice que no estamos retornando el tipo correcto en el método asíncrono, tiene razón. Ahora hay una nueva opción para la lista de tipos que se pueden retornar además de las clásicas Task: IAsyncEnumerable<T>. Esta es nuestra versión asíncrona de IEnumerable<T> así que retornemos eso:

    static async IAsyncEnumerable<string> GetNames()

De esta forma ¡estamos produciendo un stream asíncrono de cadenas! De acuerdo con las directivas de nombrado, renombremos el método GetName por GetNamesAsync.

    static async IAsyncEnumerable<string> GetNamesAsync()

Ahora tenemos un error en esta línea del método Main:

    foreach (var name in GetNamesAsync())

Lo que nos indica que el programa no sabe como hacer un foreach sobre un IAsyncEnumerable<T>. Esto se debe a que hacer foreach sobre streams asíncronos requiere el uso explícito de la palabra clave await:

    await foreach (var name in GetNamesAsync())

Esta es la versión de foreach que toma un stream asíncrono y hace await de cada elemento. Por supuesto que solo podemos hacer esto en un método asíncrono, asi que necesitamos que nuestro Main sea async. Afortunadamente, C# 7.2 agregó soporte para eso:

static async Task Main(string[] args)

Ahora todas las advertencias se van y nuestro programa está correcto. Pero si intentamos compilar veremos que nos da un numero muy grande de errores. Esto es porque cometimos el error, de no obtener las versiones preview de .NET Core 3.0 y Visual Studio 2019 alíneados de manera perfecta. Especificamente, hay una implementación de los async iterators que es diferente a lo que el compilador espera.

Esto se puede arreglar agregando un archivo separado a tu proyecto, que contenga este código reparador. Compilemos de nuevo y debería funcionar correctamente.

Eso por ahora con las novedades de C#, vemos que el lenguaje sigue avanzando a medida que pasan los años.