Getting Started With Entity Framework Core

In this tutorial, you'll find a comprehensive guide to mastering Entity Framework Core (EF Core), from installation to advanced features. We'll walk you through the essentials of EF Core, starting with an exploration of its core concepts and the two primary development approaches: Database-First and Model-First. Beyond the basics, you'll learn how to use visual design tools, like Entity Developer, and code-based techniques to create and manage entities, relationships, and database contexts.

What is Entity Framework Core?

Entity Framework Core is a powerful open-source and cross-platform Object-Relational Mapper (ORM) developed by Microsoft for .NET developers.

Placed between a .NET application and a database server, this ORM tool maps .NET objects to database tables and translates C# data access code into SQL statements that the database server can handle easily.

EF Core integration into .NET projects offers numerous benefits for developers:

  • There is no need to learn a new programming language, as it is enough to use C# and LINQ.
  • It's possible to keep C# models in sync with the database tables.
  • During execution, EF Core tracks changes made to C# entities so it can effectively handle their storage.
  • EF Core grants support for multiple database providers.

With Entity Framework Core, developers can interact with databases using .NET (C#) objects rather than writing SQL queries. This approach undoubtedly brings more simplicity and flexibility to data access when building .NET applications.

Database-First and Model-First approaches

Entity Framework Core offers developers two primary approaches to developing data models and databases: Database-First and Model-First.

The Database-First approach is ideal when working with an existing database, allowing developers to reverse-engineer the model and quickly generate code from the database schema. This method is very popular among programmers who prioritize database design and want to work with an existing database structure.

Meanwhile, the essential concept of the Model-First approach is defining the data model first and then generating both the database schema and the corresponding entity classes from that model. This approach is beneficial if you want to focus on designing your data model first and have EF Core automatically generate the necessary code and database schema for you.

You can apply Entity Developer in EF Core development regardless of whether you choose the Model-First or Database-First approach. The implementation of this visual ORM builder will make the process more intuitive and efficient, offering powerful design tools, flexible configurations, and smooth integration with your database. The graphical interface of Entity Developer allows you to create the entities without writing code, after which Entity Framework generates both the domain classes and the corresponding database schema.

Further in this tutorial, we'll discuss each EF Core development approach in more detail.

Visual design tool or code-based method

Entity Framework offers two ways to build Object-Relational Mapping (ORM) models: using a visual design tool like Entity Developer by Devart or writing code manually.

By default, Entity Framework Core relies on code-based methods to build ORMs, which means that developers must create and manage complex data models by hand.

In contrast, Entity Developer is a powerful, user-friendly graphical tool offering an intuitive drag-and-drop interface for quicker model creation and easier visualization of relationships between different data entities without writing much code. It is ideal for those who prefer a more visual, less code-intensive way of working with EF Core.

Database Approach in Entity Developer

Pros and cons of using a visual design tool

Advantages

  • Faster application development
  • Visualization for nearly all kinds of mapping
  • Clear visual representation of the data model
  • Deployment of changes in models to databases and vice versa
  • Support for the Model-First and Database-First approaches
  • Operations with custom templates
  • Reduction of errors
  • More efficient collaboration

Disadvantages

  • Limited control for more complex or custom configurations
  • Customization limitations can be handled through manual configurations
  • The learning curve required to master the tool's complex features

Pros and cons of the code-based method

On the other hand, the code-based approach involves manual coding to define entities, relationships, and configurations using EF Core's fluent API or data annotations. This method gives experienced developers complete control over the model and database schema, which they can use to fine-tune their configurations.

Advantages

  • Independent, free method
  • Total control over the code
  • Requires a minimum of memory and resources on the computer

Disadvantages

  • Much manual coding requires programming experience
  • Requires a high learning curve for beginners to use the method
  • Slower setup due to building larger models with many complex relationships
  • High error risks for developers, especially when dealing with complex configurations
  • Limited multitasking capabilities when working with several files or projects

Install EF Core packages

To get started with EF Core in your project, you need to install the necessary EF Core packages along with other data provider packages for the database in use (e.g., Oracle, MySQL, PostgreSQL, SQLite, etc.). The following instructions outline the process of installing the essential EF Core NuGet packages by running corresponding commands from the operating system's command line. Also, throughout this section, we'll be using PostgreSQL as an example database to illustrate the installation of the data provider packages.

1. To install the core package of Entity Framework, execute the following .NET CLI command:

dotnet add package Microsoft.EntityFrameworkCore

2. Next, install the following package required for the EF Core tools to work:

dotnet tool install Microsoft.EntityFrameworkCore.Tools

3. To install the data provider package for PostgreSQL, execute the following command:

dotnet add package Devart.Data.PostgreSql.EFCore

Upon executing this set of commands, all the necessary EF Core packages are installed. By running this series of commands, you will have all the necessary EF Core packages installed.

Model-First approach

Model-First Approach

In the Model-First approach, the emphasis is on creating the data model first, and then generating both the database schema and the corresponding entity classes from that model.

Using Entity Framework Core, you can define your data model either visually with Entity Developer or through code.

For a better understanding of the main configuration steps, you can find detailed instructions for each method in the corresponding Via Entity Developer and Via code sections.

If you choose Entity Developer, make sure you have installed this application by following the on-screen instructions.

Model-First: Add entities

By adding entities, we mean the creation and configuration of classes and their properties that represent the data you want to map to a specific database table.

Via Entity Developer

With the help of Entity Developer, you can create an empty model using the Model Wizard or supplement the previously developed model. In our case, we will create a new data model from scratch and configure its entities afterward.

Create an EF Core model

1. Open your project in Visual Studio. As we're using PostgreSQL as an example data provider, we'll open the PgEFCore project.

2. Right-click the PgEFCore project in the Solution Explorer and select Add > New Item.

Add new item

3. Go to Installed > C# Items > Data, select Devart EF Core Model, and click Add.

Add a New Item dialog with a new model to be created

4. In the Create Model Wizard, select Model First and click Next.

Create Model Wizard with the selected Model First option

5. On the Model properties page, define the main settings of your model and click Next. Thus, you can select the target EF Core version from the ones supported by the .NET frameworks of your project and specify the name in the Entities Namespace field.

Specify the main settings of your model on the Model properties page

6. Choose code generation templates for the objects that you want to add to the new model. In the properties area of this wizard, you can configure different parameters you want the object to follow. Let's use the default settings for this tutorial and click Next.

Choose a template on the Code Generation Templates page of the Create Model Wizard

Your model is ready now.

Create Model Wizard with a message about the successful creation of the model

7. Finally, click Finish.

As a result, the model you've just created opens.

The new DataModel file is opened in Visual Studio

Add new classes and properties

Once the newly created model opens, you can begin with creating classes and configuring properties.

1. In the Model Explorer pane, right-click your model and select New Class.

In the Model Explorer pane, select New Class to add a new class

2. In the Class Editor dialog, enter Customers in the Name field and click OK.

Specify the class name in the Class Editor dialog

3. Once the Customers class is created, drag it onto the model to begin building the diagram.

4. To define the class properties, right-click Customers and select Add > New Property.

Add a new property to the class

5. In the Property Editor dialog, configure the following Customers properties and click OK.

  • Name: Id
  • Type: String
  • Value Generated: Never

6. Optional: To change the default column settings, in the Column field, click More (...) to open the Column Editor and define the desired column attributes. Then, click OK to save your settings.

Configure new property settings and change default column settings

Take the same workflow to add more classes and properties as needed into your model. For example, we'll create and configure two classes, Customers and Orders. Then, we will extend the model by adding the third entity.

Add associations

After you've added the required classes to your data model, you can move on to establishing an association between Customers and Orders.

To create an association, perform the following sequence of actions:

1. Right-click the Customers in the model and select Add > New Association from the context menu.

Add new association

2. In the Association Editor dialog that appears, configure the required properties and click OK.

  • Cardinality: OneToMany
  • On Delete Action: NoAction
  • Orders Properties: OrderId
Association Editor

Once added, the new association will appear in the diagram and under Associations in the Model Explorer pane.

Established association

Add inheritances

If desired, you can extend the current model by adding a third PremiumCustomer class, which will be configured as a derived class from the previously created Customers class. The process of adding an inheritance is very similar to the one of adding an association:

1. Create a new PremiumCustomer class and configure its properties.

2. Right-click the model and select Add > New Inheritance from the context menu.

Add a new inheritance

3. In the Inheritance Editor dialog, configure the following properties and click OK:

  • Base Class: Customers
  • Derived Class: PremiumCustomer
  • Type: Table Per Hierarchy
Add a new inheritance

As a result, PremiumCustomer becomes a subclass of Customers, thus extending our model with the inheritance.

Add a new inheritance

Via code

With this approach, you can manually define your data model through coding by creating classes with properties that map to columns in a database table.

To implement the code-based method, follow the steps below:

1. Install the necessary NuGet packages for Entity Framework Core and the database provider (here: Devart PostgreSQL):

dotnet add package Devart.Data.PostgreSql.EFCore
dotnet add package Microsoft.EntityFrameworkCore.Design

2. Create a new file named Customers.cs in your project and define the Customers class as follows:

public class Customers
{
	public int Id { get; set; }
	public required string Name { get; set; }
	public required string Email { get; set; }
	public required string OrdersCount { get; set; }
	public DateOnly LastOrder { get; set; }
}

This C# class with properties for each column will represent the structure of the Customers table in your database.

Model-First: Add context

Adding a DbContext plays a critical role in connecting your application to the database. It acts as a bridge between your app and the database and is responsible for querying and saving data.

You can add context programmatically through code by creating a DbContext class. Alternatively, tools like Entity Developer from Devart provide a visual designer to define entities and relationships, automatically generating the DbContext and entity classes.

Via Entity Developer

You have to save your model to finalize its creation and configuration. Once saved, the model context files will be successfully generated and stored in the specified folders in the Solution Explorer under PgEFCore.

Clicking any of the displayed folders reveals their content, as shown on the following screen.

View Model diagram content

Via code

Here, you'll find brief instructions on creating a new DbContext class using manual coding.

For example, if we want to add the ApplicationDbContext class, we need to create a new file named ApplicationDbContext.cs and define the ApplicationDbContext class as follows:

using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext: DbContext {
  public DbSet < Customers > Customers {
	get;
	set;
  }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
	optionsBuilder.UsePgSql("YourConnectionStringHere");
  }

  protected override void OnModelCreating(ModelBuilder modelBuilder) {
	modelBuilder.Entity < Customers > (entity => {
  	entity.HasKey(e => e.Id);
  	entity.Property(e => e.Name).IsRequired();
  	entity.Property(e => e.Email).IsRequired();
  	entity.Property(e => e.OrdersCount).IsRequired();
  	entity.Property(e => e.LastOrder).HasColumnType("date");
	});
  }
}

