The previous chapter was about ADO.NET. ADO.NET enables .NET programmers to work with relational data. ADO.NET is an effective tool for working with data but is not necessarily good in developer efficiency. Microsoft introduced the Entity Framework to help with the developer efficiency.

EF allows interacting with data from relational databases using an object model that maps to the business objects (or domain objects) in your application. You can operate on a collection of strongly typed objects called entities. They are held in specialized collection classes that are LINQ aware. The collection classes provide querying against the data store using the LINQ grammar.

EF also provides efficiencies like state tracking, unit of work operations, and intrinsic transaction support.

Entity Framework Core is a complete rewrite of Entity Framework 6. EF Core can be used to scaffold entity classes and a derived DbContext from an existing database, or it can be used to create and update the database from entity classes and derived DbContext.

Object-Relational Mappers


Object-relational mapping frameworks in .NET manage the bulk of CRUD data access tasks for the developer. The developer creates a mapping between the .NET objects and the relational database. The ORM manages connections, query generation, change tracking, and persisting the data. ORMs can introduce performance and scaling issues if used improperly. Use ORMs for CRUD operations and the database for set-based operations.

The different ORMs have slight differences in how they operate and are used but have essentially the same parts. Entities are classes that are mapped to the database tables. A specialized collection type contains one or more entities. A change tracking mechanism tracks the state of the entities and any changes, additions, or deletions made to them. A central construct controls operations as the ringleader.

Understanding the Role of the Entity Framework Core


EF Core uses ADO.NET under the hood. EF Core is best used in forms-over-data (or API-over-data) situations. It is not very well suited for large-scale data operations such as extract-transform-load (ETL) data warehous applications or large reporting situations.

The Building Blocks of the Entity Framework


The main components of EF Core are DbContext, ChangeTracker, the DbSet specialized collection type, the database providers, and the application's entities. The common functionality for EF Core is provided by the Microsoft.EntityFrameworkCore package. Microsoft.EntityFrameworkCore.Design is required for the EF Core command-line tools.

A GlobalUsings.cs might look like the following:

global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.ChangeTracking;
global using Microsoft.EntityFrameworkCore.Design;
global using Microsoft.EntityFrameworkCore.Metadata;
global using Microsoft.EntityFrameworkCore.Metadata.Builders;

global using System.ComponentModel.DataAnnotations;
global using System.ComponentModel.DataAnnotations.Schema;

The DbContext Class


The DbContext class is the ringleader component of EF Core and provides access to the database through the Database property. DbContext manages the ChangeTracker instance, exposes the virtual OnModelCreating() method for access to the Fluent API, holds all the DbSet<T> properties, and supplies the SaveChanges method to persist data to the data store. It is not used directly, but through a custom class that inherits DbContext. It is in the derived class that the DbSet<T> properties are placed.





Creating a Derived DbContext


The first step in EF Core is to create a custom class that inherits from DbContext. Then add a constructor that accepts a strongly typed instance of DbContextOptions and passes the instance through to the base class. This is an example ApplicationDbContext.cs file:

namespace AutoLot.Samples;

public class ApplicationDbContext : DbContext
{
  public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    : base(options)
  {
  }
}

Configuring the DbContext


The DbContext instance is configured using an instance of the DbContextOptions class, which is created using the DbContextOptionsBuilder. This allows selecting the database provider (and any provider-specific settings) and EF COre DbContext general options. The DbContextOptions instance is injected into the base DbContext at runtime.

The Design-Time DbContext Factory


The design-time DbContext factory is a class that implements the IDesignTimeDbContextFactory<T> interface where T is the derived DbContext class. The interface has one method: CreateDbContext(). You must implement this method to create an instance of your derived DbContext. This is only meant to be used during development.

namespace AutoLot.Samples;

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
  public ApplicationDbContext CreateDbContext(string[] args)
  {
    var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
    var connectionString = @"..."
    optionsBuilder.UseSqlServer(connectionString);
    Console.WriteLine(connectionString);
    return new ApplicationDbContext(optionsBuilder.Options);
  }
}

OnModelCreating


