Code First en una nueva base de datos

Este vídeo y el tutorial paso a paso proporcionan una introducción al desarrollo de Code First definiendo como destino una nueva base de datos. En este escenario se incluye la definición como destino de una base de datos no existente que Code First creará, o de una base de datos vacía a la que Code First agregará nuevas tablas. Code First permite definir el modelo mediante clases C# o VB.Net. Opcionalmente, se puede realizar una configuración adicional mediante atributos en las clases y propiedades o mediante una API fluida.

Ver el vídeo

En este vídeo se proporciona una introducción al desarrollo de Code First definiendo como destino una nueva base de datos. En este escenario se incluye la definición como destino de una base de datos no existente que Code First creará, o de una base de datos vacía a la que Code First agregará nuevas tablas. Code First permite definir el modelo mediante clases C# o VB.Net. Opcionalmente, se puede realizar una configuración adicional mediante atributos en las clases y propiedades o mediante una API fluida.

Presentado por: Rowan Miller

Vídeo: WMV | MP4 | WMV (ZIP)

Requisitos previos

Es necesario tener instalado al menos Visual Studio 2010 o Visual Studio 2012 para completar este tutorial.

Si se usa Visual Studio 2010, también se debe tener Instalado NuGet.

1. Crear la aplicación

Para simplificar las cosas, vamos a crear una aplicación de consola básica que use Code First para realizar el acceso a datos.

  • Abra Visual Studio.
  • Archivo - > Nuevo - > Proyecto
  • En el menú de la izquierda, seleccione Windows y Aplicación de consola
  • Escriba CodeFirstNewDatabaseSample como nombre
  • Seleccione Aceptar.

2. Crear el modelo

Vamos a definir un modelo muy sencillo mediante clases. Solo los definimos en el archivo Program.cs, pero en una aplicación real se dividirían las clases en archivos independientes y posiblemente en un proyecto independiente.

Debajo de la definición de clases de Program, agregue las siguientes dos clases.

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
}

Observará que estamos haciendo virtuales las dos propiedades de navegación (Blog.Posts y Post.Blog). Esto habilita la característica de carga diferida de Entity Framework. Carga diferida significa que el contenido de estas propiedades se cargará automáticamente desde la base de datos cuando se intente acceder a ellas.

3. Crear un contexto

Ahora es el momento de definir un contexto derivado, que representa una sesión con la base de datos, lo que nos permite consultar y guardar datos. Definimos un contexto que deriva de System.Data.Entity.DbContext y expone un <TEntity> de DbSet con tipo para cada clase del modelo.

Vamos a empezar a usar tipos de Entity Framework, por lo que es necesario agregar el paquete NuGet EntityFramework.

  • Proyecto –> Administrar paquetes NuGet… Nota; Si no tiene la opción Administrar paquetes NuGet..., debe instalar la versión más reciente de NuGet
  • Seleccione la pestaña En línea
  • Seleccione el paquete EntityFramework
  • Haz clic en Instalar

Agregue una instrucción using para System.Data.Entity en la parte superior de Program.cs.

using System.Data.Entity;

Debajo de la clase Post de Program.cs, agregue el siguiente contexto derivado.

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

Esta es una lista completa de lo que Program.cs debe contener ahora.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace CodeFirstNewDatabaseSample
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }

        public virtual List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public virtual Blog Blog { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }
}

Es todo el código que se necesita para empezar a almacenar y recuperar datos. Obviamente, algo sucede en segundo plano y lo analizaremos dentro de un momento, pero primero veámoslo en acción.

4. Leer y escribir datos

Implemente el método Main en Program.cs como se muestra a continuación. Este código crea una nueva instancia de nuestro contexto y, a continuación, la usa para insertar un nuevo blog. A continuación, usa una consulta LINQ para recuperar todos los blogs de la base de datos ordenados alfabéticamente por Title.

