infuerno.github.io

Pluralsight: Getting Started with Entity Framework 6

Notes

Resources

Overview

Basic EF Workflow

Model options

  1. EDMX Model - Visual model defined using a designer in VS -> this creates an XML file, the EDMX -> the designer then generates classes from the EDMX
  2. Code-based Model - model directly written in code

If an EDMX exists, EF reads this and generates an in memory model. This in memory model is the representation of how the domain classes map to the database tables Instead if a model in code exists, EF will use its Code First API to generate the same in memory model Having generated the in memory model everything following is the same.

Model Creation options

  1. Database First with EDMX. Reverse engineer from an existing database to EDMX. EDMX is updateable when the database schema is updated. (Can also reverse engineer to Code, but this is a one way process. Really just a different way to start with a Code-First approach).
  2. Model First with EDMX. Use the designer to write the model (EDMX) and then generate the database. However, you can migrate the database by changing the model.
  3. Code First (most popular). Either write from scratch or reverse engineer a database. EF then has the option to determine the model and the database schema and then create the SQL to create the database. This allows generation of migrations to migrate the database when models or mappings change. Using code, EF uses conventions to infer the database schema, but mappings can be used to override the conventions.

Architecture

Using a DBContext, EF defines which domain objects it will work with in a particular model. There may be more than one DBContext defined model. EF logic is written and executed in a data logic layer, e.g. queries and calls to SaveChanges. The DBContext then executes queries, tracks changes, performs updates etc, triggered by the code in the data logic layer The domain objects and other business logic code does not need to be aware of EF at all. Only the data logic layer needs a reference to the EntityFramework.dll

EF7

Complete rewrite. Not backwards compatible. Different features e.g. no EDMX model.

Creating a Code Based Model and Database

  1. Create a domain project and domain classes e.g. Ninja, Clan, NinjaEquipment
  2. Create a datamodel project. Create a dbcontext e.g. NinjaContext by inheriting from System.Data.Entity.DbContext. This will orchestrate all the interactions with the database.
  3. When defining a DbContext, need to define what DbSets are in the model. Add a DbSet for each type that will be maintained by the DbContext. The DbSet IS a repository (repository pattern) and is responsible for maintaining an in memory collection of types. Queries are performed by way of the DbSets. These entities can then be queried and persisted directly. It is possible to have entities in the model which aren’t part of a DbSet which are reached by relationships to entities which are in a DbSet [ADVANCED].

At runtime, .NET will check the class definitions of all classes included in the DbSets in the DbContext and infer a model as well as infer how that model relates to a database schema. Use Entity Framework Power Tools to view the same model in advance (right click on context file for menu).

Where there are “foreign keys” defined between domain classes via explicit integer keys, EF will create an 1 to 1 or 1 to many relationship. However if the relationship is via a class which could be nullable, this will be created as a 0 to 1 or 0 to many relationship by default. Use Fluent API in your EF DbContext to sort out, or add the [Required] attribute to your domain classes.

NOTE. Not having explicity FK int relationships can become a problem when using EF with disconnected web apps.

Code First database creation and migrations

There are a number of ways to migrate a database from the model. Some are automated and happen at runtime - useful for running unit tests. Design time Code First migrations feature gives the most consistency and control. The Migrations API can detect changes between the model and an existing database, describe these schema changes and then generate the equivalent SQL.

Issue the Enable-Migrations command in the package manager console. This creates a folder called Migrations and a Configuration.cs class.

There are 3 steps to migrating the database:

  1. Define / update the model
  2. Create the migration file: Add-Migration Initial
  3. Apply to the database
    • Update-Database -Script will simply show the script to be run on update
    • Update-Database -Verbose will run the migration on the database, giving verbose output

By default the database will be created locally using mssqllocaldb using the namespace and context names to name the database

Using EF to interact with the database

Database initialisation

Control the initialisation of the database e.g Create if not exists or turn off in OnModelCreating: Database.SetInitializer(new NullDatabaseInitializer<NinjaContext>());

  <appSettings>
    <add key="DatabaseInitializerForType Pluralsight.Ef6gs.Ninja.DataModel.NinjaContext, Pluralsight.Ef6gs.Ninja.DataModel"
         value="Disabled" />
  </appSettings>

See: https://www.entityframeworktutorial.net/code-first/database-initialization-strategy-in-code-first.aspx

Log generated SQL

ctx.Database.Log = Console.WriteLine;

Triggering queries to execute

Updates in disconnected applications