The base DbContext class exposes the OnModelCreating method that is used to shape your entities using the Fluent API. For now, note that this will involve the following code in the derived DbContext class:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  // Fluent API calls go here
}

Saving Changes


Call SaveChanges() (or SaveChangesAsync()) on the derived DbContext to persist any changes to entities. This wraps the database calls in an implicit transaction.

Transaction and Save Point Support


For more control, you can enlist the derived DbContext into an explicit transaction.

Explicit Transactions and Execution Strategies


Saving/Saved Changes Events


The SavingChanges event fires when SaveChanges() is called but before the SQL statements are executed. SavedChanges fires after SaveChanges() has completed.

The DbSet<T> Class


For each entity type (T) in your object model, add a property of type DbSet<T> to the derived DbContext class. The DbSet<T> class is a specialized collection property used to interact with the database provider to read, add, update, or delete records in the database. Each DbSet<T> provides a number of core services for the database interactions, including translating LINQ queries against a DbSet<T> into the database queries.



The DbSet<T> type implements IQueryable<T> which enables the use of LINQ queries to retrieve records from the database. DbSet<T> also supports extension methods you would have learned in Chapter 13 like ForEach(), Select(), and All().

It is much more common to use the methods on the DbSet<T> properties than the more general methods on the derived DbContext.

The ChangeTracker


The ChangeTracker instance tracks the state for objects loaded into DbSet<T> within a DbContext instance.



If you need to check the state of an object:

EntityState state = context.Entry(entity).State;

ChangeTracker Events


There are two events that can be raised by ChangeTracker. The first is StateChanged and the second is Tracked. The StateChanged event fires when an entity's state is changed, but not when an entity is first tracked. The Tracked event fires when an entity starts being tracked.

The constructor of the derived DbContext class can be updated to specify event handlers for the StateChanged and Tracked events:

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  : base(options)
{
...
  ChangeTracker.StateChanged += ChangeTracker_StateChanged;
  ChangeTracker.Tracked += ChangeTracker_Tracked;
}

The StateChanged Event


This example shows how to write to the console anytime an entity is updated:

private void ChangeTracker_StateChanged(object sender, EntityStateChangedEventArgs e)
{
  if (e.OldState == EntityState.Modified && e.NewState == EntityState.Unchanged)
  {
    Console.WriteLine($"An entity of type {e.Enttry.Entity.GetType().Name} was updated.");
  }
}

The Tracked Event


Resetting DbContext State


EF Core 5 added the ability to reset a derived DbContext back to its original state. The ChangeTracker.Clear() method clears out all entities from the DbSet<T> collections by setting their state to Detached. The main benefit of this is to improve performance.

Entities


The strongly typed classes that map to database tables are officially called entities. The collection of entities in an application comprises a conceptual model of a physical database. This model is termed an entity data model but is usually referred to as the model. Entities do not need to map directly to the database schema.

Entity Properties and Database Columns


EF Core uses data from a table's columns to populate an entity's properties when reading from the data store and writes from the entity's properties to a table's columns when persisting data. If the property is an automatic property, EF Core reads and write through the getter and setter. If the property has a backing field, EF Core will read and write to the backing field instead of the public property, even if the backing field is private.

Table Mapping Schemes


There are two class to table mapping schemes available in EF Core: table-per-hierarchy (TPH) and table-per-type (TPT). TPH mapping is the default and maps an inheritance hierarchy to a single table. TPT maps each class in the hierarchy to its own table.

Classes can also be mapped to views and raw SQL queries. These are called query types.

Table-Per-Hierarchy Mapping


The entire hierarchy becomes a single table.

To make EF Core aware that an entity class is part of the object model, add a DbSet<T> property for the entity.

public class ApplicationDbContext : DbContext
{
  public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    : base(options) { }
  public DbSet<Car> Cars { get; set; }
}

The DbSet<T> property in the ApplicationDbContext class informs EF Core that the Car class maps to the Cars table in the database.

Table-per-Type Mapping


Since TPH is the default, EF Core must be instructed to map each class to a table. This can be done with data annotations or the Fluent API. The following Fluent API code specifies using the TPT mapping scheme:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<BaseEntity>().ToTable("BaseEntities");
  modelBuilder.Entity<Car>().ToTable("Cars");
}