class Program
{
    static void Main(string[] args)
    {
        using (var db = new BloggingContext())
        {
            // Create and save a new Blog
            Console.Write("Enter a name for a new Blog: ");
            var name = Console.ReadLine();

            var blog = new Blog { Name = name };
            db.Blogs.Add(blog);
            db.SaveChanges();

            // Display all Blogs from the database
            var query = from b in db.Blogs
                        orderby b.Name
                        select b;

            Console.WriteLine("All blogs in the database:");
            foreach (var item in query)
            {
                Console.WriteLine(item.Name);
            }

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

Ahora puede ejecutar la aplicación y probarla.

Enter a name for a new Blog: ADO.NET Blog
All blogs in the database:
ADO.NET Blog
Press any key to exit...

¿Dónde están mis datos?

Por convención, DbContext ha creado una base de datos automáticamente.

  • Si hay disponible una instancia local de SQL Express (instalada de forma predeterminada con Visual Studio 2010), Code First habrá creado la base de datos en esa instancia.
  • Si SQL Express no está disponible, Code First intentará usar LocalDB (instalado de forma predeterminada con Visual Studio 2012)
  • La base de datos se denomina según el nombre completo del contexto derivado. En nuestro caso, CodeFirstNewDatabaseSample.BloggingContext

Estas son solo las convenciones predeterminadas y existen varias maneras de cambiar la base de datos que usa Code First. Hay más información disponible en el tema How DbContext Discovers the Model and Database Connection (Cómo DbContext detecta el modelo y la conexión de base de datos). También puede acceder a la cola directamente mediante el Explorador de servidores en Visual Studio.

  • Ver -> Explorador de servidores

  • Haga clic con el botón derecho en Conexiones de datos y seleccione Agregar conexión…

  • Si no se ha conectado antes a una base de datos desde el Explorador de servidores, deberá seleccionar Microsoft SQL Server como origen de datos.

    Select Data Source

  • Conéctese a LocalDB o SQL Express, en función de cuál haya instalado.

Ahora podemos inspeccionar el esquema que Code First ha creado.

Schema Initial

DbContext ha determinado qué clases incluir en el modelo a partir de las propiedades de DbSet que hemos definido. A continuación, usa el conjunto predeterminado de convenciones de Code First para determinar los nombres de tabla y columna, determinar los tipos de datos, buscar claves principales, etc. Más adelante en este tutorial veremos cómo invalidar estas convenciones.

5. Tratar con cambios de modelo

Ahora es el momento de realizar algunos cambios en nuestro modelo. Cuando realizamos estos cambios, también es necesario actualizar el esquema de la base de datos. Para ello, vamos a usar una característica denominada Migraciones de Code First o, para abreviar, Migraciones.

Las migraciones nos permiten tener un conjunto ordenado de pasos que describen cómo actualizar el esquema de base de datos (y cómo cambiarlo a una versión anterior). Cada uno de estos pasos, conocido como migración, contiene código que describe los cambios que se van a aplicar. 

El primer paso es habilitar las migraciones de Code First para BloggingContext.

  • Herramientas -> Administrador de paquetes de biblioteca -> Consola del Administrador de paquetes

  • Ejecute el comando Enable-Migrations en la consola del Administrador de paquetes

  • Se ha agregado al proyecto la nueva carpeta Migrations que contiene dos elementos:

    • Configuration.cs: este archivo contiene la configuración que las migraciones usarán para migrar BloggingContext. No es necesario cambiar nada para este tutorial, pero aquí es donde se pueden especificar datos de inicialización, registrar proveedores para otras bases de datos, cambiar el espacio de nombres en el que se generan migraciones, etc.
    • <timestamp>_InitialCreate.cs: representa los cambios que ya se han aplicado a la base de datos para que pase de ser una base de datos vacía a una que contenga las tablas Blogs y Posts. Aunque hemos dejado que Code First cree automáticamente estas tablas, al haber optado por Migraciones, se han convertido en una migración. Code First también ha registrado en la base de datos local que esta migración ya se ha aplicado. La marca de tiempo del nombre de archivo se usa con fines de ordenación.

    Ahora vamos a realizar un cambio en nuestro modelo, agregar una propiedad URL a la clase Blog:

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }

    public virtual List<Post> Posts { get; set; }
}
  • Ejecute el comando Add-Migration AddUrl en la consola del Administrador de paquetes. El comando Add-Migration comprueba si hay cambios desde la última migración y aplica scaffolding a una nueva migración con los cambios que se encuentren. Podemos asignar un nombre a las migraciones; en este caso, llamamos a la migración "AddUrl". El código con scaffolding indica que es necesario agregar a la tabla dbo.Blogs una columna de URL, que puede contener datos de cadena. Si es necesario, podemos editar el código con scaffolding, aunque no hace falta en este caso.
namespace CodeFirstNewDatabaseSample.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class AddUrl : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.Blogs", "Url", c => c.String());
        }

        public override void Down()
        {
            DropColumn("dbo.Blogs", "Url");
        }
    }
}
  • Ejecute el comando Update-Database en la consola del Administrador de paquetes. Este comando aplica las migraciones pendientes a la base de datos. Ya se ha aplicado la migración InitialCreate, por lo que las migraciones solo aplicarán la nueva migración AddUrl. Sugerencia: Puede usar el modificador –verbose al llamar a Update-Database para ver el SQL que se ejecuta en la base de datos.