Model-First: Generate your database

After defining your model following the Model-First approach in EF Core, you can generate the database schema from it.

You can generate your database through traditional coding or by applying Devart's Entity Developer, which offers a visual alternative to manual coding.

Via Entity Developer

With Entity Developer, you can generate database scripts from a particular model. To achieve this, follow the step-by-step instructions below:

1. Right-click anywhere in the model and select Generate Database Script From Model from the context menu.

Generate database script from model

2. In the Generate Database Script Wizard window, leave the Include Drop and Regenerate Storage checkboxes cleared and click Next to proceed.

Welcome to Entity Developer

3. Configure the model synchronization settings and click Next.

Model synchronization settings

4. Review the list of tables that will be scripted and clear checkboxes next to any tables you do not want to be included. Click Next to continue.

Choose change actions

5. Now, review the script to ensure it is successfully generated. Click Finish to close the wizard.

Add new class

Via code

With this approach, you configure domain classes first, and Entity Framework automatically generates the database tables. Additionally, you can manage schema changes through code using the Migration feature.

Below, you will find a list of code-based commands to execute.

1. To add a new migration, open your terminal, navigate to your project directory, and run the command:

dotnet ef migrations add InitialCreate

This command will generate a new migration file in your project's Migrations folder. The migration file will contain the necessary code to create the Customers table in your database.