Table-per-type mapping can have significant performance implications that should be considered before its use. See the docs.

Navigation Properties and Foreign Keys


Navigation properties represent how entity classes relate to each other and enable code to traverse from one entity instance to another. By definition, a navigation property is any property that maps to a nonscalar type as defined by the database provider. In practice, navigation properties map to another entity (called reference navigation properties) or a collection of another entity (called collection navigation properties). On the database side, navigation properties are translated into foreign key relationships between tables. EF Core directly supports one-to-one, one-to-many, and many-to-many relationships. Entity classes can also have navigation properties that point back to themselves.



Missing Foreign Key Properties


If an entity with a reference navigation property deos not have a property for the foreign key value, EF Core will create a shadow foreign key property.

One-to-Many Relationships


The principal (one side) adds a collection property of the dependent entity class (the many side). The dependency entity should also have properties for the foreign key back to the principal.

public abstract class BaseEntity
{
  public int Id { get; set; }
  public byte[] TimeStamp { get; set; }
}

public class Make : BaseEntity
{
  public string Name { get; set; }
  public IEnumerable<Car> Cars { get; set; } = new List<Car>();
}

public class Car : BaseEntity
{
  public string Color { get; set; }
  public string PetName { get; set; }
  public int MakeId { get; set; }
  public Make MakeNavigation { get; set; }
}

Adding the suffix Navigation to the reference navigation properties for clarity is a style decision.

The following properties must be added to the ApplicationDbContext:

public DbSet<Car> Cars { get; set; }
public DbSet<Make> Makes [ get; set; }

One-to-One Relationships


In one-to-one relationships, both entities have a reference navigation property to the other entity. EF Core must be informed which side is the principal entity for a one-to-one relationship. This can be done by having a clearly defined foreign key to the principal entity or by indicating the principal using the Fluent API.

public class Radio : BaseEntity
{
  public bool HasTweeters { get; set; }
  public bool HasSubWoofers { get; set; }
  public string RadioId { get; set; }
  public int CarId { get; set; }
  public Car CarNavigation { get; set; }
}

public class Car : BaseEntity
{
  public Radio RadioNavigation { get; set; }
}

Car here is the principal entity because Radio has a foreign key to the Car class.

This must be added to the ApplicationDbContext class:

public DbSet<Radio> Radios { get; set; }

Many-to-Many Relationships


Both entities have a collection property to the other entity. This is implemented in the data store with a join table between the two entity tables. The name can be changed programmatically through the Fluent API. The join entity has one-to-many relationships to each of the entity tables.

public class Driver : BaseEntity
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public IEnumerable<Car> Cars { get; set; } = new List<Car>();
}

public class Car : BaseEntity
{
  public string Color { get; set; }
  public string PetName { get; set; }
  public int MakeId { get; set; }
  public Make MakeNavigation { get; set; }
  public Radio RadioNavigation { get; set; }
  public IEnumerable<Driver> Drivers { get; set; } = new List<Driver>();
}

Many-to-Many Prior to EF Core 5


The three tables can also be created explicitly and it must be done this way in EF Core versions earlier than EF Core 5. An abbreviated example:

public class Driver
{
...
  public IEnumerable<CarDriver> CarDrivers { get; set; }
}

public class Car
{
...
  public IEnumerable<CarDriver> CarDrivers { get; set; }
}

public class CarDriver
{
  public int CarId {get;set;}
  public Car CarNavigation {get;set;}
  public int DriverId {get;set;}
  public Driver DriverNavigation {get;set;}
}

Cascade behavior


Optional Relationships


Required Relationships


Entity Conventions


The conventions are always enabled unless overruled by data annotations or code in the Fluent API.



Mapping Properties to Columns


By convention, the public read-write properties map to columns of the same name. The data type matches the data store's equivalent of the property's CLR data type. Non-nullable properties are set to not null in the data store, and nullable properties (including nullable reference types) are set to allow null.

Overriding EF Core Conventions


New in EF Core 6, the conventions can be overriden using the ConfigureConventions() method. For example, to make string properties default to a certain size:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
  configurationBuilder.Properties<string>().HaveMaxLength(50);
}

