Entity Framework: primer ejemplo

Definamos un modelo muy simple usando clases. Solo los estamos definiendo en el archivo Program.cs, pero en una aplicación del mundo real dividirá sus clases en archivos separados y potencialmente en un proyecto separado. A continuación se muestra un modelo de datos que crearemos utilizando el enfoque Code First.

Crear modelo

Agregue las siguientes tres clases en el archivo Program.cs usando el siguiente código para la clase Student.

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}
  • La propiedad ID se convertirá en la columna de clave principal de la tabla de la base de datos que corresponde a esta clase.

  • La propiedad Inscripciones es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad.

  • En este caso, la propiedad Inscripciones de una entidad de Estudiante contendrá todas las entidades de Inscripción que están relacionadas con esa entidad de Estudiante.

  • Las propiedades de navegación generalmente se definen como virtuales para que puedan aprovechar ciertas funciones de Entity Framework, como la carga diferida.

  • Si una propiedad de navegación puede contener varias entidades (como en las relaciones de muchos a muchos o de uno a muchos), su tipo debe ser una lista en la que se puedan agregar, eliminar y actualizar entradas, como ICollection.

A continuación se muestra la implementación para la clase del curso.

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

La propiedad Inscripciones es una propiedad de navegación. Una entidad del curso puede estar relacionada con cualquier número de entidades de inscripción.

A continuación se muestra la implementación de la clase de inscripción y la enumeración.

public enum Grade {
   A, B, C, D, F
}

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}
  • La propiedad EnrollmentID será la clave principal.

  • La propiedad Grade es una enumeración. El signo de interrogación después de la declaración del tipo de calificación indica que la propiedad de calificación es anulable.

  • Una calificación nula es diferente de una calificación cero. Nulo significa que no se conoce una calificación o aún no se ha asignado.

  • Las propiedades StudentID y CourseID son claves externas y las propiedades de navegación correspondientes son Student y Course.

  • Una entidad de inscripción está asociada con un estudiante y una entidad de curso, por lo que la propiedad solo puede contener una única entidad de estudiante y curso.

Crear contexto de base de datos

La clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos dado es la clase de contexto de la base de datos que permite consultar y guardar datos. Puede crear esta clase derivando de la clase DbContext y exponiendo un DbSet escrito para cada clase en nuestro modelo. A continuación se muestra la implementación en la clase MyContext, que se deriva de la clase DbContext.