2. After creating the migration, apply it to your database using the following command:

dotnet ef database update

This command will execute the migration and update your database schema to match the current state of your DbContext and entity models.

Database-First approach

Database-First Approach

In contrast to Model-First, the Database-First approach in EF Core means that a model is generated from an existing database rather than creating a new model from the beginning.

Database-First: Scaffold entity classes and DbContext

You can scaffold entity classes and DbContext in EF Core either through code or with Entity Developer.

Via Entity Developer

1. Right-click in the Solution Explorer pane and select Add > New Item.

2. Go to Installed > C# Items > Data, select Devart EF Core Model, and click Add.

3. In the Create Model Wizard, select Database First and click Next.

Create a Model Wizard showing the selected Database First option

4. Fill in the details of your database connection and click Next.

Set up data connection in the Create Model Wizard

5. Select Generate From Database and click Next.

Choose the Model Components page of the Create Model Wizard

6. Choose the database objects you want to scaffold. You can select either all checkboxes or only some of them. Click Next to proceed.

Select the database objects page of the Create Model Wizard

7. Define the naming convention for the property names in the database object and click Next. We suggest keeping the default settings this time.

Set up the naming rules page of the Create Model Wizard

8. On the Model properties page, change only the Target Framework field. Select .NET 8 (or a different framework your project uses) and click Next.