Entity Framework Data Annotations


Data annotations are C# attributes that are used to further shape your entities. Data annotations override any conflicting conventions.





Annotations and Navigation Properties


The ForeignKey annotation lets EF Core know which property is the backing field for the navigation property. The InverseProperty informs EF Core of how the entities related by indicating the navigation property on the other end. It also makes the code more readable.

The Fluent API


The Fluent API configures the application entities through C# code. The methods are exposed by the ModelBuilder instance available in the DbContext OnModelCreating() method. The Fluent API is the most powerful of the configuration methods and overrides any conventions or data annotations taht are in conflict.

Class and Property Methods


The Fluent API is a superset of the data annotations when shaping your individual entities.

Class and Property Mapping


This is an example using data annotations:

[Table("Inventory", Schema="dbo")]
[Index(nameof(MakeId), Name = "IX_Inventory_MakeId")]
public class Car : BaseEntity
{
  private string_color;
  [Required, StringLength(50)]
  public string Color
  {
    get => _color;
    set => _color = value;
  }
  [Required, StringLength(50)]
  public string PetName { get; set; }
  public int MakeId { get; set; }
  [ForeignKey(nameof(MakeId))]
  public Make MakeNavigation { get; set; }
  public Radio RadioNavigation { get; set; }
  [InverseProperty(nameof(Driver.Cards))]
  public IEnumerable<Driver> Drivers { get; set; }
}

This is the Fluent API equivalent of changing the table name:

modelBuilder.Entity<Car>(entity =>
{
  entity.ToTable("Inventory", "dbo");
});

Keys and Indices


To set the primary key for an entity, use the HasKey() method:

modelBuilder.Entity<Car>(entity =>
{
  entity.ToTable("Inventory","dbo");
  entity.HadKey(e=>e.Id);
});

This can be used to create a composite key too. The process is the same for creating indices except it uses the HasIndex() Fluent API method.

To make the index unique, use the IsUnique() method.

entity.HasIndex(e => e.MakeId, "IX_Inventory_MakeId").IsUnique();

Field Size and Nullability


Properties are configured by selecting them using the Property() method and then using additional methods to configure the property.

modelBuilder.Entity<Car>(entity =>
{
...
  entity.Property(e => e.Color)
    .IsRequired()
    .HasMaxLength(50);
  entity.Property(e => e.PetName)
    .IsRequired()
    .HasMaxLength(50);
});

Default Values


HasDefaultValue() can set the default value for a column. HasDefaultValueSql() can set the value to a database function.

RowVersion/Concurrency Tokens


SQL Server Sparse Columns


Computed Columns


Check Constraints


One-to-Many Relationships


To use the Fluent API to define one-to-many relationships, pick one of the entities to update. Both sides of the navigation chain are set in one block of code.

One-to-One Relationships


These are configured in the same way expect that WithOne() is used instead of WithMany().

Many-to-Many Relationships


Many-to-many relationships are much more customizable with the Fluent API. The foreign key field names, index names, and cascade behavior can all be set in the statements that define the relationship. It also allows for specifying the pivot table directly allowing for additional fields to be added and for simplified querying.

Excluding Entities from Migrations


Using IEntityTypeConfiguration Classes


Conventions, Annotations, and the Fluent API, Oh My!


 At this point in the chapter, you might be wondering which of the three options to use to shape your entities and their relationship to each other and the data store. The answer is all three. The conventions are always active (unless you override them with data annotations or the Fluent API). The data annotations can do almost everything the Fluent API methods can do and keep the information in the entity class themselves, which can increase code readability and support. The Fluent API is the most powerful of all three. Whether you use data annotations or the Fluent API, know that data annotations overrule the built-in conventions, and the methods of the Fluent API overrule everything.

Owned Entity Types

Article notes