public class MyContext : DbContext {
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

A continuación se muestra el código completo en el archivo Program.cs.

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCodeFirstDemo {

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

   public enum Grade {
      A, B, C, D, F
   }

   public class Enrollment {
      public int EnrollmentID { get; set; }
      public int CourseID { get; set; }
      public int StudentID { get; set; }
      public Grade? Grade { get; set; }
		
      public virtual Course Course { get; set; }
      public virtual Student Student { get; set; }
   }

   public class Student {
      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
      public DateTime EnrollmentDate { get; set; }
		
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

   public class Course {
      public int CourseID { get; set; }
      public string Title { get; set; }
      public int Credits { get; set; }
		
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

   public class MyContext : DbContext {
      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
   }

}

El código anterior es todo lo que necesitamos para comenzar a almacenar y recuperar datos. Agreguemos algunos datos y luego los recuperemos. A continuación se muestra el código del método principal.

static void Main(string[] args) {

   using (var context = new MyContext()) {
      // Create and save a new Students
      Console.WriteLine("Adding new students");

      var student = new Student {
         FirstMidName = "Alain", LastName = "Bomer", 
            EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
      };

      context.Students.Add(student);
		
      var student1 = new Student {
         FirstMidName = "Mark", LastName = "Upston", 
            EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
      };

      context.Students.Add(student1);
      context.SaveChanges();

      // Display all Students from the database
      var students = (from s in context.Students 
         orderby s.FirstMidName select s).ToList<Student>();

      Console.WriteLine("Retrieve all Students from the database:");

      foreach (var stdnt in students) {
         string name = stdnt.FirstMidName + " " + stdnt.LastName;
         Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
      }
		
      Console.WriteLine("Press any key to exit...");
      Console.ReadKey();
   }
}

Cuando se ejecuta el código anterior, recibirá el siguiente resultado.

Adding new students
Retrieve all Students from the database:
ID: 1, Name: Alain Bomer
ID: 2, Name: Mark Upston
Press any key to exit...

Ahora, la pregunta que me viene a la mente es dónde están los datos y la base de datos en la que hemos agregado algunos datos y luego los recuperamos de la base de datos. Por convención, DbContext ha creado una base de datos para usted.

  • Si una instancia local de SQL Express está disponible, Code First ha creado la base de datos en esa instancia.

  • Si SQL Express no está disponible, Code First intentará utilizar LocalDb.

  • La base de datos recibe el nombre del nombre completo del contexto derivado.

En nuestro caso, la instancia de SQL Express está disponible y el nombre de la base de datos es EFCodeFirstDemo.MyContext como se muestra en la siguiente imagen.

  • Estas son solo las convenciones predeterminadas y hay varias formas de cambiar la base de datos que utiliza Code First.

  • Como puede ver en la imagen de arriba, ha creado tablas de Estudiantes, Cursos e Inscripciones y cada tabla contiene columnas con el tipo de datos y la longitud adecuados.

  • Los nombres de las columnas y el tipo de datos también coinciden con las propiedades de las respectivas clases de dominio.

Inicialización de la base de datos

En el ejemplo anterior, hemos visto que Code First crea una base de datos automáticamente, pero si desea cambiar el nombre de la base de datos y el servidor, veamos cómo Code First decide el nombre de la base de datos y el servidor mientras inicializa una base de datos. Eche un vistazo al siguiente diagrama.

Puede definir el constructor base de la clase de contexto de las siguientes formas.

  • Sin parámetro
  • Nombre de la base de datos
  • Nombre de la cadena de conexión

Sin parámetro

Si especifica el constructor base de la clase de contexto sin ningún parámetro como se muestra en el ejemplo anterior, el marco de la entidad creará una base de datos en su servidor SQLEXPRESS local con un nombre {Espacio de nombres}. {Nombre de clase de contexto}.

En el ejemplo anterior, la base de datos que se crea automáticamente tiene el nombre EFCodeFirstDemo.MyContext. Si observa el nombre, encontrará que EFCodeFirstDemo es el espacio de nombres y MyContext es el nombre de la clase de contexto, como se muestra en el siguiente código.

public class MyContext : DbContext {
   public MyContext() : base() {}

   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

Nombre de la base de datos

Si pasa el nombre de la base de datos como parámetro en un constructor base de la clase de contexto, Code First volverá a crear una base de datos automáticamente, pero esta vez el nombre será el que se pasó como parámetro en el constructor base en el servidor de base de datos local SQLEXPRESS. .

En el siguiente código, MyContextDB se especifica como parámetro en el constructor base. Si ejecuta su aplicación, la base de datos con el nombre MyContextDB se creará en su servidor SQL local.

public class MyContext : DbContext {
   public MyContext() : base("MyContextDB") {}
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

Nombre de la cadena de conexión

Esta es una manera fácil de decirle a DbContext que use un servidor de base de datos que no sea SQL Express o LocalDb. Puede optar por poner una cadena de conexión en su archivo app.config.

  • Si el nombre de la cadena de conexión coincide con el nombre de su contexto (con o sin calificación de espacio de nombres), DbContext lo encontrará cuando se utilice el constructor sin parámetros.

  • Si el nombre de la cadena de conexión es diferente del nombre de su contexto, entonces puede decirle a DbContext que use esta conexión en el modo Code First pasando el nombre de la cadena de conexión al constructor DbContext.

public class MyContext : DbContext {
   public MyContext() : base("name = MyContextDB") {}
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}
  • En el código anterior, el fragmento de la cadena de conexión de la clase de contexto se especifica como parámetro en el constructor base.

  • El nombre de la cadena de conexión debe comenzar con "name =" de lo contrario, lo considerará como un nombre de base de datos.

  • Este formulario hace explícito que espera que la cadena de conexión se encuentre en su archivo de configuración. Se lanzará una excepción si no se encuentra una cadena de conexión con el nombre dado.

<connectionStrings>
   <add name = "MyContextDB"
      connectionString = "Data Source =.;Initial Catalog = EFMyContextDB;Integrated Security = true"
      providerName = "System.Data.SqlClient"/>
</connectionStrings>
  • El nombre de la base de datos en la cadena de conexión en app.config es EFMyContextDB. CodeFirst creará un nuevoEFMyContextDB base de datos o utilizar existente EFMyContextDB base de datos en SQL Server local.

Clases de dominio

Hasta ahora, hemos dejado que EF descubra el modelo utilizando sus convenciones predeterminadas, pero habrá ocasiones en las que nuestras clases no sigan las convenciones y necesitemos poder realizar una configuración adicional. Pero puede anular estas convenciones configurando sus clases de dominio para proporcionar a EF la información que necesita. Hay dos opciones para configurar sus clases de dominio:

  • Anotaciones de datos
  • API fluida

Anotaciones de datos

DataAnnotations se utiliza para configurar sus clases que resaltarán las configuraciones más comúnmente necesarias. Las anotaciones de datos también son entendidas por una serie de aplicaciones .NET, como ASP.NET MVC, que permiten que estas aplicaciones aprovechen las mismas anotaciones para las validaciones del lado del cliente.

A continuación se muestran las anotaciones de datos utilizadas en la clase de los estudiantes.

public class Enrollment {

   [Key]
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }

   [ForeignKey("CourseID")]
   public virtual Course Course { get; set; }

   [ForeignKey("ID")]
   public virtual Student Student { get; set; }
}

API fluida

La mayor parte de la configuración del modelo se puede realizar mediante anotaciones de datos simples. La API fluida es una forma avanzada de especificar la configuración del modelo que cubre todo lo que pueden hacer las anotaciones de datos, 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, anula el método OnModelCreating en DbContext. Ahora cambiemos el nombre de la columna en la tabla de estudiantes de FirstMidName a FirstName como se muestra en el siguiente código.

public class MyContext : DbContext {

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
         .HasColumnName("FirstName");
   }

   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}