Model properties page of the Create Model Wizard

9. Choose the model diagram contents. You can use all entities, split the entities by database, or do a custom selection. For this tutorial, select All Entities and click Next.

Choose the Model Diagram Contents page of the Create Model Wizard

10. Choose the code generation templates for your objects. You can define different parameters you want the object to follow. Let's use the default settings for this tutorial. Click Next.

Choose the Code Generation Templates page of the Create Model Wizard

Your model is ready now.

11. Finally, click Finish.

Create a Model Wizard with a message about the successful creation of the model

Your created model opens.

DataModel file opened in Visual Studio

Via code

This approach uses the command line to scaffold a DbContext from an existing database. Prior to using this approach, you need to make sure that your PostgreSQL server is running and accessible.

Let's scaffold entity classes and DbContext through code by running the commands below:

1. Use the dotnet ef dbcontext scaffold command to generate the DbContext and entity classes from your existing database.

dotnet ef dbcontext scaffold "YourConnectionStringHere"
Devart.Data.PostgreSql -o Models -c ApplicationDbContext

2. Replace the placeholders with your actual connection string and other details.

  • "YourConnectionStringHere": Your PostgreSQL connection string.
  • Devart.Data.PostgreSql: The provider to use.
  • -o Models: The output directory for the generated files.
  • -c ApplicationDbContext: The name of the DbContext class to generate.

The scaffolding command will generate the DbContext class and entity classes in the specified output directory (Models in this case).

3. Review and modify the generated files as needed.

For a better understanding of the overall process, we provide an example showing how the generated DbContext class might look like:

 using Microsoft.EntityFrameworkCore;

public partial class ApplicationDbContext: DbContext {
  public ApplicationDbContext() {}

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

  public virtual DbSet < Customers > Customers {
    get;
    set;
  }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    if (!optionsBuilder.IsConfigured) {
      optionsBuilder.UsePgSql("YourConnectionStringHere");
    }
  }

  protected override void OnModelCreating(ModelBuilder modelBuilder) {
    modelBuilder.Entity < Customers > (entity => {
      entity.HasKey(e => e.Id);
      entity.Property(e => e.Name).IsRequired();
      entity.Property(e => e.Email).IsRequired();
      entity.Property(e => e.OrdersCount).IsRequired();
      entity.Property(e => e.LastOrder).HasColumnType("date");
    });

    OnModelCreatingPartial(modelBuilder);
  }

  partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

You can now use the generated ApplicationDbContext in your application, as shown above.

Please keep in mind that the scaffolding process will generate entity classes based on the existing database schema. If your database schema changes, you may need to regenerate the entity classes.

Implement CRUD operations