What is an effective tool that allows .NET programmers to work with relational data but is not necessarily good in developer efficiency?
What did Microsoft introduce since ADO.NET was not good enough in developer efficiency?
What are the strongly typed objects held in specialized LINQ aware collection classes operated on in Entity Framework called?
What data annotation in EF declares a property that is used as the foreign key for a navigation property?
What data annotation in EF declares a property as not nullable in the database?
What data annotation lets EF Core know which property is the backing field for a navigation property?
What data annotation informs EF Core of how entities are related by indicating the navigation property on the other end, and also makes the code more readable?
What data annotation in EF excludes a property or class in regard to database fields and tables?
What data annotation in EF declares the navigation property on the other end of a relationship?
In a one-to-many relationship in EF, the one side entity is the principal or dependent entity?
In a one-to-many relationship in EF, the many side entity is the principal or dependent entity?
What class is the ringleader component of EF Core?
What property of the DbContext class provides access to the database?
What class in EF Core holds all of the DbSet<T> properties?
What class in EF Core provides the SaveChanges() method that persists changes to the data store?
What member of DbContext saves all entity changes to the database (in a transaction) and returns the number of records affected?
What three members of DbContext can add, update, and remove entity instances, respectively, and also are usually called directly on the DbSet<T> properties?
What is the member of DbContext that provides access to information and operations for entity instances that the DbContext is tracking?
What is the state of an EF entity that is being tracked but does not yet exist in the database?
What is the state of an EF entity that is being tracked and is marked for deletion from the database?
What is the state of an EF entity that is not being tracked by the change tracker?
What is the state of an EF entity that is being tracked and has been changed?
What is the state of an EF entity that is being tracked, exists in the database, and has not been modified?
What is the recommended way (a type) to configure the DbContext instance at runtime?
What is the recommended way (a type) to configure the DbContext instance at design time?
What is the member of DbContext that is called when a model has been initialized, but before it has been finalized, and is where methods from the Fluent API are used to finalize the shape of the model?
What is the first thing or step to do in EF Core?
What method does the DbContext class expose that is used to shape your entities using the Fluent API?
What class is a specialized collection property used to interact with the database provider to read, add, update, and delete records in the database?
What interface does the DbSet<T> implement which enables the use of LINQ queries to retrieve records from the database?
What are the strongly typed classes that map to database tables called in EF?
How is the conceptual model of a physical database in Entity Framework (the entity data model) commonly referred to?
What are the two class to table mapping schemes available in EF Core?
What are, by definition, properties that map to a nonscalar type as defined by the database provider?
The Pro .NET book recommends ORMs for what type of operations?
The Pro .NET book recommends relying on the database for what type of operations?
What are the three types which are the most prominent main types in EF?
What type in EF manages the instance of ChangeTracker, exposes the virtual OnModelCreating() method, holds the DbSet<T> properties, and supplies the SaveChanges() method?
What member of DbContext has metadata about the shape of entities, the relationships between them, and how they map to the database (usually not used directly)?
What member of DbContext provides access to information and operations for entity instances that the specific DbContext is tracking?
LINQ queries against DbSet<T> properties in EF are translated into what?
What happens when the ChangeTracker.Clear() method is used in EF?
What approach when building a new app or adding EF Core into an existing application is when you create and configure your entity classes and the derived DbContext in code and then use migrations to update the database?
What approach when building a new app or adding EF Core into an existing application is when you scaffold the classes from an existing database?
What is the EF Core tooling that is a global CLI tool with the commands needed to scaffold existing databases into code, to create/remove database migrations, and to operate on a database?
What method, called when an EF model has been initialized, but not finalized yet, is where methods from the Fluent API are placed to finalize the shape of the model?
What did ADO.NET lack that made Microsoft introduce Entity Framework?
What does this line of code in a class derived from DbContext basically specify? public DbSet<Car> Cars { get; set; }
What in EF is a navigation property that maps to another entity (not a collection)?
Data retrieval queries in EF Core are created with LINQ queries written against properties of what type?
What in EF is a navigation property that maps to a collection of entities?
What in EF refers to DbSet<T> collections that are used to represent views, SQL statements, or tables without a primary key?
What method can you chain on to this to cause the database to be queried immediately? var cars = context.Cars.Where(x=>x.Color == "Yellow");
What do you need to do in between creating a record in code and calling SaveChanges() on the context in order to add the record to the database in EF?
Previous Next