using (var ctx = new NinjaContext()) {
  context.Ninjas.Attach(ninja); // tell EF about the entity - actually this part is not required when using the following line
  context.Entry(ninja).State = EntityState.Modified;
  context.SaveChanges(); // this will update EVERY property (unlike connected apps where EF knows which properties have changed)
}

Useful DbSet methods

Deletes in disconnected applications

Adding child objects to a parent object when the parent object is already tracked by EF will result in the child objects ALSO being tracked by EF AND having the same state.

If the child objects are already known to EF, their state will be retained.

ctx.Ninjas.Add(ninja);
// will automatically be tracked by EF despite not being explicitly added to the context
ninja.EquipmentOwned.Add(new NinjaEquipment() {Name = "Muscles", Type = EquipmentType.Tool});
ninja.EquipmentOwned.Add(new NinjaEquipment() {Name = "Sword", Type = EquipmentType.Weapon});

In this example, EF will initially insert the first Ninja and then do a SELECT to get the ID of the newly inserted record. It will use this ID to insert the 2 equipment records. The 4 SQL queries are done in a single transaction and a single connection.

Eager loading

Useful if you know you want to get all related records.

Explicit loading

Useful if you only want to get a few related records.

  var ninja = ctx.Ninjas.FirstOfDefault(n => n.Name == "jun");
  ctx.Entry(ninja).Collection(n => n.EquipmentOwned).Load();

Lazy loading

WARNING: use with caution.

If you mark a navigation propery with the keyword virtual then properties can be lazy loaded simply by referencing a child object. However if iterating data and referencing child properties this MAY result in a call to the database for each child property (rather than just one call up front when using Include).

public class Ninja
{
    // ...
    public virtual List<NinjaEquipment> EquipmentOwned { get; set; }
}


var ninja = ctx.Ninjas.FirstOrDefault(n => n.Name == "Mai" && n.Id != 3);
// at this point the EquipmentOwned count is 0
Console.WriteLine($"{ninja.Name} owns {ninja.EquipmentOwned.Count}");
// checking the SQL queries made shows an extra SELECT in order to get the count

Projection queries

Use projections to return subsets of properties of related entities. From related objects can request the whole object OR aggregate properties e.g. Count of a collection type.

// selects all EquipmentOwned objects
var ninjas = ctx.Ninjas
    .Select(n => new {n.Name, n.EquipmentOwned})
    .ToList();

// selects aggregates for EquipmentOwned objects
// since Clan is a single object CAN select single properties
var ninjas = ctx.Ninjas
    .Select(n => new {n.Name, EquipmentCount = n.EquipmentOwned.Count, ClanName = n.Clan.Name})
    .ToList();

Projections usually return anonymous types

Using EF in your applications

public interface IModificationHistory {
  DateTime DateCreated { get; set; }
  DateTime DateModified { get; set; }
  bool IsDirty { get; set; }

public override int SaveChanges() {
  foreach(var history in this.ChangeTracker.Entries()
    .Where(e => e.Entity is IModificationHistory && (e.State == EntityState.Added
      || e.State == EntityState.Modified))
    .Select(e => e.EntityState as IModificationHistory)) {
      history.DateModified = DateTime.Now;
      if (history.DateCreated == DateTime.Minvalue) {
        history.DateCreated = DateTime.Now;
      }
    }
  int result = base.SaveChanges();
  foreach(var history in this.ChangeTracker.Entries()
    .Where(e => e.Entity is IModificationHistory)
    .Select(e => e.EntityState as IModificationHistory)) {
        history.IsDirty = false; // relevant for connected applications
  }
  return result;
}

EF with ASP.NET MVC and WebAPI applications

// without an FK property
// have to additionally pass in the ninja ID as well as initially get the ninja from the database
public void SaveNewEquipment(NinjaEquipment equipment, int ninjaId) {
  using(var context = new NinjaContext()) {
    var ninja = context.Find(ninjaId);
    ninja.EquipmentOwned.Add(equipment);
    context.SaveChanges();
  }
}

// again without the FK property
public void SaveUpdatedEquipment(NinjaEquipment equipment, int ninjaId) {
  using(var context = new NinjaContext()) {
    // get the whole object graph from the database
    var equipmentWithNinja = context.Equipment.Include(n => n.Ninja).FirstOrDefault(e => e.Id == equipment.Id);
    // update the fields on equipment
    context.Entry(equipmentWithNinja).CurrentValues.SetValues(equipment);
    context.SaveChanges();
  }
}