Entity Framework (EF) Core simplifies database operations through its Object-Relational Mapping (ORM) capabilities. In this part of the tutorial, we'll give detailed instructions on implementing CRUD (Create, Read, Update, Delete) operations using EF Core based on the provided code examples.

Create

To add a new record to the database, create a model object, set its properties, and add it to the DbContext's DbSet. Calling SaveChanges preserves the record in the database. For example, a new Customer is created with properties like FirstName, LastName, and Email, added to the Customers DbSet, and saved.

using(var context = new DvdRentalContext()) {
  // Create a new Customer object
  var newCustomer = new Customer {
	FirstName = "John",
  	LastName = "Doe",
  	Email = "[email protected]"
	// Set other properties as needed
  };

  // Add the new customer to the context
  context.Customers.Add(newCustomer);

  // Save changes to the database
  context.SaveChanges();
}

Read

To read data, you can use LINQ to query the DbContext's DbSet and retrieve the data. The example queries all customers with context.Customers.ToList(), retrieving the entire Customers table as a list. The results are then iterated and displayed, showing each customer's ID, name, and email.

using(var context = new DvdRentalContext()) {
  // Query the Customer table
  var customers = context.Customers.ToList();

  // Display the customers
  foreach(var customer in customers) {
	Console.WriteLine($"Customer ID: {customer.CustomerId}, Name: {customer.FirstName} {customer.LastName}, Email: {customer.Email}");
  }
}

Update

To update a record, locate the entity using a LINQ query (e.g., FirstOrDefault to find a customer by name). Change the properties of the retrieved object, and call SaveChanges to store the changes. As you can see, the following example updates a customer's first and last names.

using(var context = new DvdRentalContext()) {
  // Find the customer with the name "John Doe"
  var customer = context.Customers
	.FirstOrDefault(c => c.FirstName == "John" && c.LastName == "Doe");

  if (customer != null) {
	// Update the customer's name
	customer.FirstName = "Jane";
	customer.LastName = "Smith";

	// Save changes to the database
	context.SaveChanges();

	Console.WriteLine("Customer name updated successfully!");
  } else {
	Console.WriteLine("Customer not found.");
  }
}

Delete

To delete a record, find the entity using a LINQ query, remove it from the DbSet context with Remove, and call SaveChanges to apply the deletion. The example deletes a customer by name, confirming the action or reporting if the customer is not found.

using(var context = new DvdRentalContext()) {
  // Find the customer with the name "John Doe"
  var customer = context.Customers
	.FirstOrDefault(c => c.FirstName == "John" && c.LastName == "Doe");

  if (customer != null) {
	// Remove the customer from the context
	context.Customers.Remove(customer);

	// Save changes to the database
	context.SaveChanges();

	Console.WriteLine("Customer deleted successfully!");
  } else {
	Console.WriteLine("Customer not found.");
  }
}

Each of the CRUD operations ensures easy and efficient data management in your EF Core-based application. These methods cover the essential tasks for inserting, reading, updating, and deleting records in the database.

EF Core performance tips

As we already mentioned in this tutorial, Entity Framework Core is a powerful ORM, but inefficient usage can lead to performance issues.

We will give you detailed and clear instructions on how to optimize your database operations by following the key EF Core performance tips:

  • Avoid query operations in loops: Prevents the N+1 query problem.
  • Select only important columns: Reduces data transfer.
  • Use the NoTracking method: Minimizes memory overhead.
  • Use SplitQuery: Separates large queries for better execution efficiency.

Implementation of these practices ensures faster, more scalable applications.

Avoid query operations in loops

The problem lies in performing database query operations inside loops, a common mistake among junior developers. This leads to the N+1 query problem, as querying inside loops sends multiple database queries (e.g., 100 queries for 100 entities). To avoid this, use batch queries to fetch data once and process it in memory.

Example of inefficient code (query in a loop)

public void QueryInsideLoop()
{
    using var context = new MyDbContext();

    for (int i = 1; i <= 100; i++)
    {
        var entity = context.MyEntities.FirstOrDefault(e => e.Id == i);
    }
}

This code executes 100 separate queries and results in poor performance.

Example of efficient code (batch query)