La nueva columna de URL se agregará ahora a la tabla Blogs de la base de datos:

Schema With Url

6. Anotaciones de datos

Hasta ahora, hemos dejado que EF detecte el modelo con sus convenciones predeterminadas, pero habrá ocasiones en las que nuestras clases no sigan las convenciones y será necesario poder realizar más configuraciones. Hay dos opciones para ello; veremos las anotaciones de datos en esta sección y la API fluida en la siguiente.

  • Vamos a agregar una clase User al modelo.
public class User
{
    public string Username { get; set; }
    public string DisplayName { get; set; }
}
  • También es necesario agregar un conjunto al contexto derivado.
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<User> Users { get; set; }
}
  • Si intentáramos agregar una migración, obtendríamos el "EntityType ‘User' no tiene ninguna clave definida. Defina la clave para este EntityType", porque EF no tiene forma de saber que Username debe ser la clave principal para el usuario.
  • Ahora vamos a usar anotaciones de datos, por lo que es necesario agregar una instrucción using en la parte superior de Program.cs.
using System.ComponentModel.DataAnnotations;
  • Anote la propiedad Username para identificar que es la clave principal.
public class User
{
    [Key]
    public string Username { get; set; }
    public string DisplayName { get; set; }
}
  • Use el comando Add-Migration AddUser para aplicar el scaffolding a una migración nueva con el fin de aplicar los cambios a la base de datos.
  • Ejecute el comando Update-Database para aplicar la nueva migración a la base de datos.

La nueva tabla se agrega a la base de datos:

Schema With Users

La lista completa de anotaciones admitidas por EF es la siguiente:

7. API fluida

En la sección anterior, analizamos el uso de anotaciones de datos para complementar o invalidar lo que se detectó por convención. La otra manera de configurar el modelo es a través de la API fluida de Code First.

La mayor parte de la configuración del modelo se puede realizar mediante anotaciones de datos simples. La API fluida es una manera más avanzada de especificar la configuración del modelo y cubre todo lo que las anotaciones de datos pueden hacer, además de alguna configuración más avanzada que no es posible con las anotaciones de datos. Las anotaciones de datos y la API fluida se pueden usar juntas.

Para acceder a la API fluida, invalide el método OnModelCreating en DbContext. Supongamos que queremos cambiar el nombre de la columna en la que se almacena User.DisplayName en display_name.

  • Invalide el método OnModelCreating en BloggingContext con el código siguiente
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .Property(u => u.DisplayName)
            .HasColumnName("display_name");
    }
}
  • Use el comando Add-Migration ChangeDisplayName para aplicar el scaffolding a una migración nueva con el fin de aplicar los cambios a la base de datos.
  • Ejecute el comando Update-Database para aplicar la nueva migración a la base de datos.

Ahora se cambia el nombre de la columna DisplayName a display_name:

Schema With Display Name Renamed

Resumen

En este tutorial hemos visto el desarrollo de Code First mediante una nueva base de datos. Hemos definido un modelo mediante clases y lo hemos usado para crear una base de datos y almacenar y recuperar datos. Una vez creada la base de datos, hemos usado Migraciones de Code First para cambiar el esquema a medida que evolucionaba el modelo. También hemos visto cómo configurar un modelo mediante anotaciones de datos o la API fluida.