An ORM (Object Relational Mapper) acts as an interface to the Database from the business layer and translates database objects (such as tables) into plain class objects, so that the object manipulations on the classes translate into database operations.
What is Entity Framework Core?
Entity Framework Core (shortly known as EF Core) is an ORM library developed by Microsoft for .NET Framework. It is a lightweight, extensible and open-source library similar to .NET Core. It helps developers conveniently work with database objects without having need to write SQL scripts for the same.
EF Core works in tandem with datastructures such as Lists for handling sets of objects for a particular class type, which translates to a table entity. The properties of the class type translate to the columns of the type defined by the property data types.
Since .NET framework comes with LINQ (Linked Queries) on the generic types, its easy to conceptualize and visualize a query operation or a database transaction (DML / Data Manipulation operations).
In this article we shall look into configuring EF Core on a Dotnet Core application and how a table can be created and data operations are performed. While the actual Entity Framework of the .NET Framework comes with two approaches: Code-First and Database-First approach, the EF Core offers code-first approach by design.
Performing Data operations using EF Core and Code First Approach
Installing Entity Framework Core
Starting with dotnet core SDK 3.0 and above, we need to install EF Core as an additional tool on top of the core SDK using the below command:
> dotnet tool install dotnet-ef --global
This installs the EF Core toolkit globally within our machine.
Configuring EF Core in Dotnet Core
EF Core doesn’t come pre installed with any template of dotnet core, and needs to be installed using the below CLI commands:
> dotnet add package Microsoft.EntityFrameworkCore.Design
> dotnet add package Microsoft.EntityFrameworkCore.SqlServer
The first command installs EF Core CLI packages for dotnet CLI, which we shall use to create and update databases. The second one installs EF Core with library support for SQL Server provider.
There are providers available for other popular databases such as MySQL, PostgreSQL, Oracle, SQLite and so on.
Once these commands are run in any project directory, the EF Core libraries will be installed and the project will be configured to use EF Core for database operations. Next, we need to provide EF Core with connection string of the database that we’re interested in to connect to.
In general, the connection strings are placed in the appsettings.json file under the section “ConnectionStrings“, which can contain more than one connection string setting as a key value pair, similar to the example below:
"ConnectionStrings": {
"DefaultConnection": "Server=abc.mysite.net;Initial Catalog=mydevdb;Persist Security Info=False;User ID=admin;Password=abc@123;MultipleActiveResultSets=False;TrustServerCertificate=False;Connection Timeout=30;"
"DevConnection": "Server=dev.mysite.net;Initial Catalog=mydevdb;Persist Security Info=False;User ID=admin;Password=abc@123;MultipleActiveResultSets=False;TrustServerCertificate=False;Connection Timeout=30;"
}
These connection strings are available to access via the IConfiguration object which is injected into the Startup class constructor as shown below:
public class Startup
{
// configuration settings are bound from appsettings.json and are
// available under the IConfiguration as key-value dictionary
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// services definition
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>((options) => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")
);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}
}
EF Core is configured and inducted into the pipeline with a predefined connection string as shown below:
public class Startup
{
// configuration settings are bound from appsettings.json and are
// available under the IConfiguration as key-value dictionary
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// services definition
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>((options) => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")
);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}
}
The method Configuration.GetConnectionString(“DefaultConnection”) returns the connection string of the key name “DefaultConnection” from the “ConnectionStrings” section.
Here we pass in a type DatabseContext which is defined as shown below:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options) {}
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
// additional database configurations can come under here
}
protected override void OnModelCreating(ModelBuilder builder)
{
// any manipulations or processing of model data like
// initializing sample data into database etc., can be done under here
}
public DbSet<User> Users { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public string Name {get; set;}
}
In the above code snippet, the DatabaseContext class extends the DbContext class which provides the methods and constructor for passing in configurations and performing additional data processing while connecting or passing data onto the database via EF Core.
The OnConfiguring method takes in the same parameters as defined in AddDbContext in Startup.cs; an instance of DbContextOptionsBuilder. This instance can be used to define settings such as the provider, the connection string and others.
It can be noted that the EF Core looks for configuration setup using the DbContextOptionsBuilder object during induction, if not found then triggers the method OnConfiguring for settings. Hence it is required that the settings be configured either at the startup level or at the OnConfiguring method.
Then comes the property Users which is of type DbSet<User>
. It infers to the mapping of this property Users to an auto-generated databse object Users, which is of type table. And the properties defined in the type User will be translated into columns for the table Users in database.
It can also be observed that the property Id bears two attributes Key and DatabaseGenerated, which are from the namespace System.ComponentModel.DataAnnotations. These are used to define schema related attributes such as PrimaryKey, ForeignKey, Indexes and The Indentity.
In this case, the attribute Key defines that the field Id is a primary key for the table Users and the attribute DatabaseGeneratedOption.Identity defines that the field shall use auto incremented Identity for values.
Also Read: Database-First approach for Existing Databases using EF Core
Code First Approach with EF Core
Now that the types and configurations are done, when we run the code the schema will not be generated onto the database. Why? Because we need to instruct the EF Core to treat these as changes (also called as Migrations) and direct the ORM to replicate these to the databse, which we shall do using the EF Core CLI.
Creating a Migration
which is actually a changeset of all the code-level schema changes we’ve made that needs to be updated on to the database. EF Core maintains these changesets in the form of c# classes under the directory Migrations. And when we run the below command:
> dotnet ef migrations add 'InitialCreate' <Or any other name you may wish to use>
the library auto generates a class InitialCreate which contains two methods Up and Down. Up corresponds to the changes that need to be replicated when the database is generated, and Down corresponds to the changes that need to be removed. One the above command completes, we can start to observe the changes reflected into our database.
step 2: We commit these changes onto the database, using the below command:
> dotnet ef database update
When we run the application, the EF Core establishes a mapping between the auto generated table Users, with the DbSet property Users in the DatabaseContext class. To query data from the table Users, we can simply run a query on the local property Users of the DatabaseContext object. This object is accessed by injecting the object wherever is required.
The lifetime of a Database Context by default is Scoped inside the Dotnet Core DI. Hence you can’t inject the Context inside a Singleton service directly.
Instead, you can inject IServiceProvider inside a Singleton and then request an instance of DatabaseContext through it. Read more about Service Lifetimes and handling cross service dependencies here
Data Manipulation using Context
public MyController : Controller
{
DatabaseContext context;
public MyController(DatabaseContext ctx)
{
this.context = ctx;
}
public User Query()
{
// querying for records
var record = context.Users.Where(X => x.Id == 1).FirstOrDefault();
// returns the first record of the table Users which has Id value 1.
return record;
}
public void Insert()
{
var user = new User { Name = "Alan" };
// add a record to the table
context.Users.Add(user);
// commit changes to table
// which actually writes the object onto the database
context.SaveChanges();
}
public void Update()
{
// Fetch Record to update
var record = context.Users.FirstOrDefault(x => x.Id == 1);
// do the update
record.Name = "Bibo";
// the ORM tracks the changes
// onto the object modified and
// commits changes back to database
context.SaveChanges();
}
public void Delete()
{
// fetch record to delete
var record = context.Users.FirstOrDefault(x => x.Id == 1);
// remove record from the table
context.Users.Remove(record);
// commit changes to table
// which actually deletes the record from the database
context.SaveChanges();
}
}
Although the above approach may seem simple, its actually a bad design to use Context object directly into the calling class. It is because it completely exposes the context and there’s no separation between the business and data layers.
We can solve this by encapsulating our data access operations inside a Repository class where the DbContext is injected and used for the data operations.
You can find the detailed explanation for this in this article – Repository Pattern and Usage in EF Core Explained