public void QueryOutsideLoop()
{
    using var context = new MyDbContext();

    var entities = context.MyEntities
        .Where(e => e.Id <= 100)
        .ToList();

    foreach (var entity in entities)
    {
        var id = entity.Id; // Simulating loop logic
    }
}

Running this code allows fetching all entities in one query, reducing database load and improving performance. Keep in mind, though, that batch querying is ideal for small datasets to avoid excessive memory usage.

Select only important columns

Usually, it is not necessary to retrieve all the columns from a table when only a few are needed, as this results in slower performance. For example, if you don't need all 20 columns from the table but Name, Surname, and Year of birth, you wouldn't like to extract all the rest columns in the table.

Example of inefficient query (loading all columns)

public void SelectAllColumns()
{
    using var context = new MyDbContext();

    var results = context.MyEntities.ToList();
}

Instead, we recommend using projections with LINQ and selecting only the necessary columns. You can achieve this using anonymous types or a DTO (Data Transfer Object).

Example of optimized query (selecting specific columns)

public void SelectImportantColumns()
{
    using var context = new MyDbContext();

    var results = context.MyEntities.Select(e => new { e.Id, e.Name }).ToList();
}

With fewer columns retrieved, you only load the data you need, reducing memory and bandwidth usage.

Use the NoTracking method

By default, Entity Framework Core tracks changes to the retrieved entities. Tracking is useful when updating and deleting entity objects, but it adds additional overhead for read-only queries. To overcome this and optimize performance, use the NoTracking method to disable change tracking.

Example of default tracking

public void SelectWithTracking()
{
    using var context = new MyDbContext();

    var results = context.MyEntities.ToList();
}

Here, EF Core tracks all entities, increasing overhead due to change tracking.

If you would like to disable tracking, we recommend applying the NoTracking method. In this case, the Entity Framework doesn't track changes to the retrieved entities, which reduces overhead and increases performance, especially for read-only data.

Example of using AsNoTracking

public void SelectWithNoTracking()
{
    using var context = new MyDbContext();

    var results = context.MyEntities.AsNoTracking().ToList();
}

Using AsNoTracking for read-only queries is a simple yet effective way to boost the performance of your EF Core applications.

Use SplitQuery to separate queries

By default, EF uses a single-query approach when loading related data. This can potentially result in performance issues like Cartesian explosion due to large JOIN queries. The code example that follows vividly demonstrates such a situation.

Examples of using SingleQuery

public void DefaultSingleQuery()
{
    using var context = new MyDbContext();

    var results = context.MyEntities
        .Include(e => e.RelatedEntities)
        .ToList();
}

To optimize complex queries, use the SplitQuery option instead of applying SingleQuery. This allows Entity Framework to execute separate queries for the primary entity and its related entities, reducing the impact of large JOINs.

Examples of using SplitQuery

public void UsingSplitQuery()
{
    using var context = new MyDbContext();

    var results = context.MyEntities
        .Include(e => e.RelatedEntities)
        .AsSplitQuery()
        .ToList();
}

On the one hand, using AsSplitQuery can increase performance and reduce complexity in such scenarios. On the other hand, it means multiple queries will be executed, so be sure to monitor the impact on the database load.

EF Core features you need to know

It goes without saying that Entity Framework Core is a robust tool for data access in .NET applications. However, diving into every detail of this ORM tool might not be our top priority. Nevertheless, understanding a few of the key features of EF Core can save you significant time and effort. That's why, rather than overwhelming you with every feature, we've selected five essential ones you really need to know:

  • Query Splitting: Optimizes your database queries.
  • Bulk Updates and Deletes: Improves the overall performance.
  • Raw SQL Queries: Gains more control when needed.
  • Query Filters: Maintains clean and efficient queries.
  • Eager Loading: Optimizes data retrieval

Query splitting

Query splitting is especially useful when loading multiple collections in Entity Framework (EF) Core. It helps prevent the Cartesian explosion problem, which can degrade performance. For example, let's assume we want to retrieve a department's information and its team's and employees' data. To achieve this, we can write the following query:

Department department =
    context.Departments
        .Include(d => d.Teams)
        .Include(d => d.Employees)
        .Where(d => d.Id == departmentId)
        .First();

This query translates to a single SQL statement with two JOIN operations. However, because the JOIN operations occur at the same level, the database generates a cross-product. Each row from the Teams table is paired with each row from the Employees table, resulting in many rows being returned. This significantly degrades the overall performance.

We can avoid these performance issues with query splitting, as shown below:

Department department =
    context.Departments
        .Include(d => d.Teams)
        .Include(d => d.Employees)
        .Where(d => d.Id == departmentId)
        .AsSplitQuery()
        .First();

With AsSplitQuery, EF Core executes a separate SQL query for each data collection and navigation. This also results in reducing the number of rows returned, thus improving the overall performance.

Bulk updates and deletes

Entity Framework (EF) Core 7.0 introduced two APIs, ExecuteUpdate and ExecuteDelete, for performing bulk updates and deletes. These methods enable you to efficiently update a large number of rows in one roundtrip to the database instead of using iterative approaches.

Let's see how these methods work in practice. Suppose the company has decided to give a 5% raise to all employees in the "Sales" department. Using a traditional approach, we will iterate through each employee and update their salary individually.

var salesEmployees = context.Employees
    .Where(e => e.Department == "Sales")
    .ToList();

foreach (var employee in salesEmployees)
{
    employee.Salary *= 1.05m;
}

context.SaveChanges();

This traditional method results in multiple database roundtrips, which can be slow and resource-intensive, especially for large datasets. We can achieve the same result in a single round trip using ExecuteUpdate:

context.Employees
    .Where(e => e.Department == "Sales")
    .ExecuteUpdate(s => s.SetProperty(e => e.Salary, e => e.Salary * 1.05m));

This approach executes a single SQL UPDATE statement, directly modifying the salaries in the database without loading entities into memory. The result is faster execution and reduced resource usage.

Raw SQL queries

Entity Framework Core 8 added a new feature that allows querying unmapped types with raw SQL. This is useful when we need to retrieve data from a database view, stored procedure, or a table that does not directly correspond to any of our entity classes.

Let's assume we want to retrieve a sales summary for each product. With EF Core 8, we can create a simple ProductSummary class representing the structure of the result set and query it directly:

public class ProductSummary
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public decimal TotalSales { get; set; }
}

var productSummaries = await context.Database
    .SqlQuery<ProductSummary>(
        @$"""
        SELECT p.ProductId, p.ProductName, SUM(oi.Quantity * oi.UnitPrice) AS TotalSales
        FROM Products p
        JOIN OrderItems oi ON p.ProductId = oi.ProductId
        WHERE p.CategoryId = {categoryId}
        GROUP BY p.ProductId, p.ProductName
        """)
    .ToListAsync();
                

The SqlQuery method returns an IQueryable, which allows you to combine the flexibility of raw SQL queries with the expressiveness of LINQ.

For security reasons, you should use parameterized queries to prevent SQL injection vulnerabilities. The SqlQuery method accepts a FormattableString, which means you can safely use an interpolated string. Each argument is converted to a SQL parameter.

Query filters

Query filters are like reusable WHERE clauses you can apply to your entities. These filters are automatically included in LINQ queries whenever you retrieve entities of the corresponding type. This ensures that you don't have to duplicate the same filtering criteria across your application.

For example, in a multi-tenant application, you may need to filter data based on the current tenant. Query filters make this easy to implement:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    // Associate products with tenants
    public int TenantId { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // The current TenantId is set based on the current request/context
    modelBuilder.Entity<Product>().HasQueryFilter(p => p.TenantId == _currentTenantId);
}

// Now, queries automatically filter based on the tenant:
var productsForCurrentTenant = context.Products.ToList();
                

When configuring multiple query filters on the same entity, only the last filter is applied. To combine multiple filters, use the && (AND) and || (OR) operators.

When needed, you can bypass filters in specific queries using the IgnoreQueryFilters method.

Eager loading

Eager loading in EF Core enables you to load related entities along with the main entity in a single database query. This approach improves application performance by reducing the number of database queries, especially for complex object graphs or scenarios where lazy loading could lead to multiple inefficient queries.

The following example demonstrates a VerifyEmail use case. It loads an EmailVerificationToken and its related User entity using the Include method to modify both entities in a single operation.

internal sealed class VerifyEmail(AppDbContext context)
{
    public async Task<bool> Handle(Guid tokenId)
    {
        EmailVerificationToken? token = await context.EmailVerificationTokens
            .Include(e => e.User)
            .FirstOrDefaultAsync(e => e.Id == tokenId);

        if (token is null || token.ExpiresOnUtc < DateTime.UtcNow || token.User.EmailVerified)
        {
            return false;
        }

        token.User.EmailVerified = true;

        context.EmailVerificationTokens.Remove(token);

        await context.SaveChangesAsync();

        return true;
    }
}
                

In this example, EF Core generates a single SQL query that joins the EmailVerificationToken and User tables and successfully retrieves all the required data.

Eager loading (and query splitting, discussed earlier) is not a universal solution. To optimize performance and avoid retrieving unnecessary data, consider using projections when you need only specific properties from related entities.

Additional Entity Developer benefits

Apart from the Entity Developer key features, there are a variety of additional benefits worth mentioning. Here, we will briefly explore the essential advantages that users can gain from Entity Developer.

Data sources

Devart Entity Developer is compatible with a broad selection of databases and online services, allowing developers to operate effortlessly across different platforms. It provides native support for popular databases such as SQL Server, MySQL, Oracle, PostgreSQL, SQLite, Firebird, and DB2.

Additionally, it connects to a wide range of cloud platforms using dotConnect and open-source data providers: Salesforce, Salesforce Marketing Cloud, QuickBooks Online, SugarCRM, Dynamics CRM (Dynamics 365), Zoho Books, Zoho CRM, Zoho Desk, Adobe Commerce and more. This flexibility ensures that developers can use Entity Developer in diverse environments with no need to switch tools, facilitating workflows and reducing the learning curve for managing different data sources.

Wide mapping

Entity Developer supports advanced mapping scenarios, including one-to-one, one-to-many, and many-to-many relationships, with an intuitive drag-and-drop interface. Developers can visually define associations, inheritance hierarchies, and complex JOINs without writing extensive code.

As part of Entity Developer, wide mapping allows you to perform the following actions:

  • Enable visual mapping for all compatible ORMs
  • Provide visual editors for classes, properties, complex types, and enums
  • Support multiple inheritance types and associations
  • Map CRUD operations to stored procedures
  • Generate methods from stored routines or command texts
  • Visually edit the storage layer of Entity Framework v1-v6 models
  • Support table splitting, entity splitting, and query views

Powerful refactoring

The Model Refactoring wizard allows you to quickly configure mapping aspects that cannot be automatically generated from your database. With its help, you can create the following entities:

  • Inheritance hierarchies
  • Complex types
  • Table splitting

Code generation templates

The template-based code generation offered by Entity Developer meets the requirements of even the most demanding users.

Using different types of templates that are included in the application, you can fine-tune the code generation process according to your particular needs.

Here's a brief list of the operations you can do following this approach:

  • Generate C# or Visual Basic code
  • Create code using T4 templates
  • Edit templates in a convenient editor with syntax highlighting
  • Store templates with models or in ED resources
  • Add properties for model objects that affect code generation
  • Get unlimited generation capabilities

Conclusion

This tutorial gives detailed instructions on how to get started with Entity Framework Core, a powerful and popular ORM that simplifies data access and accelerates development in modern .NET applications. EF Core enables developers to implement either the Model-First or Database-First approach. The implementation of the Model-First method means that the developer defines the model first, and EF generates a database using this model. Meanwhile, in the Database-First approach, the model is generated using an existing database. Both approaches are thoroughly explained in this tutorial, with detailed code snippets, illustrations, screenshots, and additional resources. Additionally, we covered tools like Devart Entity Developer, which enhances the EF Core experience, enabling much faster development while maintaining high code quality.

Connect to Data Effortlessly in .NET

Streamline your .NET Projects with feature-rich ADO.NET providers